Комментарии 28
Не только вам. У меня не получается придумать кейс, где надо было бы использовать именно SIMD в JS, не отдавая предпочтение каким-то другим технологиям.
Если копнуть чуть глубже — эту технологию активно проталкивают intel. Не знаю, они ли начали или просто подхватили, но в таком разрезе понятно, почему именно SSE-совместимый набор функций SIMD.
У меня есть пример с оператором Собеля. Я в нем сделал два допущения: сначала перегнал в float32array, затем хоть каждый раз и пересчитывал 4-ю компоненту, при записи проставлял 255.
В действительности же, в таких алгоритмах перегоняют данные в другой формат, выстраивая последовательно не как rgbargbargba, а rrrgggbbbaaa, что упрощает задачу и делает доступным более быстрые алгоритмы. Однако перегон в этот и обратно форматы сам по себе может оказаться слишком медленным и съесть весь прирост скорости.
альфа-канал обрабатывается иначе, чем rgb
Часто точно так же, если перейти в формат premultiplied alpha или доподленно известно, что альфаканал всегда имеет одно значение. При преобразовании RGBA → RGBa и обратно тоже можно использовать SIMD.
По поводу альфа-канала:
если задавать цвета следующим образом
ctx.fillStyle = "rgba(255,255,255,0.5)";
тогда да, он тут float, в отличии от остальных трёх.
Но! Если-таки использовать "модель" вот так:
var imgData = ctx.getImageData(0, 0, width, height);
var colorArray = new Uint32Array(imgData.data.buffer); // endian-dependent
То получим супер быстрый контейнер, в каждой ячейке которого храниться цвет (32-bit rgba число).
2. А что даст такой контейнер? Он же слепит все 4 канала в одно 32 битное число. И что с ним дальше делать? Оно как бы заманчиво обрабатывать аж 4 пикселя за операцию, но… Каналы нужно выдирать битовыми масками. Точность 8-битных каналов невелика. Если только обработка какая-то очень простая — тогда да.
В своих проектах для ряда задач я использую немного другое представление изображений: бью изображение на 4 подизображения и записываю с интерливингом. Таким образом, в каждом регистре у меня оказывается только одна компонента — не тратятся ресурсы на операции с альфой. Плюс полностью исчезает невыровненный доступ.
Сверткой в математике называют преобразование из одного набора данных в другой.
Эмммм… наверное, всё же «преобразование двух в третий»… или хотя бы «одного в другой посредством третьего», не?
В математике именно так задано, покуда сверткой там является g(f(x)), т.е. преобразованием g над функцией f. Так повелось, что в математике свертки практически все интегральные функции, типичными примерами являются преобразования Фурье и Лапласа.
В программировании же свертка — это преобразование всегда над одним набором данных (остальные массивы в этом случае будут выступать как параметры). Однако вот этих самых параметров может быть сколько угодно. В целом — вопрос больше именования, главное — это то, что над каждым элементом массива (либо другой структуры) будет произведено одно и то же преобразование независимо от порядка обхода и прочего.
Вот что-то на дарте: vector math dart
А вот форк pixijs использующий SIMD: gameofbombs-pixi
Весьма странно видеть MPI и OpenMP как замену SIMD. Они не занимаются векторизацией.
OpenMP — это распараллеливание программы между потоками, и оно помогает задействовать совершенно другие возможности процессоров (многоядерность), чем SIMD (широкие АЛУ).
MPI (если речь идёт о Message Passing Interface) — это вообще про обмен сообщениями между процессами в кластере. Я бы не хотел видеть такое в стандарте языка общего назначения.
К слову, OpenMP-подход (т.е. расширение компилятора для поддержки потоков) сейчас слабо распространён в разных языках, хотя удобен для пользователя. Возможно, не очень хорошо/не очень удобно модифицировать компилятор для поддержки многопоточности, а написать библиотеку намного проще.
Чаще предпочтение отдаётся подходу с параллельным коллекциями. Но, как правило, это тоже не векторизация.
MPI — мой косяк, почему-то зафиксировался в голове как не открытый аналог OpenMP.
Что касается OpenMP — во-первых, это даже лучше, если браузер решит, что что-то он может посчитать на той же видеокарте, если ему это подсказать, либо многопоточно. Во-вторых, я предлагал взять идею, однако это не означает, что нужно тянуть сам OpenMP, в том плане, что мы можем намекнуть браузеру на то, что ожидаем, что этот цикл будет обсчитать векторами, но размерность их не знаем. Это не всегда просто, но я и не ставил пока-что задачу перед собой разработку стандарта. Тем не менее, описание этого через директивы или более абстрактными методами позволит отойти от привязки к архитектуре.
Почему нужно использовать директивы, а не просто надеяться на самостоятельность браузера? Да потому что он может оптимизирует. А может и нет и никак об этом не скажет. А если мы явно на это укажем, то он выдаст ошибку по какой причине у него это не получилось, что позволяет гораздо точнее контроллировать процесс. Вдобавок, это сразу же заработает и на старых проектах (впрочем, оверхед на них, скорее всего, перетрет все преимущество).
Для меня очень странно, что не операций распаковки и запаковки данных. Это имеено то, что позволяет работать с пикселями изображения, потому что производить любые операции в 8 битах точности явно не вариант. Может быть я просто не нашел или они как-то неявно используются при каких-то операциях?
Нет, если в float32x4 загрузить Int8Array (и обратно), получится чушь, ожидаемая от загрузки прямым образом в C. Ну т.е. он подряд 4 инта загрузит в один float, что попортит данные. Пока писал операнд Собеля, наткнулся на это. Возможно, это неявно используется при перегоне из одного типизированного массива в другой (Float32Array.from(someInt8Array)), однако в самом SIMD такого нет. Причем, либо пока, либо вообще нет контроля за типами данных и неявно они не приводятся, что крайне странно видеть в js, когда в 99.99% случаев ты даже не думаешь о том, что там внутри — float, double или int.
однако в самом SIMD такого нет
pmovzxbd + cvtdq2ps для беззнаковых байтов и pmovsxbd + cvtdq2ps для знаковых
Нет, если в float32x4 загрузить Int8Array (и обратно), получится чушь
Я про флоаты ничего не говорил. Команды нужны для распаковки и запаковки целых, из 8-байтных в 16, из 16 в 32 и т.д. И если распаковке есть адекватна замена в виде перемешивания, то для запаковки с сатурацией нет.
В принципе, то же самое можно сделать последовательностью SIMD.Uint16x8.min()
и SIMD.Uint16x8.max()
, а потом еще раз перемешать. Умный компилятор мог бы распознать такую последовательность и заменить одной инструкцией. Но вот беда, min
и max
для целых чисел нет, только для чисел с плавающей точкой. Возможно это связано с тем, что не все есть в NEON.
Да, все, понял. Мой косяк. Более того, в JS в SIMD есть возможность перегнать из uint8x16 в float32x4 (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SIMD/fromInt8x16Bits), так что мой косяк вдвойне. Однако она все равно работает словно чтение из памяти, т.е. [0, 0, -128, 63, ....] станет [1.0, ...], а не [0.0, 0.0, -128.0, 63.0].
Векторные вычисления в JS, есть ли смысл, когда и как можно использовать SIMD в браузере