Представьте себе будущее, когда тяжелые математические пакеты будут написаны на js, при этом не будут уступать по производительности нативным. Красивые динамичные игры прямо в браузере, при этом держат стабильные 60 fps, сложная арифметика, сайты на реакте, в конце концов, перестанут тормозить. Чтобы это стало возможным, языку приходится динамично развиваться и включать в себя достаточно неожиданные вещи, как недавно нашумевший web-assembly, asm.js, typed arrays, так и одна технология, о которой пойдет речь в этой статье.
ES2017 обещает много интересного, но большинство из этого имеют пометку draft, каждый день придумывают что-то новое и отказываются от чего-то старого. Однако, похоже, одна экспериментальная спецификация все таки дорастет до стандарта и позволит делать быстрые математические расчеты на js. Встречайте — SIMD — single instruction multi data. Кому интересно что это такое, как оно себя ведет сейчас и что это технология обещает — добро пожаловать под кат!
Теория
Допустим, вы пишете программу на C, и у вас в коде есть что-то типа:
int32 a = 5;
int32 b = 6;
int32 c = a + b;
Если вы запустите этот код на 32-битной архитектуре — все хорошо, целочисленная арифметика, совпадающая по разрядности с разрядностью системы. Однако если запустить этот же код на 64-разрядной архитектуре, 32 старших бита будут забиты нулями и гоняться впустую, когда можно было бы уместить туда еще одно 32-битное число и сложить две пары чисел за раз. Примерно так подумали много лет назад (в 70-х годах) где-то в недрах Texas Instruments и CDC, сделав первые векторные супер-компьютеры. Однако до них некий Майкл Флинн предложил свою таксономию (классификацию) компьютеров, одной из которых были SIMD. К сожалению, на этом нить истории теряется, но мы тут не для этого собрались.
Таким образом, в 70-х годах прошлого столетия появились первые процессоры, позволявшие за раз считать несколько чисел меньшей разрядности, чем та, которой оперирует машина. Позже это перетянули к себе практически все в виде расширенного набора инструкций.
Графически классическая архитектура выглядит следующим образом:
Нам нужно сложить 4 пары чисел, поэтому мы вынуждены 4 раза вызвать инструкцию add в процессоре.
Векторная операция же выглядит следующим образом:
Когда нам нужно сложить 4 пары чисел, мы просто вызываем одну инструкцию, которая складывает их за один такт.
Небольшая ремарка по поводу "векторная операция" и SIMD-операция. Дело в том, что SIMD — более общее понятие, подразумевающее под собой выполнение в один и тот же момент времени одной или нескольких одинаковых операций над разными данными. В CUDA в каждый момент времени нити выполняют одну и ту же операцию над разными данными, но этих операций выполняется столько, сколько доступно потоков в видеокарте. Векторная арифметика подразумевает то, что выполняется именно одна операция, причем, фактически, она выполняется просто над двумя расширенными данными, составляющими из себя упорядоченно лежащие несколько чисел в одной ячейке. Таким образом, векторные операции входят как подмножество в SIMD-операции, однако в ES2017 говорится именно о векторной арифметике, не знаю, почему они решили так обобщить, далее мы будем считать эти два понятия одним и тем же в рамках этой статьи.
Так что, получается, мы можем увеличить производительность своих js приложений в 4 раза? (Протяженно)Нууу не совсем. Во-первых, большинство программ не просто перемалывают огромные объемы данных, а так же, помимо арифметики, имеют ветвления, ожидание ввода-вывода, какие-либо еще инструкции, не заключающиеся в сложении или перемножении чисел. Во-вторых, данные нужно сначала откуда-то загрузись, как правило, из оперативной памяти, что занимает гораздо больше времени, чем сделать эти самые 4 операции. Тут спасает кеш, конвейер и многоканальная память, но прирост все равно нелинейный и не такой впечатляющий, если только данная система специально не разработана под векторную обработку данных. В-третьих, покуда мы пишем не на ассемблере и даже не на достаточно низкоуровневом C++, на инициализацию самих этих чисел так же тратится некоторое время. Собственно, в этой статье я и покажу какой прирост по производительности это дает.
Практика
Итак, векторные операции скоро появятся в js. Насколько скоро? В данный момент их поддерживает firefox nightly, edge с флагом "экспериментальные возможности" и chrome с флагом при запуске --js-flags="--harmony-simd"
, т.е. хоть в каком-то виде, но все браузеры. Помимо этого есть полифилл, так что можно использовать уже прямо сейчас.
Небольшой пример как использовать SIMD в js-коде:
const someValue = SIMD.Float32x4(1,0,0,0);
const otherValue = SIMD.Float32x4(0,1,0,0);
const summ = SIMD.Float32x4.add(someValue, otherValue);
Полный список доступных функций смотрите на MDN. Хочу обратить внимание, что SIMD.Float32x4 не является конструктором и запись new SIMD.Float32x4(0, 0, 0, 0);
не является валидной.
Не буду расписывать все возможности по использованию, их не очень много, в целом — арифметика да загрузка с выгрузкой данных, еще немного примеров все на том же MDN, сразу перейду к тестам.
Будущее здесь? Производительность
Не буду тратить ваше время в пустую, сразу перейду к самому интересному: производительности и выводам. Небольшой jsfiddle, в котором я накидал несколько сравнительных тестов. Открываем консоль и смотрим числа, чем меньше, тем луче. В результате у меня получился такой вот график (время бралось среднее по 5-и запускам)
- Под "чистое" подразумевается, что поверх SIMD (или массива) нет никакой обертки, код написан напрямую. В реальности такой код крайне неудобно поддерживать, но какие-то высоконагруженные места могут быть переписаны на подобный.
Первое, что бросается в глаза — время каждый раз не стабильно. То SIMD чуть быстрее, то нет, то классы быстрее, то прототипы быстрее. Каждый раз разный результат. Однако некоторые закономерности улавливаются.
В хроме обычные массивы практически всегда выигрывают у SIMD в производительности. Такое ощущение, что в google chrome внутри вообще стоит полифил, никакой нативной поддержки нет.
Edge сегодня не в духе, тотальный провал по показателям, не знаю, что с ним случилось. Однако если ближе к теме статьи: в Edge чистое использование (без классов, прототипов, смешиваний с обычной арифметикой и массивами) дает ощутимое преимущество. Как только появляется задача периодически получать или записывать единичные значения в SIMD-структуры — идет огромный провал по скорости.
В Firefox SIMD показали себя неплохо, даже когда приходится считывать какие-то отдельные значения из его структур, однако обертка его в класс и скрытие механизмов работы с ним сыграло злую шутку.
- ООП через прототипы в js работает гораздо быстрее, чем через конструкцию class, что может быть критичным в различных задачах, связанных с графикой или математическими расчетами. Вообще, в графиках это не отображено, но причиной такого падения производительности классов является полифил babel, если же этот код вставить в консоль в таком виде, в каком он есть — классы даже обгоняют прототипы, но готовы ли вы пожертвовать пользователями, чей браузер не поддерживает классы, либо писать в прототипном стиле — решать вам.
4. Почти везде SIMD показал худший результат, как следствие — я определенно не умею его грамотно использовать.
Выводы
Главный вывод: технология SIMD в рамках языка javascript еще очень и очень сырая. Преимущества её использования совсем не очевидны, а графики пусть и на достаточно глупых тестах показывают что в большинстве случаев вы только потеряете время и производительность системы в целом. На самом деле не очень понятно зачем вообще в этот язык тащить такие низкоуровневые вещи, поскольку все преимущества, которые оно дает, теряются на стыке технологий при пересылке данных из одного формата в другой. В браузерах, где все таки поддерживается эта технология более-менее штатно, есть смысл использовать только в чистом виде, не смешивая с другими типами данных. Это может быть полезно в криптографии либо узкоспециализированных местах, однако дает смехотворный прирост в случае моделирования, покуда в задачах мат. моделирования определение модуля вектора и подобных его характеристик — задача не такая уж редкая, а она на корню портит всю идею. К сожалению, API для работы с SIMD не сильно удачное, а как только появляются обертки, сразу же наблюдается огромный провал в скорости работы, причем, во всех браузерах на данный момент.
Очень хотелось бы увидеть пример, когда данная технология в применении к js действительно даст большой прирост, однако, на мой взгляд, продолжаем ждать web-assembly, SIMD же не произведет революцию в мире js. В примерах на MDN приводится пакетная обработка данных, однако в данный момент на синтетических тестах никакого заметного прироста производительности не наблюдается, зато код становится грязнее и менее читаемым.
P.S. Если у вас есть edge, firefox nightly или не лень прописать флаг в ярлыке google chrome — поделитесь своими результатами тестов.
P.P.S. На сколько мне известно, саму технологию активно продвигают Intel под свои же цели, полагаю, под Intel XDK в первую очередь. Там, безусловно, это может принести какую-то пользу, однако широкого применения за пределами их инструментов вряд ли сыщет.
Комментарии (38)
TargetSan
22.08.2016 10:27+3Представьте себе будущее, когда тяжелые математические пакеты будут написаны на js
Избави Боже.
Alexey2005
22.08.2016 11:31-7Это всё продолжение нового тренда, согласно которому удобство разработчика важнее удобства пользователя. Плевать, что три с половиной миллиарда пользователей вынуждены покупать более мощные машины и всё равно мириться с подтормаживанием, зато пара миллионов разработчиков может не тратить время на изучение C++, а вместо этого каждые полгода изучать по новому фреймворку.
TargetSan
22.08.2016 12:20+3А С++ и JS во многом "братья по разуму". И там, и там — язык имеет целую подборку местами совершенно жутких, но бережно сохраняемых легаси косяков в дизайне. Только С++ успел сильно больше раздуться и по-прежнему сидит без модулей :)
yogurt1
22.08.2016 10:34-6Ответьте на вопрос, люди, начерта в JS математика? Если нужна в Node.js математика, то всегда есть
node.h
и nodejs/nan
Классы, Obersvable, теперь это. Может хватить? Такими темпами в ES2018 уберут асинхронность и получим очередной Python/Ruby/PHP/e.t.cOhar
22.08.2016 11:32+2Писать моноязычные приложения, которые работают везде, где есть браузер, очевидно же.
TargetSan
22.08.2016 12:22-5Так может всё же спроектировать для этого вменяемую платформу, а не прикручивать фотонный привод проволокой к яхте?
Ohar
22.08.2016 12:50+3Спроектировать? Зачем? Все инструмениы есть. Они работают. Берите и пользуйтесь.
Или что вы понимаете под платформой?TargetSan
22.08.2016 13:06-4Я к тому, что JS как-то не очень подходит на роль языка разработки именно приложений, а не веб-страничек. Да ещё и кроссплатформенных. Даже Python на его месте смотрелся бы сильно лучше. Я здесь имею ввиду не какие-то частности вроде "скобки против отступов", а общую проработанность.
Ohar
22.08.2016 13:56-1Да, для приложений — вполне возможно.
А вот для кроссплатформенности — нет.
Приложение на пайтоне не кроссплатформенно, а на JS — очень даже. Потому что питон стоит не везде, а браузер — везде (в силу объективных причин).Crandel
22.08.2016 17:01-5Покажи мне, где на моем сервере браузер стоит?)
Ohar
22.08.2016 17:15-1Если мы говорим про приложения, то говорим про пользовательские системы. Назовите современную пользовательскую систему, в которой нет браузера.
А на сервере вообще может ничего не стоять.
Да и очень интересно, что у вас за ОС такая, где нет браузера из коробки.
kahi4
22.08.2016 17:27Уточните про проработанность, что вы под этим подразумеваете? Язык как язык. Инструменты есть, библиотеки на любой вкус и цвет есть, даже строгую типизацию в typescript завезли. Он, как и любой другой язык, не для всего подходит, но в 2016 году нет ни одной причины утверждать, что он не подходит для разработки приложений. Вон, atom, VS Code и кучу другого на нем как-то работают и ничего. Тормозят нещадно, но я начинаю по-немногу привыкать, что у меня даже IRC-клиент (слэк) отжирает полтора гигабайта оперативной памяти, такое уж сейчас время.
К слову, о той же производительности — если на том же C++ налепить столько же возможностей, анимаций, удобностей и прочих вкусностей, как в slack — будет, конечно, гораздо лучше и я бы этого очень хотел, но только стартап разволился бы в миг, покуда соотношение цены/скорости разработки и качества совершенно иные.ValdikSS
23.08.2016 08:50Когда текстовый редактор не может открывать файлы больше нескольких мегабайт — это НЕ нормально.
Когда простой IM весит 50 мегабайт и несет с собой Chromium.exe и ffmpeg.dll — это НЕ нормально.
Когда нажимаешь правой кнопкой на окне любого, как подразумевается, десктопного приложения на Node.js, и видишь Back и Forward — это НЕ нормально.
Akon32
22.08.2016 12:23+6SIMD-инструкции? Зачем? JS как бы считается языком высокого уровня, независимым от железа. А тут прямо в исходном тексте программы предполагается, что именно 4-компонентные вектора есть в инструкциях процессора. А если только 3, или 5, или 8 компонентов? Неизвестно, на чём код будет запускаться.
Имхо, более дальновидными (но не простыми) решениями выглядят автоматическая векторизация в JIT (или прямо в суперскалярном процессоре), или OpenCL/CUDA-подобное API.Large
22.08.2016 20:24Не все считается на видеокарте, для игр чаще всего нужны как раз 4-векторы. Куды не будет, будут computed shaders в webgl 2.1
homm
Бенчмарки никуда не годятся:
Входные данные одни и те же, результат никуда не попадает не только за пределами цикла, но и внутри цикла даже не используется. К тому же единица оптимизации — функция, которая вызывается определенное количество раз. А у вас все внутри тега script вычисляется.
homm
Очень хотел бы порекомендовать всем писателям бенчмарков посмотреть доклад Вячеслава Егорова «Производительность JavaScript через подзорную трубу», который был на Питерском HolyJS. Но к сожалению в публичном доступе видео будет только осенью. А пока можно помотреть хотя бы это интервью.
kahi4
И да, и нет. https://jsfiddle.net/5uLsovur/7/ добавил использование значения после цикла (в всего двух случаях, покуда в остальных оно и так есть, пусть и вывод в консоль). Результаты не поменялись совершенно.
```
console.time('add pure (simd)');
for (let i = 0; i < IRT; i++) {
SIMD.Float32x4.add(fooPure, barPure);
}
console.timeEnd('add pure (simd)');
```
Судя по всему, хром не знает, чистая ли это функция, либо имеет побочные эффекты, поэтому старается перемалывать все внутри честно. Есть подозрение, он конструкцию
```
[a[0] + b[0],
a[1] + b[1],
a[2] + b[2],
a[3] + b[3]]
```
Сам внутри компилирует в SIMD, хоть это и не прописано явно, хотя к тому, что там лишь полифилл — я склоняюсь больше.
Опять же, последние тесты представляют из себя интегратор, прогоняющий пусть и абстрактную, но все же математическую модель, пишущий в консоль свой вывод. Да, прогрев кеша, возможности браузера каким-то образом отложенно оптимизировать код и прочие хитрости не учитываются. Однако если бы эта операция действительно давала прирост — вот вам реальный математический пример и в нем она только отстает. Писать бенчмарк, который покажет, что она действительно на 30% быстрее работает, но при этом вне этого бенчмарка, в реальном мире, где код перемешан черт пойми как, она не дает преимуществ — нет особого смысла.
homm
С трудом себе прдставляю этот реальный мир, где код валяется внутри script, а не в функции.
Посмотрел последний тест:
Это мягко говоря не SIMD. Если вы используете SIMD-типы для того, чтобы выполнить одну-две инструкции, а остальное у вас на обычных операциях, это даст только проигрыш на манипуляциях с данными.
Еще у вас в SIMD-версии используется тип
Float32x4
, а данные для тестов целочисленные, поэтому не-SIMD версия может работать быстрее и сравнение некорректное. А еще время работы очень маленькое — когда получаешь 10 и 30 ms, непонятно то ли правда быстрее, то ли сборка мусора в это время случилась.В общем, тесты на три с минусом и судить по ним хоть о чем-то я бы не стал. Но даже в этих тестах в ночном Фаерфоксе у меня SIMD-версия почти всегда быстрее. Иногда на 30%, иногда в 3 раза. Вот вариант более-менее избавленный от перечисленных недостатчков, правда меня хватило только на тесты сложения: https://jsfiddle.net/5uLsovur/17/
kahi4
Я писал об том. Я понимаю, когда нужно сделать пакетную обработку данных, например,… например что? Даже контраст пододвинуть или перевести из RGB в CMYK — все равно есть необходимость оперировать единичными значениями. Есть задачи, например, посчитать хеш — там SIMD, скорее всего, даст большой прирост. Возможно, сильно значимый. В маметматических задачах оперирование с конкретными данными из набора так или иначе необходимы. Если некоторые алгоритмы могут быть адаптированы под SIMD, то в большинстве случаев точки соприкосновения будут неизбежно. Проблема в том, что в том же C, на сколько мне известно, оперирование с SIMD-структурами практически не накладно — упаковали 4 числа в структуру с правильным padding, далее доступ к ним в худшем случае будет в виде косвенной адресации (не знаю точно как это реализовано, https://gcc.gnu.org/onlinedocs/gcc/Vector-Extensions.html).
В JS же точка встречи двух миров (векторного и скалярного) обходится слишком дорого, поэтому SIMD получились очень очень узкоспециализированными.
Сегодня вечером (по МСК) попробую расширить статью, дописав тесты более адаптированных под SIMD операций, например, поиск суммы массива, наложение какой-то свертки на данные, БПФ, матричные операции, обновлю статью.
Признаю, ваша критика полностью справедлива и заслужена, мой недосмотр по поводу тестов, покуда я в них ориентировался на возникшую задачу, примеры, соответственно, такие же, в них тенденция мне показалась достаточной, чтобы делать какие-то, может преждевременные, выводы.
homm
Не вижу сильных отличий от js: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SIMD/load
И вообще, по хорошему, в не-SIMD версии тестов тоже стоило бы использовать типизированные массивы.
Large
например физический движок, возможно скомпилированный из плюсов. вся эта технология задумывалась исключительно ради игр.
homm
Вот как раз доклады подоспели :)
https://habrahabr.ru/company/jugru/blog/308410/
kahi4
Да, вижу. Я тут, как и обещал, решил переделать тесты, заодно и всю статью. Готовлю к публикации, потому что качеством получившейся не доволен. И результаты там достаточно интересные! (спойлер: я очень очень поспешил с выводами, спешу исправляться).