【问题标题】:Can SBCL arrays have typed arrays as elements?SBCL 数组可以将类型化数组作为元素吗?
【发布时间】:2017-08-07 15:50:25
【问题描述】:

考虑这个简单的例子:

(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。内部数组的类型将被正确报告。
  • 你应该看看upgraded-array-element-type

标签: arrays multidimensional-array lisp common-lisp sbcl


【解决方案1】:

ELEMENT TYPE 实际含义的背景知识

如果你给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 个维度,其他一些实现支持更多维度)。这些多维数组也可以有专门的元素类型。

【讨论】:

    【解决方案2】:

    似乎是一个 XY 问题:您最好使用浮点数的多维数组:

    (make-array (list width height) ...)
    

    ...然后您执行(aref matrix row column) 并且您不必计算索引。当您将数组存储在数组中时,您仍然需要保留与每个数组关联的元数据,例如其元素的类型,因为您可能会从其他地方引用每个数组。这就是主数组只存储引用而不存储原始浮点数的原因。

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

    【讨论】:

    • 我想在这种情况下,二维数组在性能方面会更好,因为与只能保存指向子指针的数组中数组方法相比,可以直接访问其中的值数组。
    • @Werdok 是的(但要注意过早的优化)。
    • @Werdok 您还可以使用“array-total-size”和“row-major-aref”将数组作为平面访问;我认为应该可以使用 SBCL 的通用序列将多维数组作为使用这些函数的序列来操作;这看起来很有趣。
    • 你也可以制作一个单维置换数组来映射/读取。
    • @Werdok 是的,因为性能移位的数组将无法直接操作数组。对于现实世界的使用,您应该尝试分析这两种方法,看看便利是否物有所值。在大多数应用程序编程中,额外的分配可以忽略不计,但对于性能敏感的应用程序,这可能不是一个好主意。
    猜你喜欢
    • 2019-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多