Аудио-конвертер - оптимизация.

Автор Aleksoid1978, 15 октября 2016, 05:35:33

« назад - далее »

0 Пользователи и 1 гость просматривают эту тему.

Aleksoid1978

Вот решил заняться данным вопросом и попробовать для конвертации использовать SSE инструкции.
Пока реализовал самое необходимое(как по мне) преобразование - float to 32bit integer. Почему именно с этого начал и решили что самое необходимое - да потому что для большинства аудио-форматов ffmpeg(Lossy - с потерями) выдает во float. Встроенный WASAPI вывод в режиме Exclusive для этого случая будет переводить в 32 bit integer.
Вот тестовый билд - https://yadi.sk/d/qVohJjYswsgci

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

Чтобы использовался встроенный механизм конвертации типов - надо чтобы входные/выходные данные отличались только форматом, вот примерно как на скрине:


P.S. Если что - то 24bit [padded] - это тоже 32bit Integer.

По скорости - сравнивать время обработки одного сэмпла конечно же нет смысла. Я для тестов это делал 10000(чтобы можно было ощутить разницу) - sse оптимизированная функция примерно в 15-17 раз быстрее. Тут даже дело не в самой скорости - а в уменьшении использовании процессорного времени, чтобы должно положительно сказываться на работе аудио-рендерера.

Для заинтересованных лиц патч - https://yadi.sk/d/IIufo59lwp7F9
[merge_posts_bbcode]Добавлено: 2016-10-15 12:35:33[/merge_posts_bbcode]

Обновил ссылку на тестовый билд - добавил 32 bit Integer to float, ну и ссылку на патч.
Обновил ссылку на тестовый билд - добавил float to 16bit Integer, ну и ссылку на патч.
Обновил ссылку на тестовый билд - добавил 16 bit Integer to float, ну и ссылку на патч.

Думаю на этом хватит. Хотя можно еще будет добавить float to 24 bit integer(у меня такой режим поддерживает Creative X-Fi, для Shared режима.) Но этот режим практически не используется - поэтому можно пока отложить.
AMD Ryzen 5 3600 /GIGABYTE B450 Gaming X /AMD Radeon R9 16Gb@3200 /Kingston 500Gb M.2 /GTX 1650 /Samsung U28R550UQI /OLED Philips 55OLED707 /Yamaha RX-V471 + NS-555 + NS-C444 + NS-333 + YST-SW215

V0lt

Немного о том, как работает наш конвертер int<->float и почему он вообще такой.

Рассмотрим случай int16->float и float->int16

Исходные данные.
Для int16 имеем диапазон -32768..+32767.
Для float имеем диапазон -1,0..+1,0.
Нули для int16 и float совпадают.

Видим, что максимальное значение по модулю 32768

Текущий алгоритм (без доп. округления)
value_float = (float)value_int16 / 32768; // -1,0..+0,999969
value_int16 = (value_float <= -1,0) ? -32768
            : (value_float >= 0,999969) ? 32767
            : value_float * 32768;
Это очень строгий алгоритм и  в принципе его можно слегка видоизменить, если взять за максимум 32767.

Модифицированный алгоритм (без доп. округления)
value_float = (float)value_int16 / 32767; // -1,000031..+1,0
value_int16 = (value_float < -1,0) ? -32768
            : (value_float >= 1,0) ? 32767
            : value_float * 32767;
Я ждал от него мизерный прирост скорости, но получил даже некоторое замедление.

Алгоритм int32->double->int32 получает аналогично. Корректность проверяется просто - выход равен входу.

Aleksoid1978

Проверил свой алгоритм float -> int32 -> float - выход равен входу. Не проверял int32 -> float -> int32, на уверен что результат будет аналогичный.
[merge_posts_bbcode]Добавлено: 2016-10-15 15:12:26[/merge_posts_bbcode]

В ближайшее время  сделаю для преобразования int16 <-> float.
AMD Ryzen 5 3600 /GIGABYTE B450 Gaming X /AMD Radeon R9 16Gb@3200 /Kingston 500Gb M.2 /GTX 1650 /Samsung U28R550UQI /OLED Philips 55OLED707 /Yamaha RX-V471 + NS-555 + NS-C444 + NS-333 + YST-SW215

V0lt

Цитата: Aleksoid1978Не проверял int32 -> float -> int32, на уверен что результат будет аналогичный.
Увы, но не будет.

Aleksoid1978

По поводу float -> int16 - прирост по скорости вообще от 25 до 30 раз(сравнивал на 10000 операциях).
AMD Ryzen 5 3600 /GIGABYTE B450 Gaming X /AMD Radeon R9 16Gb@3200 /Kingston 500Gb M.2 /GTX 1650 /Samsung U28R550UQI /OLED Philips 55OLED707 /Yamaha RX-V471 + NS-555 + NS-C444 + NS-333 + YST-SW215

V0lt

Заливай патч на 16-бит, погоняю.

Только мне непонятно про 10000. У меня обычный алгоритм на 65536 преобразований туда и обратно выполняется за 0 мс. Приходилось еще один цикл поверх накладывать, чтобы цифры появились.

Aleksoid1978

10000 - преобразований одного пакета, а не одного элемента данных :)
И тут прирост именно при обработке пакета данных а не одного элемента. Для float <-> int32 за одни раз обрабатывается 4 элемента. А для float <-> int16 - сразу 8 элементов.

P.S. Обновил билд и патч, окончательно - теперь работают все заявленные преобразования.
[merge_posts_bbcode]Добавлено: 2016-10-16 14:46:47[/merge_posts_bbcode]

Так, если никто не хочет или не может проверить - то я в ближайшее время заливаю в SVN. Лично проверил все случаи - все корректно работает.

[merge_posts_bbcode]Добавлено: 2016-10-16 15:48:23[/merge_posts_bbcode]

Единственная просьба - у кого есть старое железо, с CPU без SSE2 - проверить работу.
AMD Ryzen 5 3600 /GIGABYTE B450 Gaming X /AMD Radeon R9 16Gb@3200 /Kingston 500Gb M.2 /GTX 1650 /Samsung U28R550UQI /OLED Philips 55OLED707 /Yamaha RX-V471 + NS-555 + NS-C444 + NS-333 + YST-SW215

V0lt

Плохо чего-то оптимизация работает.

Оригинал
convert int16 to float
completed in 187 ms.

convert int16 to float
completed in 2153 ms.

checking
The test is successful.

С оптимизацией.
convert int16 to float SSE
completed in 1358 ms.

convert int16 to float SSE
completed in 1466 ms.

checking
WARNING: -7210 != 32767
Verification failed!

Нет заявленного быстрее в N раз, да еще и ошибки странные делает.

Aleksoid1978

Что и как ты тестировал ?? Опиши. Ну и по поводу проверки(где у тебя WARNING выдает) ??

P.S. Я надеюсь ты проверял не просто на преобразовании одного элемента ??
AMD Ryzen 5 3600 /GIGABYTE B450 Gaming X /AMD Radeon R9 16Gb@3200 /Kingston 500Gb M.2 /GTX 1650 /Samsung U28R550UQI /OLED Philips 55OLED707 /Yamaha RX-V471 + NS-555 + NS-C444 + NS-333 + YST-SW215

V0lt

Убрал возможность оптимизации цикла компилятором.
Оригинал.
convert int16 to float
completed in 4010 ms.

convert int16 to float
completed in 13197 ms.

checking
The test is successful.
Оригинал без доп. округления
convert int16 to float
completed in 3994 ms.

convert int16 to float
completed in 9875 ms.

checking
The test is successful.
SSE
convert int16 to float SSE
completed in 1388 ms.

convert int16 to float SSE
completed in 1482 ms.

checking
The test is successful.
Ошибка ушла (походу был баг компилятора при оптимизациях SSE на циклах с константной длиной). Получил ускорение примерно x3 и x6.

V0lt

Цитата: Aleksoid1978Что и как ты тестировал ?? Опиши.
float содержит 24 бита для числа и 8 бит для степени. Соотвественно в него без проблем должно поместиться 16-битное целое и при правильном подходе ничего не потеряется.

Для проверки генерирую массив из 65536 элементов со значениями от -32768 до 32767. И такой же массив для float и еще одного int16.
Далее произвожу преобразования массивов int16->float->int16 в количестве 100000, ибо точность GetTickCount() оставляет желать лучшего.
Затем сравниваю 1 и 3 массивы, они должны полностью совпадать.
[merge_posts_bbcode]Добавлено: 2016-10-16 11:31:45[/merge_posts_bbcode]

Для int32->float->int32 такой подход конечно не прокатит. Но в целом качественно сравнить два метода по старшим битам можно.

Aleksoid1978

1 - я для сравнения пользовался не GetTickCount(), а нашу функцию GetPerfCounter() / 10000.
2 - я сравнивал(на скорость) не искусственно созданный массив из 65536 - а пакет идущий от декодера. И вот его прогонял 10000 раз.
AMD Ryzen 5 3600 /GIGABYTE B450 Gaming X /AMD Radeon R9 16Gb@3200 /Kingston 500Gb M.2 /GTX 1650 /Samsung U28R550UQI /OLED Philips 55OLED707 /Yamaha RX-V471 + NS-555 + NS-C444 + NS-333 + YST-SW215

V0lt

1. Не принципиально.
2. При правильно поставленном тесте искусственно созданный массив ничем не отличается от пакета идущий от декодера. Там и там кусок памяти. Только вот в массиве можно проверить все интересные значения, а пакете с этим сложнее.

Aleksoid1978

Ну вот у меня выходила разница для float to int16 - 25-30 раз прирост. Обратно не проверял.

То что float -> int16 быстрее чем int16 -> float - это понятно, для перевода int16 -> float с помощью simd надо сперва 8x16 перевести в 2 по 4x32, потом уже во float.

P.S. Проверил int16 -> float, на 8192 элементах. 100000 прогонов.
Без sse - 1100 - 1150 мс
с sse - 135-140 мс
[merge_posts_bbcode]Добавлено: 2016-10-16 19:17:24[/merge_posts_bbcode]

Ну в любом случае вывод какой - мое нововведение очень даже имеет место быть :)

[merge_posts_bbcode]Добавлено: 2016-10-16 19:41:13[/merge_posts_bbcode]

Проверил преобразование float -> int16 -> float - теряются/меняются только после 5 - 6 знака после запятой. Что в принципе и логично.
AMD Ryzen 5 3600 /GIGABYTE B450 Gaming X /AMD Radeon R9 16Gb@3200 /Kingston 500Gb M.2 /GTX 1650 /Samsung U28R550UQI /OLED Philips 55OLED707 /Yamaha RX-V471 + NS-555 + NS-C444 + NS-333 + YST-SW215

V0lt

Проверка float->int16->float имеет мало смысла. Тем более сравнивать знаки после запятой. Знаешь сколько достоверных значащих цифр у float? Будешь удивлен. :)
Надо целые сравнивать.
[merge_posts_bbcode]Добавлено: 2016-10-16 20:30:34[/merge_posts_bbcode]

Мне в патче непонятно следующая строкаstatic const __m128 __32bitMin = _mm_set_ps1((float)INT24_MAX / INT24_PEAK);Опечатка?