Pull to refresh

Comments 28

> однако мне не очень понятен выбор технологии и реализация стандарта
Не только вам. У меня не получается придумать кейс, где надо было бы использовать именно SIMD в JS, не отдавая предпочтение каким-то другим технологиям.

Если копнуть чуть глубже — эту технологию активно проталкивают intel. Не знаю, они ли начали или просто подхватили, но в таком разрезе понятно, почему именно SSE-совместимый набор функций SIMD.

Хотел было сказать «обработка изображений на canvas» (ведь rgba как раз 4-компонентный вектор), но потом подумал — стоп, но ведь там альфа-канал обрабатывается иначе, чем rgb. Выходит, что нужно обрабатывать отдельно цветовые каналы, забив «лишний» 4-ый пустышкой, а потом альфу (если нужно) отдельно ручками?

У меня есть пример с оператором Собеля. Я в нем сделал два допущения: сначала перегнал в float32array, затем хоть каждый раз и пересчитывал 4-ю компоненту, при записи проставлял 255.


В действительности же, в таких алгоритмах перегоняют данные в другой формат, выстраивая последовательно не как rgbargbargba, а rrrgggbbbaaa, что упрощает задачу и делает доступным более быстрые алгоритмы. Однако перегон в этот и обратно форматы сам по себе может оказаться слишком медленным и съесть весь прирост скорости.

Самая долгая операция в Собеле — это чтение/запись памяти, а не вычисления.
альфа-канал обрабатывается иначе, чем rgb

Часто точно так же, если перейти в формат premultiplied alpha или доподленно известно, что альфаканал всегда имеет одно значение. При преобразовании RGBA → RGBa и обратно тоже можно использовать SIMD.

Вот это, кстати, интересно. Про premultiplied alpha читал довольно давно, но как-то не очень понимал, зачем оно может быть нужно на практике (потому что пытался примерить это дело на обычные 8-битные каналы, а не 32-битный флоат).

По поводу альфа-канала:
если задавать цвета следующим образом


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 число).

1. Я имел в виду не тип числа, а правила обработки. В альфа-блендинге цветовые координаты и прозрачность обрабатываются по разхным формулам (если не использовать премультиплед альфу, как упомянуто выше).
2. А что даст такой контейнер? Он же слепит все 4 канала в одно 32 битное число. И что с ним дальше делать? Оно как бы заманчиво обрабатывать аж 4 пикселя за операцию, но… Каналы нужно выдирать битовыми масками. Точность 8-битных каналов невелика. Если только обработка какая-то очень простая — тогда да.
Во многих задачах вообще нет никакой необходимости в обработке альфы. Просто альфа игнорируется. Неэффективность (по назначению используется 75% ресурсов) компенсируется просто возможностью использования векторных операций.

В своих проектах для ряда задач я использую немного другое представление изображений: бью изображение на 4 подизображения и записываю с интерливингом. Таким образом, в каждом регистре у меня оказывается только одна компонента — не тратятся ресурсы на операции с альфой. Плюс полностью исчезает невыровненный доступ.
UFO just landed and posted this here
UFO just landed and posted this here

Да, конечно. Спасибо за замечание, в nodejs включается флагом запуска node --harmony-simd index.js.

Сверткой в математике называют преобразование из одного набора данных в другой.

Эмммм… наверное, всё же «преобразование двух в третий»… или хотя бы «одного в другой посредством третьего», не?
UFO just landed and posted this here

В математике именно так задано, покуда сверткой там является g(f(x)), т.е. преобразованием g над функцией f. Так повелось, что в математике свертки практически все интегральные функции, типичными примерами являются преобразования Фурье и Лапласа.


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

Весьма странно видеть MPI и OpenMP как замену SIMD. Они не занимаются векторизацией.


OpenMP — это распараллеливание программы между потоками, и оно помогает задействовать совершенно другие возможности процессоров (многоядерность), чем SIMD (широкие АЛУ).
MPI (если речь идёт о Message Passing Interface) — это вообще про обмен сообщениями между процессами в кластере. Я бы не хотел видеть такое в стандарте языка общего назначения.


К слову, OpenMP-подход (т.е. расширение компилятора для поддержки потоков) сейчас слабо распространён в разных языках, хотя удобен для пользователя. Возможно, не очень хорошо/не очень удобно модифицировать компилятор для поддержки многопоточности, а написать библиотеку намного проще.
Чаще предпочтение отдаётся подходу с параллельным коллекциями. Но, как правило, это тоже не векторизация.

Вы удивитесь, во что правратился стандарт OpenMP к версии 4.0 — там и SIMD, и перепоручение(offload) задач ускорителям…

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 для знаковых
Извиняюсь, кажется я не переключил контекст после написания asm-кода и интерпретировал слово SIMD в его в общем смысле.
Нет, если в 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].

Sign up to leave a comment.

Articles