Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bench against vcl and optimize #5

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open

Conversation

NamorNiradnug
Copy link
Owner

@NamorNiradnug NamorNiradnug commented May 3, 2024

C++ рулит :)

(для плюсов специально выставлены флаги -Ofast и -fveclib=libmvec, чтобы скалярный код векторизовался)

test bench_sin_f32_scalar           ... bench:     948,430 ns/iter (+/- 6,007)
test bench_sin_f32_vec              ... bench:     136,525 ns/iter (+/- 1,474)
...
test vcl_bench::bench_ExpScalar_cpp ... bench:      80,149 ns/iter (+/- 1,237)
test vcl_bench::bench_ExpVCL_cpp    ... bench:      74,194 ns/iter (+/- 1,947)
test vcl_bench::bench_SinScalar_cpp ... bench:      90,799 ns/iter (+/- 1,369)
test vcl_bench::bench_SinVCL_cpp    ... bench:      92,291 ns/iter (+/- 1,074)

@NamorNiradnug
Copy link
Owner Author

NamorNiradnug commented May 3, 2024

С -O3 выглядит так:

test vcl_bench::bench_ExpScalar_cpp ... bench:     456,992 ns/iter (+/- 9,823)
test vcl_bench::bench_ExpVCL_cpp    ... bench:      80,420 ns/iter (+/- 1,070)
test vcl_bench::bench_SinScalar_cpp ... bench:   1,080,094 ns/iter (+/- 7,431)
test vcl_bench::bench_SinVCL_cpp    ... bench:     106,585 ns/iter (+/- 1,258)

С -O3 сравнивать несколько честнее, чем с -Ofast-ffast-math как следствие), потому что с std::simd в Rust все-таки нельзя делать такие же отпимизации, как в С++ с -ffast-math.
Но все еще разница ~30% :(.

@NamorNiradnug
Copy link
Owner Author

NamorNiradnug commented May 4, 2024

После отпимизаций sin/cos почти сравнялись.
Предполагаю, что оставшийся зазор в ~1% — какие-нибудь лишние проверки, которые без unsafe-а не убрать.

В процессе выяснилось, что:

  • to_int_unchecked в знаковый i32/i64 заметно быстрее, чем в беззнаковый: это дало где-то 10μs ускорения (при ~135 μs начальных)
  • добавленные assert_eq! действительно помогают компилятору (они тут по сравнению с C++-овой версией честные, т.к. в плюсовой длина массива зарадкожена, а в бенчмарках она при передаче в замыкание в b.iter стирается)
  • слайсы работают быстрее векторов: даже если в bench_..._vec в замыкании обращаться не напрямую к захваченным векторам, а сначала брать от векторов слайсы, то это ускоряет на ~10μs; есть предположение, что это потому что компилятор не справился убрать какие-нибудь проверки/не сработали assert_eq! как assume.
test bench_cos_f32_core_simd        ... bench:     975,239 ns/iter (+/- 17,079)
test bench_cos_f32_scalar           ... bench:     947,916 ns/iter (+/- 13,282)
test bench_cos_f32_vec              ... bench:     105,525 ns/iter (+/- 1,427)
test bench_cos_f64_core_simd        ... bench:   1,990,004 ns/iter (+/- 59,352)
test bench_cos_f64_scalar           ... bench:   1,958,594 ns/iter (+/- 11,851)
test bench_cos_f64_vec              ... bench:     487,930 ns/iter (+/- 17,176)
test bench_sin_f32_core_simd        ... bench:     976,594 ns/iter (+/- 17,137)
test bench_sin_f32_scalar           ... bench:     950,661 ns/iter (+/- 19,528)
test bench_sin_f32_vec              ... bench:     105,318 ns/iter (+/- 1,614)
test bench_sin_f64_core_simd        ... bench:   1,963,591 ns/iter (+/- 39,791)
test bench_sin_f64_scalar           ... bench:   1,914,041 ns/iter (+/- 20,737)
test bench_sin_f64_vec              ... bench:     481,824 ns/iter (+/- 14,619)
test vcl_bench::bench_ExpScalar_cpp ... bench:     439,979 ns/iter (+/- 6,899)
test vcl_bench::bench_ExpVCL_cpp    ... bench:      77,665 ns/iter (+/- 1,118)
test vcl_bench::bench_SinScalar_cpp ... bench:   1,064,079 ns/iter (+/- 10,545)
test vcl_bench::bench_SinVCL_cpp    ... bench:     103,842 ns/iter (+/- 1,586)

@NamorNiradnug
Copy link
Owner Author

NamorNiradnug commented May 4, 2024

Посмотрел ассемблерный выхлоп и обраружил, что round() генерирует не одну инструкцию vroundp, а несколько (какие-то or и and, видимо чтобы обеспечить round-у нужное поведение в граничных случаях). mul_add и trunc генерируют (на нужном железе) ровно две инструкции: vfmadd и vroundp.

test bench_cos_f32_vec              ... bench:      97,229 ns/iter (+/- 1,511)
...
test bench_cos_f64_vec              ... bench:     478,899 ns/iter (+/- 37,561)
...
test bench_sin_f32_vec              ... bench:      98,584 ns/iter (+/- 1,052)
...
test bench_sin_f64_vec              ... bench:     519,695 ns/iter (+/- 44,201)
...
test vcl_bench::bench_SinVCL_cpp    ... bench:     103,941 ns/iter (+/- 7,792)

@NamorNiradnug
Copy link
Owner Author

NamorNiradnug commented May 20, 2024

Обгоняет VCL-евский синус на -Ofast!

test bench_asin_f32_scalar               ... bench:     822,945 ns/iter (+/- 11,606)
test bench_asin_f32_vec                  ... bench:      68,016 ns/iter (+/- 1,463)
test bench_atan_f32_scalar               ... bench:   1,444,918 ns/iter (+/- 11,674)
test bench_atan_f32_vec                  ... bench:      73,772 ns/iter (+/- 4,658)
test bench_cos_f32_core_simd             ... bench:     998,328 ns/iter (+/- 34,261)
test bench_cos_f32_scalar                ... bench:     999,563 ns/iter (+/- 37,323)
test bench_cos_f32_vec                   ... bench:      88,989 ns/iter (+/- 1,004)
test bench_cos_f64_core_simd             ... bench:   2,047,228 ns/iter (+/- 98,166)
test bench_cos_f64_scalar                ... bench:   2,017,689 ns/iter (+/- 59,677)
test bench_cos_f64_vec                   ... bench:     455,939 ns/iter (+/- 86,437)
test bench_sin_f32_core_simd             ... bench:     998,575 ns/iter (+/- 20,798)
test bench_sin_f32_scalar                ... bench:     987,702 ns/iter (+/- 10,921)
test bench_sin_f32_vec                   ... bench:      91,300 ns/iter (+/- 1,303)
test bench_sin_f64_core_simd             ... bench:   2,046,133 ns/iter (+/- 93,884)
test bench_sin_f64_scalar                ... bench:   1,985,718 ns/iter (+/- 29,760)
test bench_sin_f64_vec                   ... bench:     446,279 ns/iter (+/- 14,796)
test vcl_bench::bench_asin_f32_vcl_cpp   ... bench:      69,023 ns/iter (+/- 2,098)
test vcl_bench::bench_atan_f32_vcl_cpp   ... bench:      84,623 ns/iter (+/- 1,691)
test vcl_bench::bench_exp_f32_scalar_cpp ... bench:      81,997 ns/iter (+/- 2,106)
test vcl_bench::bench_exp_f32_vcl_cpp    ... bench:      75,427 ns/iter (+/- 1,688)
test vcl_bench::bench_sin_f32_scalar_cpp ... bench:      90,706 ns/iter (+/- 2,022)
test vcl_bench::bench_sin_f32_vcl_cpp    ... bench:      93,384 ns/iter (+/- 1,396)

Откуда ускорение? Схема Горнера в для квадратичного многочлена использует просто 2 FMA инструкции. А схема Эстрина возводит в квадрат, а затем тоже делает две FMA. Разница в целое умножение, которое, что важно, является зависимостью следующих двух инструкций.

Если посмотреть на кубический многочлен, там тоже вроде как будет разница между Горнером и Эстрином в одно умножение (по 3 FMA и для обоих), но там для Эстрина две из этих FMA можно считать параллельно, и видимо поэтому эффекта это не дает.

@NamorNiradnug
Copy link
Owner Author

NamorNiradnug commented May 20, 2024

Перезапустил вместе с exp:

portable_simd_addons VCL libmvec
sin 89,195 92,319 90,499
asin 68,096 67,946 TODO
atan 72,907 83,579 TODO
exp 83,019 75,149 81,720

@NamorNiradnug
Copy link
Owner Author

NamorNiradnug commented May 21, 2024

Оказывается, для разных функций разный оптимальный размер N для Simd<f32, N> в цикле: для sin и cos при уменьшении происходит значительная потеря производительности. А для exp наоборот.

test bench_exp_f32_vec                       ... bench:      66,949 ns/iter (+/- 761)
test cpp_benches::bench_exp_f32_scalar_cpp   ... bench:      76,863 ns/iter (+/- 2,101)
test cpp_benches::bench_exp_f32_vcl          ... bench:      73,446 ns/iter (+/- 1,323)

Насчет округления: rust-lang/portable-simd#421

@NamorNiradnug NamorNiradnug changed the title Bench against vcl Bench against vcl and optimize May 22, 2024
@NamorNiradnug
Copy link
Owner Author

Резальтат запуска на Xeon с AVX512

test bench_acos_f32_scalar                   ... bench:   1,067,405 ns/iter (+/- 7,519)
test bench_acos_f32_vec                      ... bench:      49,532 ns/iter (+/- 834)
test bench_acos_f64_scalar                   ... bench:   1,032,759 ns/iter (+/- 9,801)
test bench_acos_f64_vec                      ... bench:     283,327 ns/iter (+/- 4,551)
test bench_asin_f32_scalar                   ... bench:     791,740 ns/iter (+/- 6,095)
test bench_asin_f32_vec                      ... bench:      49,283 ns/iter (+/- 459)
test bench_asin_f64_scalar                   ... bench:   1,019,836 ns/iter (+/- 9,280)
test bench_asin_f64_vec                      ... bench:     284,408 ns/iter (+/- 4,157)
test bench_atan_f32_scalar                   ... bench:   1,232,271 ns/iter (+/- 12,734)
test bench_atan_f32_vec                      ... bench:      58,121 ns/iter (+/- 373)
test bench_atan_f64_scalar                   ... bench:   1,189,642 ns/iter (+/- 12,827)
test bench_atan_f64_vec                      ... bench:     251,361 ns/iter (+/- 4,611)
test bench_cos_f32_scalar                    ... bench:   1,208,844 ns/iter (+/- 11,201)
test bench_cos_f32_vec                       ... bench:      58,293 ns/iter (+/- 592)
test bench_cos_f64_scalar                    ... bench:   1,785,495 ns/iter (+/- 12,854)
test bench_cos_f64_vec                       ... bench:     173,536 ns/iter (+/- 4,031)
test bench_exp2_f32_scalar                   ... bench:     489,138 ns/iter (+/- 9,573)
test bench_exp2_f32_vec                      ... bench:      51,942 ns/iter (+/- 610)
test bench_exp2_f64_scalar                   ... bench:     666,606 ns/iter (+/- 8,348)
test bench_exp2_f64_vec                      ... bench:     148,554 ns/iter (+/- 997)
test bench_exp_f32_scalar                    ... bench:     508,427 ns/iter (+/- 6,274)
test bench_exp_f32_vec                       ... bench:      52,813 ns/iter (+/- 435)
test bench_exp_f64_scalar                    ... bench:     802,092 ns/iter (+/- 7,343)
test bench_exp_f64_vec                       ... bench:     163,624 ns/iter (+/- 1,622)
test bench_exp_m1_f32_scalar                 ... bench:   1,615,059 ns/iter (+/- 8,437)
test bench_exp_m1_f32_vec                    ... bench:      53,225 ns/iter (+/- 459)
test bench_exp_m1_f64_scalar                 ... bench:   2,209,407 ns/iter (+/- 13,083)
test bench_exp_m1_f64_vec                    ... bench:     163,121 ns/iter (+/- 1,346)
test bench_sin_f32_scalar                    ... bench:   1,212,935 ns/iter (+/- 10,284)
test bench_sin_f32_vec                       ... bench:      58,313 ns/iter (+/- 462)
test bench_sin_f64_scalar                    ... bench:   1,749,372 ns/iter (+/- 14,692)
test bench_sin_f64_vec                       ... bench:     175,742 ns/iter (+/- 1,611)
test bench_tan_f32_scalar                    ... bench:   3,172,600 ns/iter (+/- 20,362)
test bench_tan_f32_vec                       ... bench:      70,783 ns/iter (+/- 888)
test bench_tan_f64_scalar                    ... bench:   2,232,687 ns/iter (+/- 48,871)
test bench_tan_f64_vec                       ... bench:     247,923 ns/iter (+/- 3,174)
test cpp_benches::bench_acos_f32_scalar_cpp  ... bench:   1,075,900 ns/iter (+/- 158,618)
test cpp_benches::bench_acos_f32_vcl         ... bench:      58,259 ns/iter (+/- 630)
test cpp_benches::bench_asin_f32_scalar_cpp  ... bench:     822,842 ns/iter (+/- 15,335)
test cpp_benches::bench_asin_f32_vcl         ... bench:      51,195 ns/iter (+/- 579)
test cpp_benches::bench_atan_f32_scalar_cpp  ... bench:   1,227,402 ns/iter (+/- 12,127)
test cpp_benches::bench_atan_f32_vcl         ... bench:      63,519 ns/iter (+/- 346)
test cpp_benches::bench_cos_f32_scalar_cpp   ... bench:     105,421 ns/iter (+/- 1,292)
test cpp_benches::bench_cos_f32_vcl          ... bench:      67,361 ns/iter (+/- 773)
test cpp_benches::bench_exp2_f32_scalar_cpp  ... bench:     514,486 ns/iter (+/- 10,307)
test cpp_benches::bench_exp2_f32_vcl         ... bench:      52,398 ns/iter (+/- 362)
test cpp_benches::bench_exp_f32_scalar_cpp   ... bench:      77,593 ns/iter (+/- 3,599)
test cpp_benches::bench_exp_f32_vcl          ... bench:      54,452 ns/iter (+/- 344)
test cpp_benches::bench_expm1_f32_scalar_cpp ... bench:   1,633,799 ns/iter (+/- 21,062)
test cpp_benches::bench_expm1_f32_vcl        ... bench:      55,917 ns/iter (+/- 517)
test cpp_benches::bench_sin_f32_scalar_cpp   ... bench:      88,148 ns/iter (+/- 3,850)
test cpp_benches::bench_sin_f32_vcl          ... bench:      67,121 ns/iter (+/- 814)
test cpp_benches::bench_tan_f32_scalar_cpp   ... bench:   3,178,365 ns/iter (+/- 12,572)
test cpp_benches::bench_tan_f32_vcl          ... bench:      94,119 ns/iter (+/- 812)

@NamorNiradnug
Copy link
Owner Author

By the way
Clang doesn't vectorize asinf, acosf and other functions, unlike gcc
So I'm thinking about building VCL benchmarks with clang and libmvec ones with GCC.

@NamorNiradnug
Copy link
Owner Author

NamorNiradnug commented May 23, 2024

Результаты бенчмарка:

f32 vec vcl libmvec scalar
acos 47,271 56,903 121,467 1,065,080
asin 47,028 48,938 100,976 792,760
atan 55,566 63,578 82,685 1,224,171
cos 55,438 66,597 84,910 1,207,744
exp 46,565 48,843 71,973 506,651
exp2 44,422 46,093 95,569 485,562
exp_m1 46,193 50,444 177,432 1,594,801
sin 55,199 63,351 75,944 1,207,848
tan 70,524 93,470 292,641 3,163,882
f64 vec vcl libmvec scalar
acos 283,218 214,012 365,073 1,004,458
asin 282,682 205,258 341,149 1,024,878
atan 245,865 249,790 323,184 1,181,744
cos 141,100 173,473 198,156 1,767,913
exp 128,659 126,103 488,390 801,921
exp2 122,380 117,356 176,752 666,381
exp_m1 128,584 130,586 288,560 2,209,387
sin 193,403 173,342 179,630 1,740,729
tan 244,770 244,165 230,546 2,212,134

А f64 работает медленнее :(.
(разве что синус выглядит как будто локальное падение производительности CPU)

bench_func!($range, $func, $ftype, 64);
};

($range: expr, $func: tt, $ftype: ty, $vecsize: literal) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А в этом макросе нигде не надо black_box?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А в этом макросе нигде не надо black_box?

Тут как раз важно, что оно не black_box, т.к. это ломает оптимизации. Там даже удаление assert-а замедляет бенчмарк.

Наверное можно в black_box засунуть сразу весь слайс типа такого:

let input_data = black_box(data);
assert(input_data.len() == BENCH_POINTS);
...

но кажется это не должно ничего поменять (я попробую).

src/math/f32/trig.rs Show resolved Hide resolved
sin_vals
.sign_combine(self)
.sign_combine(Simd::from_bits(quadrants << 62))
sin_vals.sign_combine(Simd::from_bits(self.to_bits() ^ (quadrants << 62)))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А давай тут коммент, почему quadrants << 62? Либо я туплю, либо неочевидно.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это вытаскивание второго (если считать с младших) бита $q$, он же четность $\left \lfloor \frac{q}{2} \right \rfloor$. Математические комментарии стоит оставлять в коде?..

tests/common/mod.rs Outdated Show resolved Hide resolved
tests/common/mod.rs Outdated Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants