Представьте себе будущее, когда тяжелые математические пакеты будут написаны на 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-х годах прошлого столетия появились первые процессоры, позволявшие за раз считать несколько чисел меньшей разрядности, чем та, которой оперирует машина. Позже это перетянули к себе практически все в виде расширенного набора инструкций.


Графически классическая архитектура выглядит следующим образом:


image

Нам нужно сложить 4 пары чисел, поэтому мы вынуждены 4 раза вызвать инструкцию add в процессоре.


Векторная операция же выглядит следующим образом:


image

Когда нам нужно сложить 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-и запускам)


image

  • Под "чистое" подразумевается, что поверх SIMD (или массива) нет никакой обертки, код написан напрямую. В реальности такой код крайне неудобно поддерживать, но какие-то высоконагруженные места могут быть переписаны на подобный.

Первое, что бросается в глаза — время каждый раз не стабильно. То SIMD чуть быстрее, то нет, то классы быстрее, то прототипы быстрее. Каждый раз разный результат. Однако некоторые закономерности улавливаются.


  1. В хроме обычные массивы практически всегда выигрывают у SIMD в производительности. Такое ощущение, что в google chrome внутри вообще стоит полифил, никакой нативной поддержки нет.


  2. Edge сегодня не в духе, тотальный провал по показателям, не знаю, что с ним случилось. Однако если ближе к теме статьи: в Edge чистое использование (без классов, прототипов, смешиваний с обычной арифметикой и массивами) дает ощутимое преимущество. Как только появляется задача периодически получать или записывать единичные значения в SIMD-структуры — идет огромный провал по скорости.


  3. В Firefox SIMD показали себя неплохо, даже когда приходится считывать какие-то отдельные значения из его структур, однако обертка его в класс и скрытие механизмов работы с ним сыграло злую шутку.


  4. ООП через прототипы в 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)


  1. homm
    22.08.2016 02:32
    +7

    Бенчмарки никуда не годятся:


    console.time('add pure (simd)');
    for (let i = 0; i < IRT; i++) {
       SIMD.Float32x4.add(fooPure, barPure);
    }
    console.timeEnd('add pure (simd)');

    Входные данные одни и те же, результат никуда не попадает не только за пределами цикла, но и внутри цикла даже не используется. К тому же единица оптимизации — функция, которая вызывается определенное количество раз. А у вас все внутри тега script вычисляется.


    1. homm
      22.08.2016 02:40
      +5

      Очень хотел бы порекомендовать всем писателям бенчмарков посмотреть доклад Вячеслава Егорова «Производительность JavaScript через подзорную трубу», который был на Питерском HolyJS. Но к сожалению в публичном доступе видео будет только осенью. А пока можно помотреть хотя бы это интервью.


      1. kahi4
        22.08.2016 10:52
        +1

        И да, и нет. 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% быстрее работает, но при этом вне этого бенчмарка, в реальном мире, где код перемешан черт пойми как, она не дает преимуществ — нет особого смысла.


        1. homm
          22.08.2016 13:58
          +2

          в реальном мире, где код перемешан черт пойми как

          С трудом себе прдставляю этот реальный мир, где код валяется внутри script, а не в функции.


          Опять же, последние тесты представляют из себя интегратор

          Посмотрел последний тест:


          t = 0;
          console.time('integrator (pure SIMD)');
          while (t < T) {
            const temp = SIMD.Float32x4.mul(Xpure, Xpure);
            const a0 = SIMD.Float32x4.extractLane(Xpure, 0);
            const a1 = SIMD.Float32x4.extractLane(Xpure, 1);
            const a2 = SIMD.Float32x4.extractLane(Xpure, 2);
            const a3 = SIMD.Float32x4.extractLane(Xpure, 3);
            const r = Math.sqrt(
              SIMD.Float32x4.extractLane(temp, 0) +
              SIMD.Float32x4.extractLane(temp, 1) +
              SIMD.Float32x4.extractLane(temp, 2) +
              SIMD.Float32x4.extractLane(temp, 3)
            );
          
            const c = G / (r * r * r);
          
            Xpure = SIMD.Float32x4.add(
              Xpure,
              SIMD.Float32x4.mul(
                 SIMD.Float32x4(
                   - a0 * c,
                   - a1 * c,
                   - a2 * c,
                   0
                 ),
                 SIMD.Float32x4(dt, dt, dt, dt)
              )
            );
            t += dt;
          }
          console.timeEnd('integrator (pure SIMD)');

          Это мягко говоря не SIMD. Если вы используете SIMD-типы для того, чтобы выполнить одну-две инструкции, а остальное у вас на обычных операциях, это даст только проигрыш на манипуляциях с данными.


          Еще у вас в SIMD-версии используется тип Float32x4, а данные для тестов целочисленные, поэтому не-SIMD версия может работать быстрее и сравнение некорректное. А еще время работы очень маленькое — когда получаешь 10 и 30 ms, непонятно то ли правда быстрее, то ли сборка мусора в это время случилась.


          В общем, тесты на три с минусом и судить по ним хоть о чем-то я бы не стал. Но даже в этих тестах в ночном Фаерфоксе у меня SIMD-версия почти всегда быстрее. Иногда на 30%, иногда в 3 раза. Вот вариант более-менее избавленный от перечисленных недостатчков, правда меня хватило только на тесты сложения: https://jsfiddle.net/5uLsovur/17/


          1. kahi4
            22.08.2016 14:21

            Это мягко говоря не SIMD. Если вы используете SIMD-типы для того, чтобы выполнить одну-две инструкции, а остальное у вас на обычных операциях, это даст только проигрыш на манипуляциях с данными.


            Я писал об том. Я понимаю, когда нужно сделать пакетную обработку данных, например,… например что? Даже контраст пододвинуть или перевести из RGB в CMYK — все равно есть необходимость оперировать единичными значениями. Есть задачи, например, посчитать хеш — там SIMD, скорее всего, даст большой прирост. Возможно, сильно значимый. В маметматических задачах оперирование с конкретными данными из набора так или иначе необходимы. Если некоторые алгоритмы могут быть адаптированы под SIMD, то в большинстве случаев точки соприкосновения будут неизбежно. Проблема в том, что в том же C, на сколько мне известно, оперирование с SIMD-структурами практически не накладно — упаковали 4 числа в структуру с правильным padding, далее доступ к ним в худшем случае будет в виде косвенной адресации (не знаю точно как это реализовано, https://gcc.gnu.org/onlinedocs/gcc/Vector-Extensions.html).

            В JS же точка встречи двух миров (векторного и скалярного) обходится слишком дорого, поэтому SIMD получились очень очень узкоспециализированными.

            Сегодня вечером (по МСК) попробую расширить статью, дописав тесты более адаптированных под SIMD операций, например, поиск суммы массива, наложение какой-то свертки на данные, БПФ, матричные операции, обновлю статью.

            Признаю, ваша критика полностью справедлива и заслужена, мой недосмотр по поводу тестов, покуда я в них ориентировался на возникшую задачу, примеры, соответственно, такие же, в них тенденция мне показалась достаточной, чтобы делать какие-то, может преждевременные, выводы.


            1. homm
              22.08.2016 14:38

              Проблема в том, что в том же C, на сколько мне известно, оперирование с SIMD-структурами практически не накладно

              Не вижу сильных отличий от js: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SIMD/load


              И вообще, по хорошему, в не-SIMD версии тестов тоже стоило бы использовать типизированные массивы.


            1. Large
              22.08.2016 20:17

              например физический движок, возможно скомпилированный из плюсов. вся эта технология задумывалась исключительно ради игр.


      1. homm
        24.08.2016 17:41

        Вот как раз доклады подоспели :)
        https://habrahabr.ru/company/jugru/blog/308410/


        1. kahi4
          24.08.2016 17:44
          +1

          Да, вижу. Я тут, как и обещал, решил переделать тесты, заодно и всю статью. Готовлю к публикации, потому что качеством получившейся не доволен. И результаты там достаточно интересные! (спойлер: я очень очень поспешил с выводами, спешу исправляться).


  1. TargetSan
    22.08.2016 10:27
    +3

    Представьте себе будущее, когда тяжелые математические пакеты будут написаны на js

    Избави Боже.


    1. Alexey2005
      22.08.2016 11:31
      -7

      Это всё продолжение нового тренда, согласно которому удобство разработчика важнее удобства пользователя. Плевать, что три с половиной миллиарда пользователей вынуждены покупать более мощные машины и всё равно мириться с подтормаживанием, зато пара миллионов разработчиков может не тратить время на изучение C++, а вместо этого каждые полгода изучать по новому фреймворку.


      1. TargetSan
        22.08.2016 12:20
        +3

        А С++ и JS во многом "братья по разуму". И там, и там — язык имеет целую подборку местами совершенно жутких, но бережно сохраняемых легаси косяков в дизайне. Только С++ успел сильно больше раздуться и по-прежнему сидит без модулей :)


  1. yogurt1
    22.08.2016 10:34
    -6

    Ответьте на вопрос, люди, начерта в JS математика? Если нужна в Node.js математика, то всегда есть node.h и nodejs/nan
    Классы, Obersvable, теперь это. Может хватить? Такими темпами в ES2018 уберут асинхронность и получим очередной Python/Ruby/PHP/e.t.c


    1. Antelle
      22.08.2016 10:44
      +1

      Игрушки в браузере, не?


    1. Ohar
      22.08.2016 11:32
      +2

      Писать моноязычные приложения, которые работают везде, где есть браузер, очевидно же.


      1. TargetSan
        22.08.2016 12:22
        -5

        Так может всё же спроектировать для этого вменяемую платформу, а не прикручивать фотонный привод проволокой к яхте?


        1. Ohar
          22.08.2016 12:50
          +3

          Спроектировать? Зачем? Все инструмениы есть. Они работают. Берите и пользуйтесь.
          Или что вы понимаете под платформой?


          1. TargetSan
            22.08.2016 13:06
            -4

            Я к тому, что JS как-то не очень подходит на роль языка разработки именно приложений, а не веб-страничек. Да ещё и кроссплатформенных. Даже Python на его месте смотрелся бы сильно лучше. Я здесь имею ввиду не какие-то частности вроде "скобки против отступов", а общую проработанность.


            1. Ohar
              22.08.2016 13:56
              -1

              Да, для приложений — вполне возможно.
              А вот для кроссплатформенности — нет.
              Приложение на пайтоне не кроссплатформенно, а на JS — очень даже. Потому что питон стоит не везде, а браузер — везде (в силу объективных причин).


              1. Crandel
                22.08.2016 17:01
                -5

                Покажи мне, где на моем сервере браузер стоит?)


                1. Ohar
                  22.08.2016 17:15
                  -1

                  Если мы говорим про приложения, то говорим про пользовательские системы. Назовите современную пользовательскую систему, в которой нет браузера.
                  А на сервере вообще может ничего не стоять.
                  Да и очень интересно, что у вас за ОС такая, где нет браузера из коробки.


                  1. Crandel
                    22.08.2016 17:19
                    -4

                    Представь себе, мои python приложения работают на моем сервере, где даже иксов нету, если тебе это о чем-то говорит, не говоря уже про браузеры)


                    1. kahi4
                      22.08.2016 17:23
                      +1

                      Ага, а еще там работает node.js. Может не совсем из коробки, но в питоне тоже иногда нужно доставлять зависимости. Зато запустите приложение на питоне на iOS, например.


                      1. Crandel
                        22.08.2016 17:29
                        -4

                        Куча коллег на маках постоянно запускают код на питоне и не жалуются.


                    1. Ohar
                      22.08.2016 17:34
                      +1

                      1) Мы не переходили на «ты».
                      2) Назовите-таки вашу ОС.


                      1. Crandel
                        22.08.2016 17:37

                        debian jessie


                        1. Ohar
                          22.08.2016 19:26

                          Ок, в ней дефолтно нет браузера.
                          Но это и не пользовательская система, а серверная — в ней даже нет иксов.
                          В том же KDE вы уже получаете браузер в стандартной поставке.


                          1. Crandel
                            22.08.2016 19:39
                            -1

                            Давайте вы просто просмотрите эту ссылку Python и эту Node.js, а потом будете утверждать


                            Приложение на пайтоне не кроссплатформенно, а на JS — очень даже.


                            1. Ohar
                              22.08.2016 19:42

                              И что именно я должен там увидеть?
                              Что пайтон нужно устанавливать, а NodeJS работает в браузере, который есть везде?
                              С чем именно вы спорите?


                              1. homm
                                22.08.2016 20:29
                                -1

                                а NodeJS работает в браузере


            1. Zenitchik
              22.08.2016 16:44
              +1

              И как же это мы пишем приложение на JS? Прямо удивительно!


              1. TargetSan
                22.08.2016 17:02
                -2

                На нём можно писать приложения. Но никто не говорил, что это удобно делать.


                1. Zenitchik
                  22.08.2016 17:06
                  +2

                  Удобство — понятие относительное. На почти на 100% зависит от привычки.


            1. kahi4
              22.08.2016 17:27

              Уточните про проработанность, что вы под этим подразумеваете? Язык как язык. Инструменты есть, библиотеки на любой вкус и цвет есть, даже строгую типизацию в typescript завезли. Он, как и любой другой язык, не для всего подходит, но в 2016 году нет ни одной причины утверждать, что он не подходит для разработки приложений. Вон, atom, VS Code и кучу другого на нем как-то работают и ничего. Тормозят нещадно, но я начинаю по-немногу привыкать, что у меня даже IRC-клиент (слэк) отжирает полтора гигабайта оперативной памяти, такое уж сейчас время.

              К слову, о той же производительности — если на том же C++ налепить столько же возможностей, анимаций, удобностей и прочих вкусностей, как в slack — будет, конечно, гораздо лучше и я бы этого очень хотел, но только стартап разволился бы в миг, покуда соотношение цены/скорости разработки и качества совершенно иные.


              1. ValdikSS
                23.08.2016 08:50

                Когда текстовый редактор не может открывать файлы больше нескольких мегабайт — это НЕ нормально.
                Когда простой IM весит 50 мегабайт и несет с собой Chromium.exe и ffmpeg.dll — это НЕ нормально.
                Когда нажимаешь правой кнопкой на окне любого, как подразумевается, десктопного приложения на Node.js, и видишь Back и Forward — это НЕ нормально.


        1. dimykus
          22.08.2016 17:20
          +1

          Так вроде же WebAssembly пилят для этого


  1. Akon32
    22.08.2016 12:23
    +6

    SIMD-инструкции? Зачем? JS как бы считается языком высокого уровня, независимым от железа. А тут прямо в исходном тексте программы предполагается, что именно 4-компонентные вектора есть в инструкциях процессора. А если только 3, или 5, или 8 компонентов? Неизвестно, на чём код будет запускаться.
    Имхо, более дальновидными (но не простыми) решениями выглядят автоматическая векторизация в JIT (или прямо в суперскалярном процессоре), или OpenCL/CUDA-подобное API.


    1. Large
      22.08.2016 20:24

      Не все считается на видеокарте, для игр чаще всего нужны как раз 4-векторы. Куды не будет, будут computed shaders в webgl 2.1