考虑这个简单的例子:
(deftype image nil '(simple-array single-float (100)))
在这里,我们为一个类型定义一个简写,该类型是一个保存单个浮点数的数组。让我们尝试创建一个这样的:
(defparameter tmp
(make-array 100
:element-type 'single-float
:initial-element 0.0))
让我们检查一下类型以防万一:
CL-USER> (type-of tmp)
(SIMPLE-ARRAY SINGLE-FLOAT (100))
都很好。让我们看看是否可以将这些小数组放在另一个数组中,以使检索更容易,而不是将所有内容放入一维数组中并最终在计算访问索引时头疼。
(defparameter image-array
(make-array 10
:element-type 'image
:initial-element tmp))
它不可能失败,但以防万一:
CL-USER> (type-of image-array)
(SIMPLE-VECTOR 10)
哎呀,这根本不是我们想要的。似乎这个新数组默认为默认元素类型:
CL-USER> (array-element-type image-array)
T
这可能意味着应用程序现在不仅必须对容器数组元素进行类型检查,还必须对子数组的元素进行类型检查,并对性能产生所有影响。出现的问题是:
在SBCL 中是否可以将类型化数组作为数组元素存储在另一个数组中?
编辑:这可能有点太早了,因为它返回正确的类型:
CL-USER> (type-of (aref image-array 0))
(SIMPLE-ARRAY SINGLE-FLOAT (100))
在这种情况下,为什么我们会从(array-element-type image-array)
获得T
元素类型?
元素类型实际含义的背景
如果您为MAKE-ARRAY
指定元素类型,则要求 Common Lisp 实现创建一个具有优化空间布局 (!( 的数组,该数组可能仅限于某些元素类型。您不需要为此元素类型获取一个数组,而是为此元素类型的此实现中空间效率最高的数组。
-
对于数字,实现可能有位、8 位字节、16 位字、32 位字等特殊版本。
-
它可能具有字符数组的特殊版本,例如字符串
-
它可能具有一个或多个浮点数类型的特殊版本
是否有更多取决于您使用的实现。
对于任何没有特殊实现的元素类型,元素类型将升级到T
。这意味着数组可以包含各种对象作为元素和更大的元素,如数组、字符串、结构、CLOS 对象......将始终存储为指向堆上对象的指针。
某个实现的几个示例:
整数
CL-USER> (upgraded-array-element-type '(integer 0 1))
(UNSIGNED-BYTE 1)
CL-USER> (upgraded-array-element-type '(integer 0 2))
(UNSIGNED-BYTE 2)
CL-USER> (upgraded-array-element-type '(integer 0 3))
(UNSIGNED-BYTE 2)
CL-USER> (upgraded-array-element-type '(integer 0 4))
(UNSIGNED-BYTE 4)
CL-USER> (upgraded-array-element-type '(integer 0 5))
(UNSIGNED-BYTE 4)
CL-USER> (upgraded-array-element-type '(integer 0 7))
(UNSIGNED-BYTE 4)
CL-USER> (upgraded-array-element-type '(integer 0 8))
(UNSIGNED-BYTE 4)
CL-USER> (upgraded-array-element-type '(integer 0 15))
(UNSIGNED-BYTE 4)
CL-USER> (upgraded-array-element-type '(integer 0 16))
(UNSIGNED-BYTE 8)
CL-USER> (upgraded-array-element-type '(integer 0 256))
(UNSIGNED-BYTE 16)
CL-USER> (upgraded-array-element-type '(integer 0 4423423))
(UNSIGNED-BYTE 32)
CL-USER> (upgraded-array-element-type '(integer 0 4423423423423))
(UNSIGNED-BYTE 64)
CL-USER> (upgraded-array-element-type '(integer 0 4423423423423423423423423423423))
T
字符
CL-USER> (upgraded-array-element-type 'character)
CHARACTER
浮
CL-USER> (upgraded-array-element-type 'single-float)
SINGLE-FLOAT
CL-USER> (upgraded-array-element-type 'long-float)
DOUBLE-FLOAT
数组
CL-USER> (upgraded-array-element-type 'array)
T
即使您要求数组的更具体版本作为元素,您也很可能会得到T
作为答案。
何时要求特殊阵列
最重要的原因是节省空间。如果你只有位,一般数组可以存储位,但位向量将节省大量空间。
但是:具有特殊元素类型的数组的操作可能会变慢。在运行时,在安全代码中可能会有一个附加的类型检查,并且更改/读取元素的操作可能需要较慢的处理器指令。
Common Lisp 数组的局限性
因此,Common Lisp 没有针对结构数组、向量数组、CLOS 对象数组等优化的存储布局。由于存储的每个元素都有一个指针,因此访问始终需要间接寻址,并且无法保证这些对象以线性顺序存储在内存中。以线性顺序存储的是数组中指向它们的指针。
检查您的实现是否优化了浮点(单、双、长等(数组的空间布局。
多维数组
Common Lisp 支持真正的多维数组,最多ARRAY-RANK-LIMIT
个(ABCL 在我的 ARM 上最多有 8 个维度,其他一些实现支持更多维度(。这些多维数组也可以具有专门的元素类型。
似乎是一个 XY 问题:你最好使用一个多维浮点数组:
(make-array (list width height) ...)
。然后你做(aref matrix row column)
,你不必计算索引。当您将数组存储在数组中时,您仍然需要保持与每个数组关联的元数据,例如其元素的类型,因为您可以从其他地方引用每个数组。这就是为什么主数组只存储引用而不是原始浮点数。
另请注意,由于数组升级,可以存储在数组中的类型可以是声明类型的超类型:系统类 ARRAY。