Repository navigation
Replace the _normalization_composition map with a sorted array - #23
samhocevar wants to merge 1 commit into
Conversation
|
@samhocevar, thanks for the patch. I verified the change locally and it looks good to me. Two things I would like changed before merging. 1. The braces produce 961 warnings per TU on clang
For a header-only library that is a lot of noise for every clang user. GCC does not have this warning, which is why CI stayed green. Removing the braces fixes it (I regenerated and confirmed 0 warnings). 2. Please keep
|
| pattern | clang / libc++ | gcc-15 / libstdc++ |
|---|---|---|
| small set of pairs hit repeatedly | 5.78 -> 22.75 ns (3.9x slower) | 8.05 -> 10.36 ns (1.3x slower) |
| all misses | 15.03 -> 12.04 ns (1.25x faster) | 20.96 -> 6.64 ns (3.2x faster) |
| hits scattered over the table | 14.22 -> 12.37 ns (1.15x faster) | 20.23 -> 15.02 ns (1.35x faster) |
Binary search over 961 entries is about 10 dependent loads per lookup and the CPU cannot overlap them, whereas the hash map is 2 loads. So the array only wins once the map's nodes stop fitting in cache, which is what the miss and scattered rows show. Your 7x looks plausible for a miss-heavy microbenchmark on libstdc++, where hashing a std::u32string plus a prime-modulo bucket index is comparatively expensive.
None of this is visible at the API level, though. I benchmarked to_nfc() on both toolchains, with a plain NFD string and with a worst case built from long combining sequences, and the two versions are indistinguishable: run to run variance is around 10 percent, well above any systematic difference. That makes sense since compose_pair() is only reached inside combining sequences and decomposition plus canonical ordering dominate the work.
So no objection on performance grounds. I would just suggest either dropping the 7x from the commit message or stating the environment it was measured in.
Fix the braces and the static and I will merge. Thanks again.
|
Thanks for the through review! Everything makes sense, I will fix all the issues. I redid the benchmarks with some slightly different test code, with GCC 16, Clang 21.1.8, and whatever Visual Studio 2026 currently ships with. I could not reproduce the ×7 speedup with this specific benchmark (I remember it was obtained with GCC 12). Here is what I found; the #define N 0x8000
extern char32_t data[N];
for (uint32_t n = 0; n < N * N; ++n)
{
char32_t a = data[n % N], b = data[n * 17 % N], c;
if (unicode::compose_pair(a, b, c))
volatile auto d = c;
}
|
Using a sorted array with binary search offers the following improvements: - no more runtime heap allocations to initialize the lookup table - the data is static and can be directly mapped from rodata There are no noticeable differences performance-wise, and they would be hard to measure given how highly input-dependent cache behaviour is.
0a1662c to
25debdd
Compare
|
Okay, I force-pushed a commit that fixes all issues and remarks (including the aliasing thing, I would feel bad leaving such a bug outstanding). |
Rebased from #23. Its premise -- that _normalization_composition is a std::unordered_map with std::u32string keys, built before main() -- was overtaken by 9603809, which made it a sorted constexpr array of Composition, so the heap allocations and the start-up cost the patch set out to remove are already gone. What is left of it is still worth having: packing (first, second, composed) into one uint64_t makes an entry 8 bytes instead of 12, and makes the binary search compare one integer rather than build two std::pairs per step. struct Composition goes with it, having lost its last user. 961 entries. A program that calls to_nfc() links 4,096 fewer bytes, and to_nfc() over a 200k character string goes from 18.6 ms to 17.6 ms (best of 7, g++ -O2). Values are unchanged: all 1,114,112 code points dumped through every table and accessor before and after are byte for byte identical.
|
Closing this — not because the idea was wrong, but because the table underneath it got rewritten. Context: 8675d56..160b901 rewrote all of this library's lookup tables, for a downstream user (Culebra) that links a static binary per program and only wants the tables a program actually uses. Before: calling On #23's two goals:
Thank you for your fine contribution! |
Using a sorted array with binary search offers the following improvements: