As noted in the 0.5 benchmark comment, swapping all MVars for TVars in #38 did make the single-stripe solution significantly faster. The benchmark itself is however quite weird, as it assumes incredibly tiny jobs (fib 10), roughly in the range of dozens to hundreds of nanoseconds.
I wrote a more comprehensive benchmark over a fork (link).
For tiny jobs the difference indeed looks substantive:
Benchmark
$ cabal bench resource-pool-bench:bench --benchmark-options='--items=10000000 --item-size=1ns:5ns --stripes=4 +RTS -N4'
[..]
Arguments:
4 workers over 4 capabilities
10000000 items, each between 1ns and 5ns
4 stripes
[..]
Results:
Expected time per item: 3.00ns ± 0.00ns
Extra time spent per item: 0.64us ± 46.26ns
$ cabal bench resource-pool-bench:bench --benchmark-options='--items=10000000 --item-size=1ns:5ns --stripes=1 +RTS -N4'
[..]
Arguments:
4 workers over 4 capabilities
10000000 items, each between 1ns and 5ns
1 stripes
[..]
Results:
Expected time per item: 3.00ns ± 0.00ns
Extra time spent per item: 1.33us ± 17.87ns
Bump runtimes a thousandfold and now the difference is wholly inconsequential:
Benchmark
$ cabal bench resource-pool-bench:bench --benchmark-options='--items=10000 -
-item-size=10us:100us --stripes=4 +RTS -N4'
[..]
Arguments:
4 workers over 4 capabilities
10000 items, each between 10us and 100us
4 stripes
[..]
Results:
Expected time per item: 54.98us ± 94.05ns
Extra time spent per item: 0.65us ± 0.47us
Benchmark bench: FINISH
$ cabal bench resource-pool-bench:bench --benchmark-options='--items=10000 --item-size=10us:100us --stripes=1 +RTS -N4'
[..]
Arguments:
4 workers over 4 capabilities
10000 items, each between 10us and 100us
1 stripes
[..]
Results:
Expected time per item: 54.98us ± 94.05ns
Extra time spent per item: 0.73us ± 87.03ns
And this is the busy-waiting case; if threads get to sleep instead the margins are way narrower (in my case both above are at 0.95ms ± 5.91us and 0.96ms ± 11.94us respectively).
On top of that, the very existence of striping complicates library design substantially:
I think the correct approach would be to remove striping and then optimize the rest of the library. Anyone who still wants striping should be able to create one pool per capability themselves, avoiding the need for whatever magic currently allows this to work across all possible configurations.
As noted in the 0.5 benchmark comment, swapping all
MVars forTVars in #38 did make the single-stripe solution significantly faster. The benchmark itself is however quite weird, as it assumes incredibly tiny jobs (fib 10), roughly in the range of dozens to hundreds of nanoseconds.I wrote a more comprehensive benchmark over a fork (link).
For tiny jobs the difference indeed looks substantive:
Benchmark
Bump runtimes a thousandfold and now the difference is wholly inconsequential:
Benchmark
And this is the busy-waiting case; if threads get to sleep instead the margins are way narrower (in my case both above are at 0.95ms ± 5.91us and 0.96ms ± 11.94us respectively).
On top of that, the very existence of striping complicates library design substantially:
Running the collector thread every second is egregiously inefficient;
"Maximum number of resources" is misleading. There's no guarantee incoming requests will be spread uniformly among all stripes.
I think the correct approach would be to remove striping and then optimize the rest of the library. Anyone who still wants striping should be able to create one pool per capability themselves, avoiding the need for whatever magic currently allows this to work across all possible configurations.