Skip to content

Some static table optimisations#21

Open
samhocevar wants to merge 3 commits into
yhirose:masterfrom
samhocevar-forks:patches/table-optims
Open

Some static table optimisations#21
samhocevar wants to merge 3 commits into
yhirose:masterfrom
samhocevar-forks:patches/table-optims

Conversation

@samhocevar

Copy link
Copy Markdown
Contributor

I had a look at the initialisation cost of cpp-unicodelib. It’s not a massive problem, but using unordered_map does cause a lot of allocations and pointer chasing. In this PR there are two examples of possible improvements:

  • replace short strings with packed integers, when the maximum string length is known for sure.
  • replace unordered_map with a sorted array and use binary search for lookups.

If you think this is the right direction, I can finish the work with all other tables and ensure that cpp-unicodelib does no heap allocation for its data.

There are also ways to make the data structures extremely efficient, using e.g. perfect hashing, but it would add complexity to the Python side of table generation. The Frozen library provides a drop-in replacement for unordered_map that is constexpr, but then again I suppose it is not desirable to add such a dependency. Fascinating project, though.

Since all keys in the map are two-character strings, it makes sense to pack the two
char32_t values into a uint64_t:

- faster key hashes and comparisons.

- smaller key objects (allocated heap space goes from 137kiB to 92kiB on MSVC x64).

- in the (unlikely) case that short string optimisation is not available, avoids
  heap allocations for the string data.
Memory profiling indicates that initialising this map used 315kiB of heap memory,
distributed through 5,982 allocations. Switching to a sorted static array allows
the data to be directly mapped from .rodata, requiring no allocations.

It can be a minor runtime performance tradeoff because std::lower_bound uses binary
search, whereas unordered_map uses hashing then linear lookup through a linked list.
There is no clear winner here.
…dered_map

There was no particular reason to use a multimap for this data.
@yhirose

yhirose commented Jul 22, 2026

Copy link
Copy Markdown
Owner

@samhocevar Thanks for the PR! Could you measure how much memory and performance improvement this approach can achieve?

@samhocevar

Copy link
Copy Markdown
Contributor Author

I will try to make some meaningful measurements. In the meantime, I found a more obvious problem with the initialisation of these variables, addressed in #22.

@samhocevar

Copy link
Copy Markdown
Contributor Author

For future reference, here is already a breakdown of allocation count and total heap space for the initialisation of the constant maps and vectors. This is for MSVC on Windows x64, but Clang for x64 has pretty similar numbers.

Variable heap allocs heap bytes
_simple_case_mappings 2990 161223
_case_foldings 1586 108887
_normalization_composition 963 70295
_script_extension_properties_for_id 119 4976
_special_case_mappings_default 121 16871
_special_case_mappings 18 2112
Total 5797 364364

@yhirose

yhirose commented Jul 23, 2026

Copy link
Copy Markdown
Owner

@samhocevar thanks for this. I have merged #22 first, it made a conflict with this PR. Could you please resolve it? Sorry about that.

@samhocevar

Copy link
Copy Markdown
Contributor Author

I think I will restart this PR from scratch and the changes will be rather different. Shall we keep it open for discussion or do you want to close it?

@yhirose

yhirose commented Jul 24, 2026

Copy link
Copy Markdown
Owner

@samhocevar if you want to restart, I don't mind closing it and you can open another PR.

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