Skip to content

[mypyc] Cache single-character strings up to U+0FFF - #22465

Open
Jahongir-Qurbonov wants to merge 1 commit into
python:masterfrom
Jahongir-Qurbonov:mypyc-str-char-cache
Open

Jahongir-Qurbonov wants to merge 1 commit into
python:masterfrom
Jahongir-Qurbonov:mypyc-str-char-cache

Conversation

@Jahongir-Qurbonov

Copy link
Copy Markdown
Contributor

CPython caches single-character strings only for Latin-1. In compiled code,
for c in s, s[i] and chr(n) therefore allocate and free a new str for every
non-Latin-1 character, and the allocation is most of the cost of the loop.

This adds a cache for single-character strings in U+0100..U+0FFF (Latin Extended,
Greek, Cyrillic, Armenian, Hebrew, Arabic, Indic scripts, Thai, Georgian, ...),
used by str indexing, iteration and chr(). Entries are created on first use and
never evicted. The cache is disabled on free-threaded builds.

Code points above U+0FFF (CJK, emoji) aren't cached. I first tried a 256-entry
direct-mapped cache covering all code points: Cyrillic and emoji text got 2x faster,
but random CJK text got ~35% slower because of evictions. The fixed range has no
eviction, so text outside it isn't affected.

Memory: the table is 30 KB of zero-initialized pointers, plus 60 bytes for each
distinct character actually used (at most ~230 KB if all 3840 are used).

Benchmark

1M-character strings, ms, best of 9, CPython 3.13, Linux x86_64, gcc -O3:

Loop Text Before After
for c in s: n += 1 ASCII 2.9 2.7
Cyrillic 10.8 4.8
random CJK 12.6 12.7
random emoji 12.2 12.5
for c in s: n += ord(c) Cyrillic 13.6 4.9
random CJK 14.0 14.2
for c in s: if c == "x": ... Cyrillic 13.5 4.9
random CJK 13.2 13.4

CJK/emoji differences are within noise.

Testing

  • New run test testStrSingleCharItems checks indexing and iteration across Latin-1,
    the cached range and beyond, with cache hits on a second pass. It also checks that
    c += "x" on a cached character doesn't modify the cached object.
  • The existing test_chr_code_points already checks chr() for every BMP code point.
  • str_ops.c compiles with -Werror against both normal and free-threaded 3.14 headers.

CPython only caches single-character Latin-1 strings, so iterating over or
indexing text in other alphabetic scripts (Greek, Cyrillic, Arabic, Indic
scripts, etc.) allocated and freed a new str object for every character.
In compiled code the allocation dominated the cost of `for c in s`.

Cache single-character strings for U+0100..U+0FFF in a table that is filled
on first use and never evicted, and use it for str indexing, iteration and
chr(). Larger code points such as CJK aren't cached: a small direct-mapped
cache made random CJK text ~35% slower due to evictions.

The cache is disabled on free-threaded builds.
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.

1 participant