Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Accurate and efficient software microbenchmarks

Accurate and efficient software microbenchmarks

Software is often improved incrementally. Each software optimization should be assessed with microbenchmarks. In a microbenchmark, we record performance measures such as elapsed time or instruction counts during specific tasks, often in idealized conditions. In principle, the process is easy: if the new code is faster, we adopt it. Unfortunately, there are many pitfalls, such as unrealistic statistical assumptions and poorly designed benchmarks. Abstractions like cloud computing add further challenges. We illustrate effective benchmarking practices with examples.

Daniel Lemire

April 06, 2023
Tweet

More Decks by Daniel Lemire

Other Decks in Technology

Transcript

  1. Accurate and efficient software microbenchmarks Daniel Lemire professor, Data Science

    Research Center Université du Québec (TÉLUQ) Montreal blog: https://lemire.me twitter: @lemire GitHub: https://github.com/lemire/
  2. Background Fastest JSON parser in the world (on commodity processors):

    https://github.com/simdjson/simdjson First to parse JSON files at gigabytes per second
  3. Where is the code ? All code for this talk

    is online (reproducible!!!) https://github.com/lemire/talks/tree/master/2023/performance/code
  4. How fast is your disk? PCIe 4 drives: 5 GB/s

    reading speed (sequential) PCIe 5 drives: 10 GB/s reading speed (sequential)
  5. System calls System calls (especially IO) may dominate, assume that

    they remain constant. Idem with multicore and multi-system processes.
  6. Tiny functions Uncertainty principle: by measuring you are affecting the

    execution so that you cannot measure safely tiny functions.
  7. Take statically compiled code Transcoding UTF-16 to UTF-8 of an

    80kB Arabic string using the simdutf library (NEON kernel).
  8. Use the average? Let be the true value and let

    be the noise distribution (variance ). We seek .
  9. Repeated measures increase accuracy Measures are Sum is . Variance

    is . Average is . Variance is . Standard deviation of .
  10. Simulation mu, sigma = 10000, 5000 for N in range(20,

    2000+1): s = [sum(np.random.default_rng().normal(mu, sigma, N))/N for i in range(30)] print(N,np.std(s))
  11. Actual measurements // returns the average double transcode(const std::string& source,

    size_t iterations); ... for(size_t i = iterations_start; i <= iterations_end; i+=step) { std::vector<double> averages; for(size_t j = 0; j < 30; j++) { averages.push_back(transcode(source, i)); } std::cout << i << "\t" << compute_std_dev(averages) << std::endl; }
  12. 1-sigma is 32% 2-sigma is 5% 3-sigma is 0.3% (once

    ever 300 trials) 4-sigma is 0.00669% (once every 15000 trials) 5-sigma is 5.9e-05% (once every 1,700,000 trials) 6-sigma is 2e-07% (once every 500,000,000) for
  13. Measuring sigma events Take 300 measures after warmup, and measure

    the worst relative deviation $ for i in {1..10}; do sudo ./sigma_test; done 4.56151 4.904 7.43446 5.73425 9.89544 12.975 3.92584 3.14633 4.91766 5.3699
  14. What if we measured the minimum? Relative standard deviation (

    ) N average minimum 200 3.44% 1.38% 2000 2.66% 1.19% 10000 2.95% 1.27%
  15. CPU performance counters Processors have zero-overhead counters recording instruction retired,

    actual cycles, and so forth. No need to freeze the CPU frequency: you can measure it.
  16. Limitations You can only measure so many things (2, 4

    metrics, not 25) Required privileged access (e.g., root)
  17. Counters in the cloud x64: Requires at least a full

    CPU ARM Graviton: generally available but limited number (e.g., 2 counters)
  18. Generally, fewer instructions means faster code Some instructions are more

    expensive than others (e.g., division). Data dependency can make instruction counts less relevant. Branching can artificially lower instruction count.
  19. If you are adding speculative branching, make sure your test

    input is large. while (howmany != 0) { val = random(); if( val is an odd integer ) { out[index] = val; index += 1; } howmany--; }
  20. 2000 'random' elements, AMD Rome trial mispredicted branches 1 50%

    2 18% 3 6% 4 2% 5 1% 6 0.3% 7 0.15% 8 0.15%
  21. Take away 2 Benchmarking often is good Long-running benchmarks are

    not necessarily more accurate. Prefer cheap, well-designed benchmarks.