История изменений
Исправление sadko4u, (текущая версия) :
Позвольте докажу обратное.
lsp-plugins> make clean && make -j8 test
lsp-plugins> .test/lsp-plugins-test ptest dsp.filters.static
Получаем:
--------------------------------------------------------------------------------
LSP version: 1.1.22
--------------------------------------------------------------------------------
CPU information:
Architecture: x86_64
CPU string: AMD Ryzen 7 2700 Eight-Core Processor
CPU model: vendor=AMD, family=0x17, model=0x8
Features: FPU CMOV MMX FXSAVE SSE SSE2 SSE3 SSSE3 SSE4.1 SSE4.2 SSE4A XSAVE FMA3 AVX AVX2
--------------------------------------------------------------------------------
Statistics of performance test 'dsp.filters.static':
┌Case──────────────────────────┬Time[s]┬───Iter┬Samp[s]┬────Est┬Perf[i/s]┬Cost[us/i]┬Rel[%]┐
│native::biquad_process_x1 x8 │ 10.00│ 799000│ 10.00│ 798799│ 79879.97│ 12.5188│107.32│
│sse::biquad_process_x1 x8 │ 10.00│ 808000│ 10.00│ 807635│ 80763.58│ 12.3818│108.51│
│avx::biquad_process_x1 x8 │ 10.01│1071000│ 10.00│1070371│107037.18│ 9.3425│143.81│
│avx::biquad_process_x1_fma3 x8│ 10.01│ 745000│ 10.00│ 744304│ 74430.44│ 13.4354│100.00│
├──────────────────────────────┼───────┼───────┼───────┼───────┼─────────┼──────────┼──────┤
│native::biquad_process_x2 x4 │ 10.00│1488000│ 10.00│1487514│148751.43│ 6.7226│116.00│
│sse::biquad_process_x2 x4 │ 10.00│1283000│ 10.00│1282392│128239.27│ 7.7979│100.00│
│avx::biquad_process_x2 x4 │ 10.00│1404000│ 10.00│1403319│140331.95│ 7.1260│109.43│
│avx::biquad_process_x2_fma3 x4│ 10.00│1381000│ 10.00│1380840│138084.04│ 7.2420│107.68│
├──────────────────────────────┼───────┼───────┼───────┼───────┼─────────┼──────────┼──────┤
│native::biquad_process_x4 x2 │ 10.00│1870000│ 10.00│1869154│186915.46│ 5.3500│100.00│
│sse::biquad_process_x4 x2 │ 10.00│3083000│ 10.00│3082373│308237.37│ 3.2443│164.91│
│avx::biquad_process_x4 x2 │ 10.00│3417000│ 10.00│3416206│341620.64│ 2.9272│182.77│
│avx::biquad_process_x4_fma3 x2│ 10.00│3631000│ 10.00│3630336│363033.67│ 2.7546│194.22│
├──────────────────────────────┼───────┼───────┼───────┼───────┼─────────┼──────────┼──────┤
│native::biquad_process_x8 x1 │ 10.00│1837000│ 10.00│1836170│183617.02│ 5.4461│100.00│
│sse::biquad_process_x8 x1 │ 10.00│3049000│ 10.00│3048621│304862.14│ 3.2802│166.03│
│sse3::x64_biquad_process_x8 x1│ 10.00│5649000│ 10.00│5648231│564823.18│ 1.7705│307.61│
│avx::x64_biquad_process_x8 x1 │ 10.00│3774000│ 10.00│3773788│377378.87│ 2.6499│205.52│
│avx::biquad_process_x8_fma3 x1│ 10.00│4269000│ 10.00│4268859│426885.96│ 2.3425│232.49│
└──────────────────────────────┴───────┴───────┴───────┴───────┴─────────┴──────────┴──────┘
Performance test 'dsp.filters.static' has succeeded, execution time: 170.15 s
Коротко о тесте: тестируется банк из 8 последовательных биквадратных фильтров на буфере в 512 семплов. Что такое биквадратный фильтр: https://en.wikipedia.org/wiki/Digital_biquad_filter
-
native:: - реализация на Си++
-
sse::, sse3::, avx:: - использование функций с ручной оптимизацией под соответствующий набор команд процессора (грубо говоря - векторизованный код на ассемблере).
-
x1 - алгоритм принимает на вход 1 биквадратный фильтр и обрабатывает буфер из 512 семплов, и так делается, пока все 8 фильтров в пачке не будут применены.
-
x8 - алгоритм принимает на вход сразу 8 биквадратных фильтров и обрабатывает с их помощью буфер из 512 семплов.
Иными слвами, x1 - 8 фильтров поштучно, x8 - 8 фильтров одной пачкой, x2 и x4 - промежуточные варианты. Заметим ещё раз, что фильтры объединены последовательно, а не каждый обрабатывает свой буфер из 512 семплов.
Каждый тест запускается на буфере в течение 10 секунд и считается количество отработавших циклов обработки с шагом 1000 (то есть, время замеряется на каждом 1000ом шаге итерации, чтобы минимизировать задержки на дополнительных библиотечных/системных вызовах).
В итоге, нормированное количество выполненных итераций в секунду (третья справа графа) и время выполнения одной итерации (предпоследняя графа).
А теперь сравниваем:
- native::biquad_process_x1 - 12.5188 микросекунд на итерацию
- native::bilinear_transform_x8 - 5.4461 микросекунд на итерацию
То есть, просто переписав алгоритм на использование 8 последовательных фильтров вместо одного и сделав его потенциально векторизуемым, мы уже его ускорили в 2.3 раза.
Теперь переходим к «тяжёлой артиллерии» и оптимизируем алгоритм руками, просто написав соответствующий ассемблерный код:
- sse::biquad_process_x8 - 3.2802 мксек (буст в 3.8 раза)
- sse3::x64_biquad_process_x8 - 1.7705 мксек (буст в 7 раз)
- avx::x64_biquad_process_x8 2.6499 мксек (буст в 4.7 раз)
- avx::biquad_process_x8_fma3 - 2.3425 мксек (буст в 5.3 раза)
Очевидно, что ручные оптимизации с использованием машинных кодов значительно лучше справляются, нежели компилятор. Почему AVX отработал хуже - особенности работы арифметики Ryzen поколения Zen+, там ещё AVX неполноценный, поэтому sse3 с использованием 16 xmm-регистров оказывается быстрее (видимо, лучше параллелится на уровне микрокоманд).
Этот тест подтверждает следующее утверждение:
Though there are a lot of performance penalties for this kind of design, the most significant one is that it doesn't scale. At all. One hundred rockets costs one hundred times as much as one rocket. And it's extremely likely it costs even more than that! Even to a non-programmer, that shouldn't make any sense. Economy of scale. If you have more of something, it should get cheaper, not more expensive. And the way to do that is to design the data properly and group things by similar transformations.
То есть, мы одним тестом показали, что 8 фильтров стоят в разы дешевле, чем если бы мы применяли их поштучно. Именно такие оптимизации и позволяют вашему JACK’у не иксрунить. А если разработчики не будут думать о таких оптимизациях и впредь - ваш JACK и будет впредь иксрунить.
Исправление sadko4u, :
Позвольте докажу обратное.
lsp-plugins> make clean && make -j8 test
lsp-plugins> .test/lsp-plugins-test ptest dsp.filters.static
Получаем:
--------------------------------------------------------------------------------
LSP version: 1.1.22
--------------------------------------------------------------------------------
CPU information:
Architecture: x86_64
CPU string: AMD Ryzen 7 2700 Eight-Core Processor
CPU model: vendor=AMD, family=0x17, model=0x8
Features: FPU CMOV MMX FXSAVE SSE SSE2 SSE3 SSSE3 SSE4.1 SSE4.2 SSE4A XSAVE FMA3 AVX AVX2
--------------------------------------------------------------------------------
Statistics of performance test 'dsp.filters.static':
┌Case──────────────────────────┬Time[s]┬───Iter┬Samp[s]┬────Est┬Perf[i/s]┬Cost[us/i]┬Rel[%]┐
│native::biquad_process_x1 x8 │ 10.00│ 799000│ 10.00│ 798799│ 79879.97│ 12.5188│107.32│
│sse::biquad_process_x1 x8 │ 10.00│ 808000│ 10.00│ 807635│ 80763.58│ 12.3818│108.51│
│avx::biquad_process_x1 x8 │ 10.01│1071000│ 10.00│1070371│107037.18│ 9.3425│143.81│
│avx::biquad_process_x1_fma3 x8│ 10.01│ 745000│ 10.00│ 744304│ 74430.44│ 13.4354│100.00│
├──────────────────────────────┼───────┼───────┼───────┼───────┼─────────┼──────────┼──────┤
│native::biquad_process_x2 x4 │ 10.00│1488000│ 10.00│1487514│148751.43│ 6.7226│116.00│
│sse::biquad_process_x2 x4 │ 10.00│1283000│ 10.00│1282392│128239.27│ 7.7979│100.00│
│avx::biquad_process_x2 x4 │ 10.00│1404000│ 10.00│1403319│140331.95│ 7.1260│109.43│
│avx::biquad_process_x2_fma3 x4│ 10.00│1381000│ 10.00│1380840│138084.04│ 7.2420│107.68│
├──────────────────────────────┼───────┼───────┼───────┼───────┼─────────┼──────────┼──────┤
│native::biquad_process_x4 x2 │ 10.00│1870000│ 10.00│1869154│186915.46│ 5.3500│100.00│
│sse::biquad_process_x4 x2 │ 10.00│3083000│ 10.00│3082373│308237.37│ 3.2443│164.91│
│avx::biquad_process_x4 x2 │ 10.00│3417000│ 10.00│3416206│341620.64│ 2.9272│182.77│
│avx::biquad_process_x4_fma3 x2│ 10.00│3631000│ 10.00│3630336│363033.67│ 2.7546│194.22│
├──────────────────────────────┼───────┼───────┼───────┼───────┼─────────┼──────────┼──────┤
│native::biquad_process_x8 x1 │ 10.00│1837000│ 10.00│1836170│183617.02│ 5.4461│100.00│
│sse::biquad_process_x8 x1 │ 10.00│3049000│ 10.00│3048621│304862.14│ 3.2802│166.03│
│sse3::x64_biquad_process_x8 x1│ 10.00│5649000│ 10.00│5648231│564823.18│ 1.7705│307.61│
│avx::x64_biquad_process_x8 x1 │ 10.00│3774000│ 10.00│3773788│377378.87│ 2.6499│205.52│
│avx::biquad_process_x8_fma3 x1│ 10.00│4269000│ 10.00│4268859│426885.96│ 2.3425│232.49│
└──────────────────────────────┴───────┴───────┴───────┴───────┴─────────┴──────────┴──────┘
Performance test 'dsp.filters.static' has succeeded, execution time: 170.15 s
Коротко о тесте: тестируется банк из 8 последовательных биквадратных фильтров на буфере в 512 семплов. Что такое биквадратный фильтр: https://en.wikipedia.org/wiki/Digital_biquad_filter
native:: - реализация на Си++ sse::, sse3::, avx:: - использование функций с ручной оптимизацией под соответствующий набор команд процессора (грубо говоря - векторизованный код на ассемблере).
x1 - алгоритм принимает на вход 1 биквадратный фильтр и обрабатывает буфер из 512 семплов, и так делается, пока все 8 фильтров в пачке не будут применены. x8 - алгоритм принимает на вход сразу 8 биквадратных фильтров и обрабатывает с их помощью буфер из 512 семплов. Иными слвами, x1 - 8 фильтров поштучно, x8 - 8 фильтров одной пачкой, x2 и x4 - промежуточные варианты. Заметим ещё раз, что фильтры объединены последовательно, а не каждый обрабатывает свой буфер из 512 семплов.
Каждый тест запускается на буфере в течение 10 секунд и считается количество отработавших циклов обработки с шагом 1000 (то есть, время замеряется на каждом 1000ом шаге итерации, чтобы минимизировать задержки на дополнительных библиотечных/системных вызовах).
В итоге, нормированное количество выполненных итераций в секунду (третья справа графа) и время выполнения одной итерации (предпоследняя графа).
А теперь сравниваем:
- native::biquad_process_x1 - 12.5188 микросекунд на итерацию
- native::bilinear_transform_x8 - 5.4461 микросекунд на итерацию
То есть, просто переписав алгоритм на использование 8 последовательных фильтров вместо одного и сделав его потенциально векторизуемым, мы уже его ускорили в 2.3 раза.
Теперь переходим к «тяжёлой артиллерии» и оптимизируем алгоритм руками, просто написав соответствующий ассемблерный код:
- sse::biquad_process_x8 - 3.2802 мксек (буст в 3.8 раза)
- sse3::x64_biquad_process_x8 - 1.7705 мксек (буст в 7 раз)
- avx::x64_biquad_process_x8 2.6499 мксек (буст в 4.7 раз)
- avx::biquad_process_x8_fma3 - 2.3425 мксек (буст в 5.3 раза)
Очевидно, что ручные оптимизации с использованием машинных кодов значительно лучше справляются, нежели компилятор. Почему AVX отработал хуже - особенности работы арифметики Ryzen поколения Zen+, там ещё AVX неполноценный, поэтому sse3 с использованием 16 xmm-регистров оказывается быстрее (видимо, лучше параллелится на уровне микрокоманд).
Этот тест подтверждает следующее утверждение:
Though there are a lot of performance penalties for this kind of design, the most significant one is that it doesn't scale. At all. One hundred rockets costs one hundred times as much as one rocket. And it's extremely likely it costs even more than that! Even to a non-programmer, that shouldn't make any sense. Economy of scale. If you have more of something, it should get cheaper, not more expensive. And the way to do that is to design the data properly and group things by similar transformations.
То есть, мы одним тестом показали, что 8 фильтров стоят в разы дешевле, чем если бы мы применяли их поштучно. Именно такие оптимизации и позволяют вашему JACK’у не иксрунить. А если разработчики не будут думать о таких оптимизациях и впредь - ваш JACK и будет впредь иксрунить.
Исправление sadko4u, :
Позвольте докажу обратное.
lsp-plugins> make clean && make -j8 test
lsp-plugins> .test/lsp-plugins-test ptest dsp.filters.static
Получаем:
--------------------------------------------------------------------------------
LSP version: 1.1.22
--------------------------------------------------------------------------------
CPU information:
Architecture: x86_64
CPU string: AMD Ryzen 7 2700 Eight-Core Processor
CPU model: vendor=AMD, family=0x17, model=0x8
Features: FPU CMOV MMX FXSAVE SSE SSE2 SSE3 SSSE3 SSE4.1 SSE4.2 SSE4A XSAVE FMA3 AVX AVX2
--------------------------------------------------------------------------------
Statistics of performance test 'dsp.filters.static':
┌Case──────────────────────────┬Time[s]┬───Iter┬Samp[s]┬────Est┬Perf[i/s]┬Cost[us/i]┬Rel[%]┐
│native::biquad_process_x1 x8 │ 10.00│ 799000│ 10.00│ 798799│ 79879.97│ 12.5188│107.32│
│sse::biquad_process_x1 x8 │ 10.00│ 808000│ 10.00│ 807635│ 80763.58│ 12.3818│108.51│
│avx::biquad_process_x1 x8 │ 10.01│1071000│ 10.00│1070371│107037.18│ 9.3425│143.81│
│avx::biquad_process_x1_fma3 x8│ 10.01│ 745000│ 10.00│ 744304│ 74430.44│ 13.4354│100.00│
├──────────────────────────────┼───────┼───────┼───────┼───────┼─────────┼──────────┼──────┤
│native::biquad_process_x2 x4 │ 10.00│1488000│ 10.00│1487514│148751.43│ 6.7226│116.00│
│sse::biquad_process_x2 x4 │ 10.00│1283000│ 10.00│1282392│128239.27│ 7.7979│100.00│
│avx::biquad_process_x2 x4 │ 10.00│1404000│ 10.00│1403319│140331.95│ 7.1260│109.43│
│avx::biquad_process_x2_fma3 x4│ 10.00│1381000│ 10.00│1380840│138084.04│ 7.2420│107.68│
├──────────────────────────────┼───────┼───────┼───────┼───────┼─────────┼──────────┼──────┤
│native::biquad_process_x4 x2 │ 10.00│1870000│ 10.00│1869154│186915.46│ 5.3500│100.00│
│sse::biquad_process_x4 x2 │ 10.00│3083000│ 10.00│3082373│308237.37│ 3.2443│164.91│
│avx::biquad_process_x4 x2 │ 10.00│3417000│ 10.00│3416206│341620.64│ 2.9272│182.77│
│avx::biquad_process_x4_fma3 x2│ 10.00│3631000│ 10.00│3630336│363033.67│ 2.7546│194.22│
├──────────────────────────────┼───────┼───────┼───────┼───────┼─────────┼──────────┼──────┤
│native::biquad_process_x8 x1 │ 10.00│1837000│ 10.00│1836170│183617.02│ 5.4461│100.00│
│sse::biquad_process_x8 x1 │ 10.00│3049000│ 10.00│3048621│304862.14│ 3.2802│166.03│
│sse3::x64_biquad_process_x8 x1│ 10.00│5649000│ 10.00│5648231│564823.18│ 1.7705│307.61│
│avx::x64_biquad_process_x8 x1 │ 10.00│3774000│ 10.00│3773788│377378.87│ 2.6499│205.52│
│avx::biquad_process_x8_fma3 x1│ 10.00│4269000│ 10.00│4268859│426885.96│ 2.3425│232.49│
└──────────────────────────────┴───────┴───────┴───────┴───────┴─────────┴──────────┴──────┘
Performance test 'dsp.filters.static' has succeeded, execution time: 170.15 s
Коротко о тесте: тестируется банк из 8 последовательных биквадратных фильтров на буфере в 512 семплов. Что такое биквадратный фильтр: https://en.wikipedia.org/wiki/Digital_biquad_filter
native:: - реализация на Си++ sse::, sse3::, avx:: - использование функций с ручной оптимизацией под соответствующий набор команд процессора (грубо говоря - векторизованный код на ассемблере).
x1 - алгоритм принимает на вход 1 биквадратный фильтр и обрабатывает буфер из 512 семплов, и так делается, пока все 8 фильтров в пачке не будут применены. x8 - алгоритм принимает на вход сразу 8 биквадратных фильтров и обрабатывает с их помощью буфер из 512 семплов. Иными слвами, x1 - 8 фильтров поштучно, x8 - 8 фильтров одной пачкой, x2 и x4 - промежуточные варианты. Заметим ещё раз, что фильтры объединены последовательно, а не каждый обрабатывает свой буфер из 512 семплов.
Каждый тест запускается на буфере в течение 10 секунд и считается количество отработавших циклов обработки с шагом 1000 (то есть, время замеряется на каждом 1000ом шаге итерации, чтобы минимизировать задержки на дополнительных библиотечных/системных вызовах).
В итоге, нормированное количество выполненных итераций в секунду (третья справа графа) и время выполнения одной итерации (предпоследняя графа).
А теперь сравниваем:
- native::biquad_process_x1 - 12.5188 микросекунд на итерацию
- native::bilinear_transform_x8 - 5.4461 микросекунд на итерацию
То есть, просто переписав алгоритм на использование 8 последовательных фильтров вместо одного и сделав его потенциально векторизуемым, мы уже его ускорили в 2.3 раза.
Теперь переходим к «тяжёлой артиллерии» и оптимизируем алгоритм руками, просто написав соответствующий ассемблерный код:
- sse::biquad_process_x8 - 3.2802 мксек (буст в 3.8 раза)
- sse3::x64_biquad_process_x8 - 1.7705 мксек (буст в 7 раз)
- avx::x64_biquad_process_x8 2.6499 мксек (буст в 4.7 раз)
- avx::biquad_process_x8_fma3 - 2.3425 мксек (буст в 5.3 раза)
Очевидно, что ручные оптимизации с использованием машинных кодов значительно лучше справляются, нежели компилятор. Почему AVX отработал хуже - особенности работы арифметики Ryzen поколения Zen+, там ещё AVX неполноценный, поэтому sse3 с использованием 16 xmm-регистров оказывается быстрее (видимо, лучше параллелится на уровне микрокоманд).
Этот тест подтверждает следующее утверждение:
Though there are a lot of performance penalties for this kind of design, the most significant one is that it doesn't scale. At all. One hundred rockets costs one hundred times as much as one rocket. And it's extremely likely it costs even more than that! Even to a non-programmer, that shouldn't make any sense. Economy of scale. If you have more of something, it should get cheaper, not more expensive. And the way to do that is to design the data properly and group things by similar transformations.
То есть, мы одним тестом показали, что 8 фильтров стоят в разы дешевле, чем если бы мы применяли их поштучно. Именно такие оптимизации и позволяют вашему JACK’у не иксрунить. А если разработчики не будут думать о таких оптимизациях и впредть - ваш JACK и будет впредть иксрунить.
Исходная версия sadko4u, :
Позвольте докажу обратное.
lsp-plugins> make clean && make -j8 test
lsp-plugins> .test/lsp-plugins-test ptest dsp.filters.static
Получаем:
--------------------------------------------------------------------------------
LSP version: 1.1.22
--------------------------------------------------------------------------------
CPU information:
Architecture: x86_64
CPU string: AMD Ryzen 7 2700 Eight-Core Processor
CPU model: vendor=AMD, family=0x17, model=0x8
Features: FPU CMOV MMX FXSAVE SSE SSE2 SSE3 SSSE3 SSE4.1 SSE4.2 SSE4A XSAVE FMA3 AVX AVX2
--------------------------------------------------------------------------------
Statistics of performance test 'dsp.filters.static':
┌Case──────────────────────────┬Time[s]┬───Iter┬Samp[s]┬────Est┬Perf[i/s]┬Cost[us/i]┬Rel[%]┐
│native::biquad_process_x1 x8 │ 10.00│ 799000│ 10.00│ 798799│ 79879.97│ 12.5188│107.32│
│sse::biquad_process_x1 x8 │ 10.00│ 808000│ 10.00│ 807635│ 80763.58│ 12.3818│108.51│
│avx::biquad_process_x1 x8 │ 10.01│1071000│ 10.00│1070371│107037.18│ 9.3425│143.81│
│avx::biquad_process_x1_fma3 x8│ 10.01│ 745000│ 10.00│ 744304│ 74430.44│ 13.4354│100.00│
├──────────────────────────────┼───────┼───────┼───────┼───────┼─────────┼──────────┼──────┤
│native::biquad_process_x2 x4 │ 10.00│1488000│ 10.00│1487514│148751.43│ 6.7226│116.00│
│sse::biquad_process_x2 x4 │ 10.00│1283000│ 10.00│1282392│128239.27│ 7.7979│100.00│
│avx::biquad_process_x2 x4 │ 10.00│1404000│ 10.00│1403319│140331.95│ 7.1260│109.43│
│avx::biquad_process_x2_fma3 x4│ 10.00│1381000│ 10.00│1380840│138084.04│ 7.2420│107.68│
├──────────────────────────────┼───────┼───────┼───────┼───────┼─────────┼──────────┼──────┤
│native::biquad_process_x4 x2 │ 10.00│1870000│ 10.00│1869154│186915.46│ 5.3500│100.00│
│sse::biquad_process_x4 x2 │ 10.00│3083000│ 10.00│3082373│308237.37│ 3.2443│164.91│
│avx::biquad_process_x4 x2 │ 10.00│3417000│ 10.00│3416206│341620.64│ 2.9272│182.77│
│avx::biquad_process_x4_fma3 x2│ 10.00│3631000│ 10.00│3630336│363033.67│ 2.7546│194.22│
├──────────────────────────────┼───────┼───────┼───────┼───────┼─────────┼──────────┼──────┤
│native::biquad_process_x8 x1 │ 10.00│1837000│ 10.00│1836170│183617.02│ 5.4461│100.00│
│sse::biquad_process_x8 x1 │ 10.00│3049000│ 10.00│3048621│304862.14│ 3.2802│166.03│
│sse3::x64_biquad_process_x8 x1│ 10.00│5649000│ 10.00│5648231│564823.18│ 1.7705│307.61│
│avx::x64_biquad_process_x8 x1 │ 10.00│3774000│ 10.00│3773788│377378.87│ 2.6499│205.52│
│avx::biquad_process_x8_fma3 x1│ 10.00│4269000│ 10.00│4268859│426885.96│ 2.3425│232.49│
└──────────────────────────────┴───────┴───────┴───────┴───────┴─────────┴──────────┴──────┘
Performance test 'dsp.filters.static' has succeeded, execution time: 170.15 s
Коротко о тесте: тестируется банк из 8 последовательных биквадратных фильтров на буфере в 512 семплов. Что такое биквадратный фильтр: https://en.wikipedia.org/wiki/Digital_biquad_filter
native:: - реализация на Си++ sse::, sse3::, avx:: - использование функций с ручной оптимизацией под соответствующий набор команд процессора (грубо говоря - векторизованный код на ассемблере).
x1 - алгоритм принимает на вход 1 биквадратный фильтр и обрабатывает буфер из 512 семплов, и так делается, пока все 8 фильтров в пачке не будут применены. x8 - алгоритм принимает на вход сразу 8 биквадратных фильтров и обрабатывает с их помощью буфер из 512 семплов. Иными слвами, x1 - 8 фильтров поштучно, x8 - 8 фильтров одной пачкой, x2 и x4 - промежуточные варианты. Заметим ещё раз, что фильтры объединены последовательно, а не каждый обрабатывает свой буфер из 512 семплов.
Каждый тест запускается на буфере в течение 10 секунд и считается количество отработавших циклов обработки с шагом 1000 (то есть, время замеряется на каждом 1000ом шаге итерации, чтобы минимизировать задержки на дополнительных библиотечных/системных вызовах).
В итоге, нормированное количество выполненных итераций в секунду (третья справа графа) и время выполнения одной итерации (предпоследняя графа).
А теперь сравниваем: native::biquad_process_x1 - 12.5188 микросекунд на итерацию native::bilinear_transform_x8 - 5.4461 микросекунд на итерацию
То есть, просто переписав алгоритм на использование 8 последовательных фильтров вместо одного и сделав его потенциально векторизуемым, мы уже его ускорили в 2.3 раза.
Теперь переходим к «тяжёлой артиллерии» и оптимизируем алгоритм руками, просто написав соответствующий ассемблерный код: sse::biquad_process_x8 - 3.2802 мксек (буст в 3.8 раза) sse3::x64_biquad_process_x8 - 1.7705 мксек (буст в 7 раз) avx::x64_biquad_process_x8 2.6499 мксек (буст в 4.7 раз) avx::biquad_process_x8_fma3 - 2.3425 мксек (буст в 5.3 раза)
Очевидно, что ручные оптимизации с использованием машинных кодов значительно лучше справляются, нежели компилятор. Почему AVX отработал хуже - особенности работы арифметики Ryzen поколения Zen+, там ещё AVX неполноценный, поэтому sse3 с использованием 16 xmm-регистров оказывается быстрее (видимо, лучше параллелится на уровне микрокоманд).
Этот тест подтверждает следующее утверждение:
Though there are a lot of performance penalties for this kind of design, the most significant one is that it doesn't scale. At all. One hundred rockets costs one hundred times as much as one rocket. And it's extremely likely it costs even more than that! Even to a non-programmer, that shouldn't make any sense. Economy of scale. If you have more of something, it should get cheaper, not more expensive. And the way to do that is to design the data properly and group things by similar transformations.
То есть, мы одним тестом показали, что 8 фильтров стоят в разы дешевле, чем если бы мы применяли их поштучно. Именно такие оптимизации и позволяют вашему JACK’у не иксрунить. А если разработчики не будут думать о таких оптимизациях и впредть - ваш JACK и будет впредть иксрунить.