gh-132121: Always escape non-printable characters in pygettext#132122
gh-132121: Always escape non-printable characters in pygettext#132122serhiy-storchaka merged 3 commits intopython:mainfrom
pygettext#132122Conversation
serhiy-storchaka
left a comment
There was a problem hiding this comment.
Could you please measure performance of escape_ascii() for non-ASCII strings of different length (10 to 100000)?
|
This is on a PGO build:
test code: def test_escape_ascii(string):
for _ in range(100):
escape_ascii(string, 'utf-8')
make_escapes(True)
for n in (10, 100, 1_000, 10_000, 100_000):
print(f"{n=}")
string = 'α' * n
print(f"\tonly printable: {timeit.timeit(partial(test_escape_ascii, string), number=10)}")
string = '\302\200' * n
print(f"\tonly non-printable: {timeit.timeit(partial(test_escape_ascii, string), number=10)}")
string = 'α' * ((9*n)//10) + '\302\200' * (n//10)
print(f"\tmix (90% printable, 10% non-printable): {timeit.timeit(partial(test_escape_ascii, string), number=10)}")
string = 'α' * (n//2) + '\302\200' * (n//2)
print(f"\tmix (50% printable, 50% non-printable): {timeit.timeit(partial(test_escape_ascii, string), number=10)}")Strings that only contain non-printable characters are much slower with this PR, though they should be quite uncommon. I also tested mixed strings (50% printable/non-printable and 90%/10% printable/non-printable) where the numbers look more reasonable but maybe there's a way to optimize the function? |
|
Thanks. It is expected that non-printable characters should be slower, but non-ASCII non-printable characters should be extremely rare. I worried about printable characters. How is this in comparison with the current code? The time grow looks linear, but this may be CPython specific optimization, on other implementations it may be quadratic. I am asking because you changed the implementation to use |
|
Ah, I see, you already tested it against main and PR. The difference is insignificant. Could you please test it on PyPy? Just to be sure that the worst case is not so bad. |
Will do, either today or tomorrow :) |
|
On PyPy 3.11 it regresses by quite a bit (I'll only include
This is a pretty big regression. I rewrote the function back to a list comprehension and the numbers look much better:
def escape_ascii(s, encoding):
return ''.join(escapes[ord(c)] if ord(c) < 128 else c
if c.isprintable() else escape_nonascii(c, encoding)
for c in s)I'll update the PR to keep the list comprehension. |
|
Thank you. So there was a difference. BTW, there is an error in your benchmarks for non-printable characters. The changes in |
Ah, you're right, thanks for pointing it out!
This change makes it so that the |
Ah, good point! LGTM. Thank you for your PR. |
|
Thanks for the review! |
Pretty much the title, all non-printable characters are always escaped regardless whether
--escapeis passed or not.--escapeoption #132121