GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — gh-149584: Fix excessive overhead in the Tachyon profiler regarding the cache behavior by pablogsal · Pull Request #149649 · python/cpython Conversation
|
| Step | Work rate | Sample rate | Avg call time | Incremental win |
|---|---|---|---|---|
upstream/main baseline |
382,593/s | 363,963/s | 2.614 us | - |
| Exact reads for hot structs | 549,741/s | 514,642/s | 1.819 us | +167,148/s (+43.7%) |
| Track live page-cache entries | 747,946/s | 688,282/s | 1.337 us | +198,205/s (+36.1%) |
| Batch predicted remote reads | 897,703/s | 806,084/s | 1.114 us | +149,756/s (+20.0%) |
| Reuse profiler result objects | 1,187,314/s | 1,047,698/s | 0.842 us | +289,611/s (+32.3%) |
Final benchmark using the benchmark script:
| Metric | Baseline | Final | Change |
|---|---|---|---|
| Work rate | 382,593/s | 1,180,434/s | 3.08x faster |
| Sample rate | 363,963/s | 1,038,965/s | 2.85x faster |
| Avg call time | 2.614 us | 0.85 us | -67.5% |
| Success rate | 99.67% | 99.84% | +0.17 pp |
Final benchmark output:
Average call time: 0.85 us
Work rate: 1,180,434.2 calls/sec
Sample rate: 1,038,964.9 samples/sec
Success rate: 99.84%
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Use exact remote reads for interpreter state, thread state, and interpreter frame structs instead of pulling full remote pages into the profiler page cache. This matches the core change from python#149585.
The profiler clears the page cache between samples, so live entries are always packed at the front. Track the live count and only clear/search that prefix instead of scanning all 1024 slots on the hot path.
|
@maurycy do you mind reviewing this PR? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Use the frame cache to predict the next thread state and top frame address, then batch interpreter/thread/frame reads with process_vm_readv when profiling a Linux target. Reuse prefetched frame buffers in the frame walker when the prediction is valid.
Cache the last FrameInfo tuple per code object/instruction offset, reuse cached thread id objects, and append cached parent frames directly on full frame-cache hits. This cuts Python allocation churn in the steady-state profiler path.
There was an error while loading. Please reload this page.
|
@pablogsal Awesome job, I'm delighted :) Definitely will do! Perhaps it's worth an entry in the "Optimizations" section? One thing that I'm wondering immediately is extending |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Added async benchmark modes so the benchmark harness can exercise |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Since is beta1 I think we are good for now. Later makes sense |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Can you give it another go? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
@pablogsal Left two more comments:
(If they do not show, you have to check the resolved threads; the comments are follow-ups.) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Will take a look in 1h |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
@pablogsal One last thing (in the current round): after v. before numbers for the newly added async benchmarks in the description, to see the gain and for future reference (for |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I'm traveling so I don't have access to the same box where I run the original ones so I will need to do these in a different one :) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Thank you. Happy PyCon! :) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@maurycy addressed the comments |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| InterpreterThreadCacheEntry *entry = | ||
| &self->cached_tstates[interpreter_thread_cache_index(interpreter_addr)]; | ||
| entry->interpreter_addr = interpreter_addr; | ||
| entry->thread_state_addr = thread_state_addr; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Given:
typedef struct { uintptr_t interpreter_addr; uintptr_t thread_state_addr; uint64_t code_object_generation; } InterpreterThreadCacheEntry;
Can code_object_generation become stale here?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| self->cached_generation_interpreter_addr = interpreter_addr; | ||
| self->cached_code_object_generation = code_object_generation; |
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Given:
typedef struct { uintptr_t interpreter_addr; uintptr_t thread_state_addr; uint64_t code_object_generation; } InterpreterThreadCacheEntry;
Can thread_state_addr become stale here?
I've added these two "mirror" comments because I'm puzzled about the lifecycle of InterpreterThreadCacheEntry.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Oh sorry, let me push a fix. The intended design is that each writer fully owns its cache slot: the same pattern the other shortcut already uses, where tstate and generation each have their own (interpreter_addr, value) pair. The L2 table joins both values under one slot, which is what makes the lifecycle hard to reason about. I'll split InterpreterThreadCacheEntry into two single-purpose tables, InterpreterTstateCacheEntryand InterpreterGenerationCacheEntry, so the L2 matches the L1 design and each writer fully owns its table.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| PyObject *last_frame_info; | ||
| ptrdiff_t last_addrq; | ||
| uintptr_t addr_code_adaptive; | ||
| } CachedCodeMetadata; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
But this is also per interpreter, or not?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
This is notactually per-interpreter: code_object_cache is a single hashtable on the unwinder, keyed by the remote code object's address. It's shared across all interpreters in the target process.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
@pablogsal Thank you. 🙏🏼 I've left three comments. The complexity gets real. 😞 Bump about the new benchmark numbers. Random idea is improving test coverage with many interpreters, similarly to: cpython/Lib/test/test_external_inspection.py Lines 3381 to 3452 in a8e3247
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
I won't have access to my box until next week :(
I don't think this helps a lot. I tried out of curiosity and this test passes without any of the bugfixes we have added. We can try to add more tests in a separate PR if you want :) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Measured in a different machine:
|
| Metric | Baseline | Final | Change |
|---|---|---|---|
| Work rate | 101,117/s | 160,804/s | 1.59x faster |
| Sample rate | 97,857/s | 153,686/s | 1.57x faster |
| Avg call time | 9.89 µs | 6.22 µs | -37.1% |
| Success rate | 98.93% | 98.90% | -0.03 pp |
get_all_awaited_by() — baseline vs final| Metric | Baseline | Final | Change |
|---|---|---|---|
| Work rate | 19,241/s | 22,103/s | 1.15x faster |
| Sample rate | 19,157/s | 21,973/s | 1.15x faster |
| Avg call time | 51.97 µs | 45.24 µs | -12.9% |
| Success rate | 100.00% | 100.00% | flat |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
@pablogsal Thank you. It's delightful work 🔥, and I appreciate the opportunity. I approve!
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
Thanks @pablogsal for the PR 🌮🎉.. I'm working now to backport this PR to: 3.15. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
GH-150152 is a backport of this pull request to the 3.15 branch. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Thanks for the great review @maurycy. The code is much better thanks to you 🖤 |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.