SBCL的数组可以有类型化数组作为元素吗?

3

考虑这个简单的例子:

(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 呢?

根据下面的讨论,简而言之,SBCL将外部数组的元素报告为“T”,因为内部数组是引用类型。它似乎只报告原始类型作为数组元素,而将引用类型升级为“T”。内部数组的类型将被正确报告。 - Werdok
1
你应该查看 upgraded-array-element-type - user5920214
2个回答

7

元素类型的背景知识

如果你给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没有针对结构数组,向量,CLOS对象等的优化存储布局。由于存储了每个元素的指针,因此访问始终需要间接引用,并且没有任何东西可以保证这些对象按线性顺序存储在内存中。它们在数组中存储的是指向它们的指针。
检查您的实现是否针对浮点数(single, double, long, ...)数组具有优化的空间布局。
Common Lisp支持真正的多维数组,最多具有ARRAY-RANK-LIMIT(ABCL在我的ARM上最多具有8个维度,其他某些实现支持更多维度)。这些多维数组也可以具有专门的元素类型。

4
似乎这是一个XY问题:您最好使用一个多维浮点数数组:
(make-array (list width height) ...)

如果你使用(aref matrix row column),就无需计算索引。当你将一个数组存储在另一个数组中时,仍然需要保留与每个数组关联的元数据,例如其元素类型,因为您可能从其他地方引用每个数组。这就是为什么主数组只存储引用而不是原始浮点数。

还要注意,由于数组升级,可以存储在数组中的类型可以是声明类型的超类型:系统类数组


我认为在这种情况下,使用二维数组会更好地提高性能,因为可以直接访问其中的值,而不是像数组中嵌套数组的方法只能保存子数组的指针。 - Werdok
@Werdok 是的(但要注意避免过早优化)。 - coredump
1
@Werdok,你也可以使用“array-total-size”和“row-major-aref”来访问数组作为一个平面的数组;我认为可以使用SBCL的通用序列来使用这些函数操作多维数组,将其作为序列来操作;这看起来很有趣。 - coredump
2
你也可以创建一个单维位移数组来进行映射/读取。 - jkiiski
1
@Werdok 是的,对于性能而言,使用位移数组会输给直接操作数组。对于实际应用,您应该尝试并分析两种方法,看看方便是否值得代价。在大多数应用程序编程中,额外的分配将是微不足道的,但对于性能敏感的应用程序来说,这可能不是一个好主意。 - jkiiski
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接