| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
| // from being immediately detected as cold and invalidated. | ||
| cold->vm_data.warm = true; | ||
| if (_PyJIT_Compile(cold, cold->trace, 1)) { | ||
| Py_DECREF(cold); |
There was a problem hiding this comment.
It's immortal. Decrefing won't do anything. I think you mean to call the dealloc function on it?
Sorry, something went wrong.
There was a problem hiding this comment.
Good spot. I think I need to move making it immortal to after it is fully created.
Sorry, something went wrong.
|
@pablogsal any ideas what this failure means? https://github.com/python/cpython/actions/runs/16140914160/job/45547999926?pr=136411 |
Sorry, something went wrong.
Is a race condition in the tests that will be fixed by #136347 |
Sorry, something went wrong.
|
Performance is in the noise:
However, I wouldn't expect much of a speedup by reducing the size of exits, so this seems reasonable. |
Sorry, something went wrong.
|
I think the benchmarking public mirror is down. Can you please share the link when it comes up again? |
Sorry, something went wrong.
|
Note to self: The cold exit executor should be freed when freeing the interpreter to avoid leaking memory. |
Sorry, something went wrong.
| executor->exits[i].executor = NULL; | ||
| executor->exits[i].index = i; | ||
| executor->exits[i].temperature = initial_temperature_backoff_counter(); | ||
| executor->exits[i].executor = cold; |
There was a problem hiding this comment.
We put cold_executor here without INCREF because cold_executor is immortal. But in executor_clear we DECREF each executor from exits because there can be other executors as well. But this is imbalance of INCREF/DECREF. Maybe it is worth to add a comment here that we omit INCREF because of immortality of cold executor?
Sorry, something went wrong.
There was a problem hiding this comment.
A couple of notes. I like the general idea, not a huge fan of the jit_exit side-channel (but I get why we have it).
Sorry, something went wrong.
| Py_FatalError("Cannot allocate core JIT code"); | ||
| } | ||
| #endif | ||
| _Py_SetImmortal((PyObject *)cold); |
There was a problem hiding this comment.
Why does it need to be immortal? This means that we'll leak one of these per interpreter, along with about a page of JIT code. I think the interpreter can just hold a normal reference that we free at shutdown, right?
Sorry, something went wrong.
There was a problem hiding this comment.
It is cleared when the interpreter is freed https://github.com/python/cpython/pull/136411/files#diff-7ac11e526f79b42d6ea9d3592cb99da46775640c69fa5510f4a6de87cced7141R818
Sorry, something went wrong.
There was a problem hiding this comment.
Then why is it immortal? I'm worried it could get shared at some point, which is generally safe to do with immortal objects. An immortal object that gets cleared at interpreter shutdown seems like it could lead to hard-to-debug problems down the road.
Maybe just not make it immortal, and refcount it normally?
Sorry, something went wrong.
There was a problem hiding this comment.
It is immortal: it outlives all mortal objects in the same interpreter. Reference counting it is just extra overhead.
Sorry, something went wrong.
| } | ||
| exit->temperature = initial_temperature_backoff_counter(); | ||
| } | ||
| assert(tstate->jit_exit == exit); |
There was a problem hiding this comment.
Maybe set it to NULL now?
Sorry, something went wrong.
There was a problem hiding this comment.
We could do, but I'd rather leave it pointing to the last exit.
Cold exit relies on jit_exit being the last exit, and I can imagine us adding "compile me now" stubs later that would also rely on it.
Sorry, something went wrong.
There was a problem hiding this comment.
Not a big deal, but I'd lean towards clearing it here. A possibly-dangling pointer seems dangerous, and we could assert it's NULL when setting it to make sure we didn't mess up somewhere.
Sorry, something went wrong.
There was a problem hiding this comment.
It is only meaningful when starting an executor, and it will always have been set to a live executor at that point.
I'd like to keep it this way as it will simplify changing it to be passed in a register with TOS caching.
Sorry, something went wrong.
|
When you're done making the requested changes, leave the comment: I have made the requested changes; please review again. |
Sorry, something went wrong.
Once we have TOS caching we can pass the exit in one of the cache registers. On occasion we will need to spill a value on the stack, but it should be mostly free. |
Sorry, something went wrong.
|
With the changes to _START_EXECUTOR to check for invalidation, _START_EXECUTOR has grown from 0 to 62 bytes (x86-64) which is still pretty good given the savings of 350 bytes per _EXIT_TRACE. |
Sorry, something went wrong.
There was a problem hiding this comment.
A couple suggestions for possible improvements (and in the discussions above), but nothing blocking. This is a good change, thanks!
Sorry, something went wrong.
| [_START_EXECUTOR] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, | ||
| [_MAKE_WARM] = 0, | ||
| [_FATAL_ERROR] = 0, | ||
| [_DEOPT] = 0, | ||
| [_ERROR_POP_N] = HAS_ARG_FLAG, | ||
| [_TIER2_RESUME_CHECK] = HAS_DEOPT_FLAG, | ||
| [_COLD_EXIT] = HAS_ESCAPES_FLAG, |
There was a problem hiding this comment.
Can you mark _PyExecutor_ClearExit and _PyExecutor_FromExit as non-escaping?
...also, am I the only one around here who reviews generated code? ;)
Sorry, something went wrong.
There was a problem hiding this comment.
Ah wait, _PyExecutor_ClearExit can escape.
That's annoying... the traces are going to have a _CHECK_VALIDITY op after the _START_EXECUTOR anyways, because the deopt path in _START_EXECUTOR can escape. But it will never actually reach the next instruction. Seems like something that should be addressed, actually.
Maybe just go back to checking in _EXIT_TRACE like we were before? It's going to get checked anyways, so might as well just do it there.
Sorry, something went wrong.
There was a problem hiding this comment.
...also, am I the only one around here who reviews generated code? ;)
Maybe 🙂
Sorry, something went wrong.
There was a problem hiding this comment.
Checking validity in _START_EXECUTOR is much more efficient than in _EXIT_TRACE as it is only a single predictable branch.
Sorry, something went wrong.
| _PyExecutorObject *e = executor->exits[i].executor; | ||
| executor->exits[i].executor = cold; | ||
| Py_DECREF(e); |
There was a problem hiding this comment.
Could use _PyExecutor_ClearExit here to avoid repeating the same logic twice.
Sorry, something went wrong.
There was a problem hiding this comment.
But that couples the two functions, and they do have distinct uses.
Sorry, something went wrong.
It can't really escape, because it is on the deopt path. But that's a separate issue: #137276 |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
This PR reinstates the _COLD_EXIT uop, but this time expects the exit to be passed, not the executor.
This way we only need one jitted stub, not hundreds.
Using stubs hugely simplifies _EXIT_TRACE as all it needs to do is jump to the exit's executor.
The x86-64 stencil for _EXIT_TRACE shrinks from 384 bytes to 36 bytes, although it does require one extra _CHECK_VALIDITY (19 bytes) to be added to each trace.
Since traces often contain multiple _EXIT_TRACEs this is a substantial space saving.