Добрый вечер, Хабр. Немного отвлекусь от расчетов больших и страшных девайсов для выхода за пределы гравиколодца. Есть идея запустить небольшой скрипт, рисующий красивые визуалы (которые потом можно пустить или на пиксел-арт, или на текстуры к чему-нибудь хайтековому).
Сами клеточные автоматы - огромное поле, которое можно рассматривать с самых разных аспектов.
Можно варьировать правила, можно пытаться уйти от брутфорсного перебора вариантов к более изящному запоминанию типичных сценариев и подбором возможных вариантов развития по хэшу клеточного рисунка (Golly, например). Но моя цель - достаточно редкие правила как MirekGro и Bombers, и желания/сил возиться с адаптацией существующих hashlife-алгоритмов к этим двум своеобразным правилам нет.
А еще хочется испытать одномерные "хаотические" правила вроде Rule30 для целей, с визуалами пока вовсе не связанными. Так что пусть будет bruteforce-моделирование с пересчетом сумм вокруг каждой клеточки - просто, быстро в написании и достаточно ресурсоемко.
Как хранить состояние клетки. POJO vs bitwise record
Cостояний у КА может быть больше, чем только "жив/мертв". В MirekGro клетка может пребывать в 4-х состояниях, в Bombers - в 25-и состояниях. Для отслеживания перехода "сейчас/потом" можно или хранением состояние как plain old javascript object вида { now, next },
или же в виде бинарной записи на unsigned Int16, старший байт которого - это состояние "потом", а младший - "сейчас". Посмотрим, какой вариант лучше в производительности.
Модельная ситуация - перевести состояние "потом" в состояние "сейчас", а затем назначить новое состояние "потом". Для большей надежности повторим этот процесс несколько раз. Тестовый вариант - массив из 100000 элементов, который мы будем тасовать 500 раз.
(Не)научные эксперименты проводим в node 12.18.4 на камне i5-8600KF, которому дано 32Гб RAM.
Тест с бинарной записью прошел в среднем за 25,092мс. Тест с объектом - за 43,574мс. Бинарная запись обеспечивал выигрыш в ~1,74 раз. Рост не на порядок, но и почти двукратное ускорение вычисление - это приятно. Так что модель КА будет работать на двухбайтовом числе.
Расплетение массива в вектор vs квадратной матрицы
Еще один момент - как лучше хранить модельные данные, стоит ли держать их в матрице или лучше расплести матрицу в один (но очень длинный вектор)? А заодно - как различается производительности двухмерных массивов в зависимости от того, обычные ли это массивы JS или же новомодные (или уже нет) TypedArrays?
Пройдемся по каждому элементу массива и раз за разом присвоим ему несколько значений. Первый эксперимент - сравнение прогонов по матрице 1000 x 1000, "расплетенной" в вектор из 1000000 элементов. Для обычных Array пять прогонов подряд (миллион элементов, 100 перетасовок) показывают, что одномерная развертка оказалась быстрее в ~ 3,75 раз (245,8мс против 922,8мс)
Теперь запустим аналогичный эксперимент с Uint8Array. Тот же миллион элементов, те же 100 тасовок. Пять прогонов показали, что для Uint8Array разность в производительности между матрицей и вектором - в пределах погрешности измерения performance.now()
.
Можно предположить, что такая разница (точнее - ее полное отсутствие) связано с тем, что для обычного Array постоянно происходит перерасчет границ массива (т.к. каждый его элемент может иметь произвольный размер), что приводит к просадке производительности при работе с многомерным массивом. Для TypedArray такой проблемы быть не должно из-за фиксированного размера каждого элемента. Кстати, Uint8Array также оказался немного быстрее (где-то на 10%), чем обычный Array.
Выводы
Даже в JS можно спуститься на уровень работы с отдельными байтами и получит от этого ощутимый прирост производительности. Ну или багов. И удовольствия
Двухмерные массивы на обычном Array - это весьма прожорливые конструкции (а ведь я даже не пытался экспериментировать с разреженными массивами или диагональными матрицами, где ситуация может стать совсем забавной)
TypedArrays обладают преимуществом в производительности, и с ними нет необходимости "расплетать" 2d-матрицу в вектор
Голоса в голове просят повторить эксперимент уже не для IntArray, а для Float. Потому что это уже может пригодиться в Rocket Science. Для той же баллистики, например
Комментарии (5)
Saiv46
06.12.2021 08:19Для большей производительности придётся использовать WebAssembly, либо шейдеры WebGL
the_stucky Автор
06.12.2021 20:25Если визуализация, то да. Если незатейливый number crunching - то, скорее openCL + wasm
nin-jin
Есть ещё вариант со множествами из координат (по множеству на каждый цвет), это позволяет работать с квазибесконечными полями:
the_stucky Автор
Спасибо, как понимаю, вся магия происходит через обсчет состояния отдельной клетки
for( let y = -1 ; y <= 1 ; ++y ) for( let x = -1 ; x <= 1 ; ++x ) { if( !x && !y ) continue
И функцию key(x, y), возвращающую состояние ячейки с произвольными координатамиif( prev.has( key( nx + x , ny + y ) ) ) ++sum
}
if( sum != 3 && ( !prev.has( nkey ) || sum !== 2 ) ) continue state.add( nkey )
nin-jin
Пересчёт происходит лишь вокруг живых клеток. А координаты упаковываются в int32, что позволяет использовать это число в качестве ключа.