Могут ли массивы 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))
В таком случае, почему мы получаем T
как тип элемента из (array-element-type image-array)
?
2 ответа
Немного предыстории того, что на самом деле означает ТИП ЭЛЕМЕНТА
Если вы даете тип элемента 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 имеет максимум 8 измерений на моем ARM, некоторые другие реализации поддерживают больше измерений). Эти многомерные массивы также могут иметь специализированные типы элементов.
Похоже на проблему XY: вам лучше использовать многомерный массив с плавающей точкой:
(make-array (list width height) ...)
... тогда вы делаете (aref matrix row column)
и вам не нужно вычислять индексы. Когда вы храните массив внутри массива, вам все равно нужно сохранять метаданные, связанные с каждым массивом, например, тип его элементов, потому что вы можете ссылаться на каждый массив из другого места. Поэтому в основном массиве хранятся только ссылки, а не необработанные числа с плавающей точкой.
Также обратите внимание, что тип, который может храниться в массиве, может быть супертипом объявленного типа из-за обновления массива: System Class ARRAY.