Skip to content

LISP - Specialized Vectors

A standard Lisp vector is a general-purpose container that can hold any type of Lisp object. This flexibility comes at a cost: each element requires storage for a type tag and a pointer, a concept known as boxing. A specialized vector, in contrast, is optimized to hold elements of only one specific, primitive type. This offers significant advantages in performance and memory usage.

  • Memory Efficiency: By storing raw data values directly without boxing (e.g., storing a raw 8-bit integer instead of a pointer to an integer object), specialized vectors use significantly less memory.
  • Increased Performance: Accessing elements is much faster. The CPU can work with data directly, improving cache locality and enabling powerful SIMD (Single Instruction, Multiple Data) optimizations by the compiler.
  • Type Safety: The Lisp system enforces the type restriction. Attempting to store an element of the wrong type will signal an error, catching bugs early.
  • Foreign Function Interface (FFI): They are essential for interfacing with libraries written in other languages like C, as they map directly to C-style arrays in memory.

You create a specialized vector by providing the :element-type argument to make-array. The system will automatically choose the most compact representation possible.

You can create vectors for various integer sizes. (unsigned-byte 8) is perfect for holding bytes, for instance.

;; Create a vector to hold 5 integers, each an unsigned byte (0-255).
;; It is initialized with zeros.
(let ((byte-vector (make-array 5 :element-type '(unsigned-byte 8))))
(print byte-vector)
;; -> #(0 0 0 0 0)
;; Assign some values
(setf (aref byte-vector 0) 10)
(setf (aref byte-vector 1) 255)
(print byte-vector)
;; -> #(10 255 0 0 0)
;; This would signal a type error, as 300 is out of range for an unsigned-byte 8.
;; (setf (aref byte-vector 2) 300)
)

A bit-vector is an extremely compact way to store a sequence of boolean (0 or 1) values. Lisp provides a convenient reader macro #* for creating them literally.

;; Create a bit-vector using the reader macro
(let ((bit-v #*10110))
(print bit-v)
;; -> #*10110
;; Access an element
(print (aref bit-v 2))
;; -> 1
;; Modify an element
(setf (aref bit-v 2) 0)
(print bit-v))
;; -> #*10010

A Lisp string is, by definition, a specialized one-dimensional array whose elements are of type character.

;; These are effectively equivalent
(defvar string1 "Hello")
(defvar string2 (make-array 5 :element-type 'character :initial-contents '(#\H #\e #\l #\l #\o)))
(print (aref string1 1)) ; -> #\e

Specialized float vectors are critical for scientific and numerical computing.

;; Create a vector of single-precision floating-point numbers.
(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)

Choose specialized vectors when you are:

  • Dealing with large amounts of homogeneous, primitive data (e.g., numbers, characters).
  • Writing performance-critical code where memory bandwidth and cache performance are important (e.g., game development, data analysis, image processing).
  • Handling binary data, such as reading from files or network sockets.
  • Interfacing with C or other systems-level libraries.

For general-purpose programming where flexibility is more important than raw performance, or when you need to store a mix of different object types, a standard vector is often the more convenient choice.