Skip to content

LISP - 专用向量

标准的 Lisp 向量是一种通用容器,可以容纳任何类型的 Lisp 对象。这种灵活性是有代价的:每个元素都需要为类型标签和指针分配存储空间,这一概念被称为装箱(boxing)。相比之下,专用向量(specialized vector)经过优化,仅存储一种特定的原始类型元素。这在性能和内存使用方面具有显著优势。

  • 内存效率:通过直接存储原始数据值而不进行装箱(例如,存储原始的 8 位整数而非指向整数对象的指针),专用向量显著减少内存使用。
  • 性能提升:访问元素速度快得多。CPU 可以直接处理数据,提高缓存局部性,并使编译器能够进行强大的 SIMD(单指令多数据)优化。
  • 类型安全:Lisp 系统强制执行类型限制。尝试存储错误类型的元素会发出错误信号,从而及早发现错误。
  • 外部函数接口(FFI):它们对于与用 C 等其他语言编写的库进行接口至关重要,因为它们直接映射到内存中的 C 风格数组。

您可以通过为 make-array 提供 :element-type 参数来创建专用向量。系统将自动选择尽可能紧凑的表示形式。

您可以创建各种整数大小的向量。例如,(unsigned-byte 8) 非常适合存储字节。

;; 创建一个用于存储 5 个整数的向量,每个整数都是一个无符号字节(0-255)。
;; 它用零初始化。
(let ((byte-vector (make-array 5 :element-type '(unsigned-byte 8))))
(print byte-vector)
;; -> #(0 0 0 0 0)
;; 赋值
(setf (aref byte-vector 0) 10)
(setf (aref byte-vector 1) 255)
(print byte-vector)
;; -> #(10 255 0 0 0)
;; 这将引发类型错误,因为 300 超出了 unsigned-byte 8 的范围。
;; (setf (aref byte-vector 2) 300)
)

位向量是存储布尔值(0 或 1)序列的极其紧凑的方式。Lisp 提供了一个方便的读取器宏 #*,用于字面创建它们。

;; 使用读取器宏创建位向量
(let ((bit-v #*10110))
(print bit-v)
;; -> #*10110
;; 访问元素
(print (aref bit-v 2))
;; -> 1
;; 修改元素
(setf (aref bit-v 2) 0)
(print bit-v))
;; -> #*10010

根据定义,Lisp 的 string(字符串)是一种专用的一维数组,其元素类型为 character(字符)。

;; 这些是等效的
(defvar string1 "Hello")
(defvar string2 (make-array 5 :element-type 'character :initial-contents '(#\H #\e #\l #\l #\o)))
(print (aref string1 1)) ; -> #\e

专用浮点向量对于科学和数值计算至关重要。

;; 创建一个单精度浮点数向量。
(let ((float-vec (make-array 3 :element-type 'single-float)))
(setf (aref float-vec 0) 3.14f0)
(setf (aref float-vec 1) 1.618f0)
(print float-vec))
;; -> #(3.14 1.618 0.0)

在以下情况时选择专用向量:

  • 处理大量同构的原始数据(例如,数字、字符)。
  • 编写对内存带宽和缓存性能要求较高的性能关键代码(例如,游戏开发、数据分析、图像处理)。
  • 处理二进制数据,例如从文件或网络套接字读取。
  • 与 C 或其他系统级库进行接口。

对于灵活性比原始性能更重要的通用编程,或者当您需要存储不同对象类型的混合时,标准向量通常是更方便的选择。