Обновить
8
0

Пользователь

Отправить сообщение

Фрагмент с пещерами напомнил это:
https://gamedev.ru/projects/forum/?id=258554
Там качество где-то даже и получше, нет квадратов при сильном приближении. Освещение более продвинутое.

Квадраты, понятно, зависят от метода рендера. Если это рейкастинг, то есть для каждого пикселя ищем пересечение луча из камеры с воксельным миром, то сгладить в теории не проблема... той же линейной интерполяцией, если попали лучом между вокселями, считаем среднее по 8 соседним. По сути это выборка из 3D-текстуры с фильтрацией.
Но возможно в движках со сложными структурами данных типа SVO считается, что брать 8 соседних вокселей слишком дорого, они могут быть в разных ветках дерева и т.п.

Подозреваю, что подобных трюков много в статьях по старым псевдо-3D играм, наверняка в Doom 1,2 именно так углы и хранились. И таблицы для тригонометрии, разумеется.

С тестом:
https://gcc.godbolt.org/z/9n53e9588
Квадрат 2*2 оказался медленнее, видимо непоследовательная запись в память всё портила, поэтому переделал на 2 соседних пикселя по горизонтали, теперь в 2 раза быстрее. А простое дописывание vectorize(assume_safety) к циклу почти ничего не даёт (от 0 до 15%, но чаще 0).
Также в варианте с 2*2 я ошибся и не использовал y_pixel_stride, uv_row_stride, uv_pixel_stride - из-за этого и пропали vpinsrb. Все эти параметры дают гибкость, но с ними приходится брать по байтику, компилятор не может быть уверен, что данные лежат последовательно. Впрочем, по факту простыня vpinsrb не делает хуже, ускоряют именно 2 пикселя за итерацию.
Хотя 2 раза - тоже далеко не предел мечтаний, но больше не хочется тратить на это времени.

Действительно как-то очень простенько.
Фактически всё ограничивается знакомством с некими магическими заклинаниями для ускорения кода, "но всемогущий маг лишь на бумаге я" - 18% это очень мало для успешной векторизации.
Я попытался восстановить полный код примера, правда на x86, c ARM/Neon я не знаком:
https://gcc.godbolt.org/z/99Yfd7Y9E
Видно, что векторизует, но масса вот этих vpinsrb, выдёргиваний по 1 байтику - это явно неэффективно.
Буферы U,V имеют в 2 раза меньшее разрешение - логично считать за одну итерацию квадрат 2*2 пикселя, чтобы не читать одни те же значения U,V по 4 раза:
https://gcc.godbolt.org/z/T68hdvTcs
Вроде бы стало лучше, во всяком случае махинаций с байтиками сходу не вижу. Но надо тестировать.
Также я заменил прагму assume_safety на модификатор __restrict для параметров, действует аналогично, но совместимо с gcc.

Ещё следует подумать, можно ли избавиться от плавающей точки с сохранением приемлемой точности, это уберёт лишние команды конверсии и может улучшить векторизацию (если обойтись int16, то их больше влезет в вектор, чем float32).

В общем, надо дописывать свою статью про высокоуровневую векторизацию (у меня на другом примере, но не суть).

По поводу обработки границ у Гаусса, отмечу, что в OpenCL есть images, они же текстуры из 3D API. При выборке из текстуры края обрезаются автоматически, также есть трюки, позволяющие сократить число выборок за счёт бесплатной билинейной интерполяции.
Конечно, images не будут ничего ускорять, если запускать кернел на CPU - там будет работать софтверная имитация.

Ну не скажу, что хорошо организован, автоматической сборки нет. И не такие большие объёмы кода переписаны на Си.

Для прототипирования очень удобен онлайн-компилятор https://gcc.godbolt.org/
Тем более, что я не люблю интринсики и упираю на (полу)автоматическую векторизацию - там сразу видно качество этой векторизации.
Компиляторы - gcc или Clang, Clang обычно предпочтительнее, т.к. лучше поддерживает векторные расширения. Но иногда gcc выдаёт лучший результат.

А в оффлайне делается dll-ка с помощью IDE CodeBlocks + опять же gcc или Clang. Последние версии компиляторов ставятся на Винду через пакетный менеджер msys2. gcc можно также скачать из проекта mingw-w64, но не последней версии.

Как вариант, можно линковать obj, причём в 64 битах вроде бы линкуется вообще безо всяких конверсий. Но давно не использовал этот метод, отладка совсем никакая (dll можно отлаживать из CodeBlocks с дельфийским host application).

Почему не MSVC? Ну, gcc/Clang мне кажутся позадорнее в плане оптимизации (но возможно, я плохо знаю опции MSVC), и в MSVC нет векторных расширений.
Есть возможность поставить в IDE MSVC компилятор Clang, но я пока не пробовал и не знаю, насколько этот Clang полноценный, поддерживает ли расширения.

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

Ну и выше я много писал о том, что давно забросил использование SIMD через дельфийский ассемблер (кроме совсем мелких функций), использую для этого компиляторы Си. Там если что-то передаётся в функцию, то как правило крупными кусками - массивы, структуры. Их можно либо выровнять вручную, либо заставить сишный компилятор использовать movups.

Кстати, можно и в чисто дельфийском коде этот метод применить - собираем все глобальные переменные в структуру (запись) и вручную её выравниваем. Точнее, используем указатель на запись, который выравнен в блоке памяти [размер+16].

А арифметика с операндом в памяти вот прямо заметно лучше, чем movups в регистр и та же операция с регистром?
Я не проверял, но мне кажется, что разница будет пара процентов. Выборка из памяти никуда не денется в любом случае. И это имеет большее значение, чем +1 инструкция.
И кстати, как в 64-битном режиме - тоже ничего не выравнивает? Могу ошибаться, но там вроде есть требование от ОС по выравниванию стека.

Хм, правда болезненная?
По-моему на современных процессорах выравнивание уже не особо влияет. Я получал разницу порядка 10% (для небольшого промежуточного буфера). Если уж хочется выжать эти 10%, то в Delphi FastMM умеет выравнивать на 16 со специальной опцией, при желании можно сделать выравнивание вручную.
При этом в коде можно всегда использовать команды без выравнивания, начиная с первых Core i на реально выровненных данных у movups такая же скорость, как у movaps.

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

Я собирался использовать не классический компилятор Билдера, конечно, а как раз Clang-based. Который по идее должен уметь векторизацию, развёртку циклов, эффективный инлайн - во всяком случае оригинальный Clang умеет.
И тормозит он не (не только) потому, что у разработчиков руки кривые, а потому что там гораздо более сложная оптимизация.

А есть ли хоть какие-то преимущества у C++Builder перед Delphi для Windows-разработки?

Я думал об использовании C-Builder в дополнение к Дельфи для оптимизации узких мест. То есть основная программа на Дельфи (более простой и приятный язык), а то, что раньше делалось ассемблерными вставками, делается функциями на Си (который всё-таки более читабельный по сравнению с ассемблером). Си как замена ассемблеру - назад в 1970-е :)
Но это имеет смысл только если вам нужна вот такая суровая оптимизация, с ассемблерными или сишными вставками. Для большинства Дельфи-проектов (разного рода морд к БД) - очевидно, не нужна.
И C-Builder в конечном итоге оказался для этого неудобен, использую сишный код через dll, собранную CodeBlocks и Clang/gcc.

Быстрое сжатие картинок без потерь - отлично, давно такое ищу.

Хотя надо отметить, что обогнать сжатие в png - невелика заслуга, там оно медленное by design, т.к. "фильтр" для строки (преобразование, улучшающее сжимаемость) выбирается методом перебора. В зависимости от степени сжатия может перебираться больше или меньше разных фильтров, поэтому на макс. сжатии png очень медленный, на минимальном, без фильтров, это фактически тот же zip.

Интересно было бы сравнить Quite OK Image с ImageZero - тоже никому не известный алгоритм. В статье на Хабре заявлено аж 35-кратное превосходство в скорости над png (хм, может быть, автор QOI переизобрёл ImageZero заново?). Когда я сравнивал ImageZero с png, ускорение получалось меньше, в 2-4 раза, но зато некоторые картинки сжимались в 1.5 раза лучше. Возможно, png тестировался не на максимальном сжатии, точно не помню.

В целом ImageZero явно лучше png (точнее, алгоритмов сжатия png), но спустя 9 лет по-прежнему никому не известен...

а именно про интринсики и нет ничего. Только вот это:

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

Менее известны векторные расширения (gcc, Clang, также ICC поддерживает gcc-нотацию). Здесь я выкладывал пример, полная версия.

Вот ещё пример успешной автовекторизации с незаурядной перетасовкой данных в исполнении gcc.

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

А не пробовали положить все данные в один буфер? Вот как здесь 2-й и 3-й варианты:
www.khronos.org/opengl/wiki/Vertex_Specification_Best_Practices#Formatting_VBO_Data
Мне 3-й метод кажется более предпочтительным, т.к. он в целом более популярен (используется в DirectX), и видеокарты должны быть лучше заточены под такое расположение данных. Хотя это только предположение, не проверял.

Также отмечу, что если изначально у вас объёмные данные, которые можно представить 3D-массивом, то их можно визуализировать вообще без использования вершин и полигонов, через volume raycasting.
habr.com/ru/post/123632
Опасается, что области памяти, с которыми работаем, могут пересекаться и векторизовать нельзя. Проанализировать код и понять, что нет — пока видимо ума не хватает.
__restrict как раз и говорит ему, что не пересекаются.
Статичные массивы вроде float arr[1024*1024*64] векторизует и так.
gcc.godbolt.org/z/5hP9vYanG
Сравнил в том виде, в каком выкладывал в godbolt (Clang / gcc / интринсики, мс).
i3-3250 (IvyBridge), AVX: 69 / 45 / 44
i9-9900K (CoffeeLake), AVX: 52 / 38 / 40
i9-9900K (CoffeeLake), FMA: 44 / 38 / 40
Clang отстал на 10-60%, а gcc даже обогнал интринсики. В горизонтальные сложения он тоже не умеет, но зато сообразил перетасовать данные (матрицу 8*8?) ДО цикла.
gcc.godbolt.org/z/PWjar9qar
Что логично, я тоже об этом думал, глядя на попытки Clang-а, и в статьях по умножению матриц на Хабре что-то такое было. Похоже, gcc уже настолько крут, что читает Хабр :)
Если серьёзно, есть с gcc одна странность — если вызывать функцию idctAV в вашем примере 1 раз, то работает быстро, а если несколько раз в цикле для большей стабильности результата, то скорость сильно проседает (только с gcc, с clang такого нет).
Код
    const int cycles = 10;
    auto start = high_resolution_clock::now();

    for (size_t c = 0; c < cycles; ++c) 
      idctAV((float*)&arr[0], (float*)&output[0], (float*)&idctMat[0]);
	 
    auto end = high_resolution_clock::now();
    const auto result = std::accumulate(output.begin(), output.end(), 0.0);
    std::cout << result << std::endl;
    std::cout << duration_cast<milliseconds>((end - start) / cycles).count() << " ms" << std::endl;
Может быть я что-то напортачил с тайпкастами, я плохо знаю эти ваши плюсы. В моём примере проседания по скорости нет, там idctAV вызывается несколько раз, но как функция в DLL.
А ваш изначальный результат — это, как и следовало ожидать, невекторизованный код (простыня из vmovss/vmulss/vaddss).
gcc.godbolt.org/z/4jffcb89n
Я попытался ради интереса повторить ваш тест, но не уверен, что правильно оформил интринсиковый вариант:
gcc.godbolt.org/z/cbqGhYcas
так правильно или нет? В частности, тип idctRows.
Обычная версия кстати вполне успешно векторизуется:
gcc.godbolt.org/z/e4K3Ps5z3
только компилятор не умеет использовать горизонтальное сложение, и из-за этого долго тасует данные в регистрах, чтобы затем выдать красивую «простыню» из mulps/addps. Но сомневаюсь, что это даёт замедление аж в 5-7 раз, скорее вы сравнивали с невекторизованной версией.
У вас для Гаусса 4x4 нет готового кода на Дельфи, который можно взять за образец, либо ассемблер, либо словесное описание.
Здесь я взял за основу 3x3, может сами допишете?
gcc.godbolt.org/z/1KKG9a
до общения с Вами пребывал в уверенности, что лучшая оптимизация- в ICC- как никак, компилятор от производителя процессора!
ICC тоже хорош, но у него своя система параметров, и не все из них работают через godbolt. Попробуйте -fast, и будет таки fast, но принудительно включится FMA/AVX2. В общем, я толком не знаю, как с ним обращаться.
когда можно ради упрощения жизни компилеру переломать базовые структуры данных
Да всё уже, отбой, не надо ломать. На практике раздельные массивы «не взлетели». Наверное, виноват не последовательный доступ к памяти, лезем в память по 9-и разным указателям вместо 1-го. Может быть, если бы хранить элементы мини-массивами по 4 штуки, было бы лучше, но это совсем уже неудобно в работе.
От новых инструкций (AVX, FMA) тоже толку немного.
Но в результате я всё-таки обогнал ваш ассемблер примерно на 30%, а для компактной матрицы сильно, раза в 3.
Аккаунта на Гитхабе нет, поэтому архивом, там же и результаты.
забить всю ПСП и пережевывать данные быстрее, чем они через нее пролазят
Не уверен, что если вы забиваете ПСП, то это повод для гордости. Может слишком мало арифметики на чтение/запись? Но да-а, теперь уже не хочется ничего менять, когда всё захардкожено ассемблером.
Лично для меня одной из причин залезть именно в ассемблер было то, что предыдущий опыт использования «ускоряющей» dll был полон мучительной отладки, когда какие-то данные вызвали ошибку в потрохах ДЛЛ, а дебагер не может до них докопаться.
Если это ваша dll-ка, то отлаживать её можно из сишной IDE c дельфийским host application.
Это добавляет сложности, но и отладка ассемблера — тоже так себе удовольствие, особенно через пару лет после того, как вы этот ассемблер писали.
Но совершенно спокойно можно прямо в родной старючей delphi xe4 (не слезая с кактуса)
Хорошо, не слезайте, больше не буду уговаривать :)

Информация

В рейтинге
5 212-й
Зарегистрирован
Активность