SBCL 数组是否可以将类型化数组作为元素?



考虑这个简单的例子:

(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。

最新更新