[libcxx-commits] [libcxx] [libc++][pstl] Add serial implementations of std::inclusive_scan and std::transform_inclusive_scan (PR #236219)

Michael G. Kazakov via libcxx-commits libcxx-commits at lists.llvm.org
Fri Oct 30 03:31:05 PDT 2026


mikekazakov wrote:

I've ran some benchmarks to compare 1-pass and 3-pass approaches on two parallel algorithms, the branch is available here: https://github.com/mikekazakov/llvm-project/tree/compare_gh195548.

```
std::transform_inclusive_scan (test/benchmarks/numeric/pstl.transform_inclusive_scan.bench.cpp):
1-pass
std::transform_inclusive_scan(std::execution::par, vector<double>) (minimal)/64/real_time               1188 ns         1187 ns       223868	1.17x
std::transform_inclusive_scan(std::execution::par, vector<double>) (minimal)/65536/real_time           58880 ns        58589 ns         4691	0.98x
std::transform_inclusive_scan(std::execution::par, vector<double>) (minimal)/1048576/real_time        436580 ns       369797 ns         1135	1.49x
std::transform_inclusive_scan(std::execution::par, vector<double>) (minimal)/67108864/real_time      6647804 ns      5368902 ns           41	1.28x
std::transform_inclusive_scan(std::execution::par, vector<double>) (cheap)/64/real_time                 2193 ns         1313 ns       100000	0.64x
std::transform_inclusive_scan(std::execution::par, vector<double>) (cheap)/65536/real_time            211928 ns       208055 ns         1301	1.18x
std::transform_inclusive_scan(std::execution::par, vector<double>) (cheap)/1048576/real_time          543795 ns       513778 ns          519	1.47x
std::transform_inclusive_scan(std::execution::par, vector<double>) (cheap)/67108864/real_time       23206383 ns     21313727 ns           11	0.99x
std::transform_inclusive_scan(std::execution::par, vector<double>) (expensive)/64/real_time             1348 ns         1348 ns       192789	1.09x
std::transform_inclusive_scan(std::execution::par, vector<double>) (expensive)/65536/real_time        212062 ns       202377 ns         1183	2.45x
std::transform_inclusive_scan(std::execution::par, vector<double>) (expensive)/1048576/real_time     1193322 ns      1098987 ns          232	1.19x
std::transform_inclusive_scan(std::execution::par, vector<double>) (expensive)/67108864/real_time   68030750 ns     63773500 ns            4	1.05x
3-pass
std::transform_inclusive_scan(std::execution::par, vector<double>) (minimal)/64/real_time               1397 ns         1396 ns       195545
std::transform_inclusive_scan(std::execution::par, vector<double>) (minimal)/65536/real_time           57998 ns        57917 ns         4766
std::transform_inclusive_scan(std::execution::par, vector<double>) (minimal)/1048576/real_time        650867 ns       560978 ns          581
std::transform_inclusive_scan(std::execution::par, vector<double>) (minimal)/67108864/real_time      8512934 ns      6684756 ns           41
std::transform_inclusive_scan(std::execution::par, vector<double>) (cheap)/64/real_time                 1408 ns         1407 ns       195973
std::transform_inclusive_scan(std::execution::par, vector<double>) (cheap)/65536/real_time            251667 ns       248808 ns         1108
std::transform_inclusive_scan(std::execution::par, vector<double>) (cheap)/1048576/real_time          801594 ns       776860 ns          342
std::transform_inclusive_scan(std::execution::par, vector<double>) (cheap)/67108864/real_time       23117691 ns     20835083 ns           12
std::transform_inclusive_scan(std::execution::par, vector<double>) (expensive)/64/real_time             1470 ns         1465 ns       192185
std::transform_inclusive_scan(std::execution::par, vector<double>) (expensive)/65536/real_time        521367 ns       512009 ns          541
std::transform_inclusive_scan(std::execution::par, vector<double>) (expensive)/1048576/real_time     1427313 ns      1318365 ns          189
std::transform_inclusive_scan(std::execution::par, vector<double>) (expensive)/67108864/real_time   71749656 ns     59881750 ns            4

std::copy_if (libcxx/test/benchmarks/algorithms/modifying/pstl.copy_if.bench.cpp):
1-pass
std::copy_if(std::execution::par, vector<double>) (minimal)/64/real_time               1543 ns         1542 ns       175055		1.19x
std::copy_if(std::execution::par, vector<double>) (minimal)/65536/real_time          152085 ns       139928 ns         1853		1.24x
std::copy_if(std::execution::par, vector<double>) (minimal)/1048576/real_time        740612 ns       551550 ns          467		1.96x
std::copy_if(std::execution::par, vector<double>) (minimal)/67108864/real_time     19972413 ns     17199833 ns           12		1.12x
std::copy_if(std::execution::par, vector<double>) (cheap)/64/real_time                 1596 ns         1595 ns       170396		1.17x
std::copy_if(std::execution::par, vector<double>) (cheap)/65536/real_time            154763 ns       151312 ns         1809		1.26x
std::copy_if(std::execution::par, vector<double>) (cheap)/1048576/real_time          461890 ns       429821 ns          593		2.02x
std::copy_if(std::execution::par, vector<double>) (cheap)/67108864/real_time       19696825 ns     16477200 ns           15		1.05x
std::copy_if(std::execution::par, vector<double>) (expensive)/64/real_time             1752 ns         1751 ns       153632		1.12x
std::copy_if(std::execution::par, vector<double>) (expensive)/65536/real_time        160312 ns       155406 ns         1784		1.25x
std::copy_if(std::execution::par, vector<double>) (expensive)/1048576/real_time      487009 ns       451649 ns          562		1.80x
std::copy_if(std::execution::par, vector<double>) (expensive)/67108864/real_time   18559486 ns     16851867 ns           15		1.16x
3-pass
std::copy_if(std::execution::par, vector<double>) (minimal)/64/real_time               1843 ns         1842 ns       147303
std::copy_if(std::execution::par, vector<double>) (minimal)/65536/real_time          189064 ns       186726 ns         1477
std::copy_if(std::execution::par, vector<double>) (minimal)/1048576/real_time       1457788 ns       941429 ns          280
std::copy_if(std::execution::par, vector<double>) (minimal)/67108864/real_time     22398447 ns     17145364 ns           11
std::copy_if(std::execution::par, vector<double>) (cheap)/64/real_time                 1871 ns         1869 ns       152447
std::copy_if(std::execution::par, vector<double>) (cheap)/65536/real_time            195420 ns       192982 ns         1343
std::copy_if(std::execution::par, vector<double>) (cheap)/1048576/real_time          934680 ns       900108 ns          296
std::copy_if(std::execution::par, vector<double>) (cheap)/67108864/real_time       20809951 ns     14340167 ns           12
std::copy_if(std::execution::par, vector<double>) (expensive)/64/real_time             1973 ns         1967 ns       142532
std::copy_if(std::execution::par, vector<double>) (expensive)/65536/real_time        201738 ns       198699 ns         1345
std::copy_if(std::execution::par, vector<double>) (expensive)/1048576/real_time      879643 ns       846429 ns          333
std::copy_if(std::execution::par, vector<double>) (expensive)/67108864/real_time   21548817 ns     12768231 ns           13
```

On my hardware (AppleM5P) the single-pass approach is ~1.1-1.2x faster on average, though it's very dependent on the operations and the partition sizes.

https://github.com/llvm/llvm-project/pull/236219


More information about the libcxx-commits mailing list