-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
bpo-42834: Make static cache variables in _json.c compatible with subinterpreters #24121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
vstinner
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you consider to use _Py_IDENTIFIER() API? It is compatible with subinterpreters and it provides interned strings. _PyUnicode_FromId() can fail with memory allocation error (at the first call, when the string is created). There are "Id" variant of many functions.
No that didn't cross my mind due to my inexperience with the internal C API. Thank you for taking the time to review this and suggesting it to me! Please give me a week to address your suggestions as I'm currently quite busy. Thanks! |
|
Sorry, it seems that I was wrong, my personal computer isn't producing consistent pyperformance results, so I don't see any real slowdown/speedup. |
|
@vstinner I think this should be ready now. Pyperformance on windows (this isn't really accurate, but it's something at least) says: If it gets merged, I'll monitor speed.python.org for awhile and see if anything crops up. Thanks for your patience and help in all of this! |
|
@vstinner I was finally able to test it on a linux machine (btw, json_dumps is slightly unstable on my machine and pyperformance sometimes complains, only json_loads is stable): on this branch: Seems like performance isn't affected :). Please take a look when you have the time for it . Thank you! |
| /* Use JSONDecodeError exception to raise a nice looking ValueError subclass */ | ||
| static PyObject *JSONDecodeError = NULL; | ||
| PyObject *exc; | ||
| _Py_static_string(PyId_decoder, "json.decoder"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need PyId_decoder and PyId_JSONDecodeError micro-optimization? Decoding an ASCII string is fast in Python. Why not keeping PyImport_ImportModule & PyObject_GetAttrString?
I don't care much about the performance of the error path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that performance on error path is usually not important. However, I think json may be a little special - many people use it to load from sources that may provide invalid json data, this is pretty common:
try:
val = json.loads(...)
except json.decoder.JSONDecodeError:
pass
else:
...So raising error is quite a common operation. Using PyId here surprisingly seems to speed up error raising by around 20%.
Misc/NEWS.d/next/Library/2021-01-05-23-55-24.bpo-42834.LxRnZC.rst
Outdated
Show resolved
Hide resolved
What is your benchmark showing a 20% difference? |
It's a microbenchmark, so I don't see it as 100% representative of a real-world scenario: pyperf timeit "import json" "try: json.loads('{dkfjdkjf')
except: pass" Edit: Honestly I'm quite surprised. I did not expect the static string interning to save that much time. I expected more of the call overhead to be from Edit2: Wow, on master where exception is cached, it's only slightly faster than pyid with no cached exception version: |
|
Can you please compare using the current master branch as a reference?
It seems like you import json at each iteration. Use |
Wow! I didn't know that. I assumed that the first argument is automatically considered setup code without having to specify
I did:
Edit: 1us -> 0.1us |
|
Merged, thanks. Well, since the code already uses static strings for the "hot code" (true/false), it's ok to "optimize" the error path (raise an exception). |
|
Thanks for your patience and guidance on this PR Victor! I have waaaay more respect for the all the pyperf/pyperformance people now - benchmarks are pretty hard 😆. |
I don't understand what you mean. If you don't use CPU isolation, you should run benchmarks on an idle machine (don't run other programs in parallel). You should not pick which numbers look better to you, but accumulate more data. Use --append option rather than -o/--output when you create JSON files. Using pyperf, you can run a benchmark many times and accumulate more and more runs, pyperf computes the mean and std dev. Then you can have fun with stats and hist commands ;-) |
Thanks, TIL something new again about append.
Sorry I wasn't clear here. What I meant was: I did that to make sure that all the tuning was working, not cherry picking data on purpose. Because on Windows, when I ran pyperf, there was actually still quite a lot of noise between each run (>1us! because tuning didn't really work!). So this just meant: my machine was quite stable. On the linux benchmarking machine I didn't have any other user programs running (well other than the desktop environment and terminal :p). |
|
Point taken about accumulating data though - I never knew such a useful feature existed. If I had known I'd definitely use it way more. Thanks! |
|
Oh, I only run benchmarks on Linux. Someone should write a documentation explaining how to run reproducible benchmarks on Windows. pyperf changes the process priority if it has the required dependency and the permissions. But I don't recall the details. |
Yeap. After a while I gave up and just installed Linux 😅. I don't know if there's an easy way for pyperf to disable turbo boost properly on Windows, and some of the other tuning options which affect accuracy too can't be easily changed. So I just installed Linux on a real computer and ran all the benchmarks you see there. Edit: Just to clarify: |
This patch helps isolate static caches in _json.c, and may help with decreasing refleaks at finalization too.
No measured performance slowdowns in json_loads and json_dumps in pyperformance with this patch. There is a measurable slowdown in error raising of around 8%, but this isn't usually very hot code.
https://bugs.python.org/issue42834