Об Intel ISPC

Интел выкатил в опенсорс такую вот игрушку: Intel SPMD Program Compiler.

Это очередная попытка придумать параллельный язык C: пишете некую функцию, к примеру, обрабатывающую элемент данных, варез сам строит SIMD-представление (SSE, AVX), которое обрабатывает 4(8) элементов за раз. Ну как-то так:

void vector_sum(uniform float in[], uniform int count, reference uniform float out[]) {
    float sum = 0.0;
    for (uniform int i = 0; i < count; i += programCount) {
        int index = i + programIndex;
        sum+= in[index];
    }
    out[programIndex] = sum;
}
И оно у вас пойдет фигачить шириной programCount, ну там дальше надо будет элементы out просуммировать уже снаружи.

Примеры (из поставки), надо сказать, впечатляют, там действительно генерируется код, который быстрее компиляторного (из того же C++ кода) в разы. Ну и собственноручно сделанные простые примеры тоже вполне внушают оптимизм, сгенерированный код всяко не хуже ручного SSE (там, где работает логика параллельного исполнения нескольких копий функции; скалярное произведение до соответствующей встроенной команды оно оптимизировать не смогло).

Но возникает сразу много мелких но, которые сильно уменьшают полезность:

  • Авторам тулзы пришлось делать спецификаторы "постоянства" переменной между разных инстансов SIMD-программы. Да, все понятно, без этого при параллельном исполнении никак не выйдет, только эта фишка тут же бъет по пальцам:
  • Встроенные типы - только 32- и 64-битные int и float. Работа с 16-битными целыми данными (а у меня картинки, да) превращается в ужоснах: вроде есть встроенные функции загрузки 8- и 16-битных данных, но выясняется что дать им varying-адрес (т.е. разный в каждом SIMD-потоке) не получится т.е. загрузить свое значение в каждом потоке просто нельзя. Что-то по константному адресу прочитать - это пожалуйста.
  • Вся конструкция очень хрупкая, пока я преодолевал (безуспешно) предыдущее ограничение - у меня компилятор чаще падал, чем работал. Ну, пофиксят со временем.
  • Ну и все это - только на одном ядре. Для распараллеливания по ядрам нужен какой-то внешний планировщик (в примерах - есть такой пример). Делать это через OpenMP я не пробовал, попробую.
Вместе с тем, результат на родных примерах отличный: в один поток получается в 3-4 раза быстрее, чем то что интеловский компилятор генерирует из примерно того же кода. На всех ядрах Mandelbrot в 13 раз быстрее, чем однопоточный код из компилятора.

Но, как я уже писал, местоположение этой штуки в экосистеме мне не очень понятно. Готовый код туда не пойдет, надо доводить напильником. Но если его довести напильником до состояния OpenCL (а это непринципиально сложнее, ну разве что пересылка данных туда-сюда добавится), то вроде как сплошной выигрыш получается:

  • На видеокарте пойдет (с какой скоростью - вопрос, но пойдет).
  • На многих ядрах - тоже пойдет.
Получается, место ISPC - точечные оптимизации в достаточно тривиальных местах (где, к примеру, не нужно скалярного произведения или транспонирования матрицы 4x4 сделанной на 4-х XMM-регистрах), там где я до этого использовал OpenMP + ручной SSE-код. Возможно, писать на этом псевдо-C будет быстрее. Особенно в случаях, когда есть ветвления, выписывать их вручную всегда мучительно, а тут оно как-то само. А вот там, где объем работы большой и может окупить пересылку на GPU и обратно - там лучше, наверное, OpenCL.

P.S. AVX-кодогенератора пока нет, обещают.

Comments

Сходил по ссылке:
- C-like language
- LLVM Compiler
- Попробую догадаться... использует TBB?

OpenCL, вид слева.

Ну следов TBB я не заметил, оно же линкуется как библиотека, ничего такого нет.

Просто порождает объектник или LLVM-код (бинарный) или ассемблер. Вот что умеет использовать (но я не пробовал) - это intel small vector math library или как ее там.

А ассемблер - обычный такой, SSE4.2 (ну я такой просил). И объектник - обычный, линкуешься с ним и все.

Т.е. если рассматривать код в примере выше, то в первом приближении он развернется в сложение 4-х элементов за раз внутри цикла путем
movups xmm1,[откуда]
addps xmm0,xmm1 # xmm0 - это sum
(а если на самом деле, то за один цикл фигачится 8 элементов:
movups xmm2,[data+i]
addps xmm0,xmm2
movups xmm1,[data+i+4]
addps xmm0, xmm1
# Всякие штуки с обработкой длины массива некратной 8 я опускаю, а в них весь кайф этой тулзы, выписывать их вручную всегда очень лениво.

И это не совсем OpenCL
- оффлайн-компиляция
- нет многопоточности

Да, понял. Разница существенна, согласен.

На третий день зоркий глаз заметил....
что я пускал компилятор с --targer=sse4x2 что, собственно, и порождает код "шириной" 8.

Кстати, чем он опять же тоже похож на бету OpenCL - это неиспользование AVX.

OpenCL хотя-бы VEX-prefix команды генерирует, а эта - нет.

Да, я, кстати, обратил внимание. А есть от этого практическая польза прям сейчас? Или это лишь дает нам знать, что внедрение AVX инструкций в Intel OpenCL потребует не так много ресурсов?

Про практическую пользу не знаю.
Но старые команды я легко глазом читаю, а эти - пока еще нет.

AVX "скоро скоро" обещают и там и там.

наконец-то лёд тронулся

у Интел есть намного более гибкий и удобный ArBB, для выч задач самое то.

Насколько я понимаю, ArBB - это map-reduce над векторами, ну в первом приближении.
Это тоже ценная штука и многие вещи закроет (и как только будет не в бете и на маке - я туда посмотрю).

А ispc - это совсем другое. Оно не над векторами, оно над скалярами, просто берет этих скаляров по 4(8) в ряд.
Принципиально важно то, что оно условные операторы вполне разумно обрабатывает (естественно, разваливая вычисления на два блока, а потом собирая маской). Не бог весть как трудно вручную, но очень уж занудно.
Работал бы с 16-бит int - цены бы не было машинке.

ArBB тоже работает над скалярами. смысл в том, чтобы писать код в "stream" терминах, явно указывать какие элементы зависимы, а какие нет. если элементы не зависимы, то делается AVX + openmp. язык примерно как у CUDA.