Я не могу воспроизвести ваши наблюдения. Здесь ниже то, что я измеряю, наряду с двумя другими тестами с различными типами массивов. Прежде всего обратите внимание, что мой установочный скрипт выполняет следующие действия:
(sb-ext:restrict-compiler-policy 'debug 3)
(sb-ext:restrict-compiler-policy 'safety 3)
Это может повлиять на компиляцию кода.
Ваши тесты
(print :list)
(let ((x (list 0 1 2 3 4 5 6 7 8 9)))
(time (dotimes (i 1000000000) (setf (nth 9 x) 0))))
(print :simple-vector)
(let ((x (vector 0 1 2 3 4 5 6 7 8 9)))
(time (dotimes (i 1000000000) (setf (aref x 9) 0))))
Трассировка выглядит следующим образом :
:LIST
Evaluation took:
6.649 seconds of real time
6.649545 seconds of total run time (6.649545 user, 0.000000 system)
100.02% CPU
21,224,869,441 processor cycles
0 bytes consed
:SIMPLE-VECTOR
Evaluation took:
0.717 seconds of real time
0.716708 seconds of total run time (0.716708 user, 0.000000 system)
100.00% CPU
2,287,130,610 processor cycles
0 bytes consed
Что касается вашего исходного вопроса, разница между использованием списков и векторов почти в 10 раз выше (9,27). Я не знаю, почему ваши тесты показывают по-другому (правка: так, очевидно, это было связано с неопределенным поведением, я видел, как вы мутировали постоянные данные и исправляли их в моем коде, не думая, что они могут быть связаны: /)
Специализированный массивы
Я также попытался описать массив как можно точнее, объявив в его типе тип элементов, которые он содержит, (mod 10)
и размеры массива (10)
. Фактический тип элемента массива в SBCL - (UNSIGNED-BYTE 4)
, что означает, что массив может быть упакован: несколько элементов массива могут быть сохранены в машинном слове (32 или 64 бита). Упаковка хороша для места, но для хранения / доступа к элементам требуются этапы кодирования / декодирования.
(print :packed-array)
(let ((x (make-array 10
:element-type '(mod 10)
:initial-contents '(0 1 2 3 4 5 6 7 8 9))))
(declare (type (array (mod 10) (10)) x)
(optimize (speed 3)))
(time (dotimes (i 1000000000)
(setf (aref x 9) (the (mod 10) 0)))))
У меня также есть версия, где фактический тип элемента массива (unsigned-byte 32)
, который должен избегать выполнения преобразований:
(print :unpacked-array)
(let ((x (make-array 10
:element-type '(unsigned-byte 32)
:initial-contents '(0 1 2 3 4 5 6 7 8 9))))
(declare (type (array (unsigned-byte 32) (10)) x)
(optimize (speed 3)))
(time (dotimes (i 1000000000)
(setf (aref x 9) (the (mod 10) 0)))))
Дополнительные тесты:
:PACKED-ARRAY
Evaluation took:
1.168 seconds of real time
1.167929 seconds of total run time (1.167929 user, 0.000000 system)
100.00% CPU
3,727,528,968 processor cycles
0 bytes consed
Здесь вы можете видеть, что использование наиболее точного типа на самом деле замедляет тесты в данном конкретном случае. Это, вероятно, может быть объяснено накладными расходами операций декодирования / кодирования (?)
:UNPACKED-ARRAY
Evaluation took:
0.231 seconds of real time
0.231094 seconds of total run time (0.231094 user, 0.000000 system)
100.00% CPU
737,633,062 processor cycles
0 bytes consed
С массивами 32-битных значений время выполнения меньше.
Если вы вызываете disassemble
для тесты для обоих типов векторов (просто уберите вызов time
, который добавляет много шума и, возможно, понизит уровни отладки), в итоге разница сводится к тому, какой тип регистра используется в l oop:
Простой вектор использует 64 бита (RCX), поскольку вектор должен иметь возможность хранить объекты типа T:
; C0: L2: B900000000 MOV ECX, 0
; C5: 48894A49 MOV [RDX+73], RCX
; C9: 4883C002 ADD RAX, 2
; CD: L3: 483D00943577 CMP RAX, 2000000000
; D3: 7CEB JL L2
В тесте с 32-битным массивом используется регистр ECX (32 бита) .
; 960: L2: 31C9 XOR ECX, ECX
; 962: 894A25 MOV [RDX+37], ECX
; 965: 4883C002 ADD RAX, 2
; 969: L3: 483D00943577 CMP RAX, 2000000000
; 96F: 7CEF JL L2
Также смещения отличаются, но соответствуют типам:
- 37 = 4 (bytes) * 9 (index) + 1 (storage for array size?)
- 73 = 8 (bytes) * 9 (index) + 1 (storage for array size?)