You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Performance penalty for builtin/extension functions due to interrupt checks #19829
I was profiling an extension that's intended to act as a faster replacement for the pack() and unpack() functions. The functions in the extension are very fast - just a few instructions each.
During profiling I discovered, much to my surprise, that the overhead of the DO_FCALL opcode far exceeded the time actually spent in my function, and that 2/3rds of the overhead was coming from ZEND_VM_FCALL_INTERRUPT_CHECK.
Doing some digging I found that this interrupt check runs after every single internal function call. This means that my extension's functions are being overshadowed by interrupt checks on every call which are >5x slower than the actual function itself.
The net result overall is less visible (a 5-10% performance degradation in my synthetic test script), but I have to ask if there's a better way to do this. Why is PHP checking for interrupts after every internal function call? I get it for stuff like network and I/O functions which could take a long time, but not every builtin function is like that. For the fast ones, this is an unavoidable and (probably) unnecessary performance penalty.
I don't know much about how the interrupt system works, but my feeling is that there has to be a better way to implement it than having every internal function call pay such a penalty. Even if there was a way to avoid this interrupt check with some ZEND_ACC flag that would tell the engine a function is "fast" and not to do the interrupt check?
Zend/bench.php doesn't show a significant difference in most tests, but I think largely because it doesn't really do much internal function calling. I didn't run the other benchmarks because my env doesn't have the right extensions.
FWIW I don't think killing the interrupt checks completely is the right move, but I do think it could probably be polled less often.
I mean to remove them temporary to verify the impact on realworld userland app. If you know how to remove them, then you can push the php-src code to GH and GHA will output the benchmark numbers.
Interrupts are checked quite often, including before entering userland function (via ZEND_VM_ENTER_EX()), after returning from internal functions (via explicit check), in smart branches, loops, and some jumps (via ZEND_VM_SET_OPCODE()), etc. Most of these (including internal calls) were added in 5c68836.
As @mvorisek has suggested, it would be interesting to verify the impact of interrupts by disabling them temporarily. If they are indeed the source of the overhead, a possible workaround would be to declare the function as frameless, as frameless calls do not check interrupts. This may be beneficial on its own, too.
If they are indeed the source of the overhead, a possible workaround would be to declare the function as frameless, as frameless calls do not check interrupts. This may be beneficial on its own, too.
I had looked into this, but the documentation on frameless functions is extremely sparse. Also, as far as I know, they have to be declared differently from regular functions, which would be a pain in my case as I still need to support PHP <8.4.
Benchmark results are as follows. The results look a bit underwhelming, but I will say that I don't think the instruction counts paint a full picture though.
All the use-cases of interrupts need to be accounted for if you intend to remove it. The main cases I know about are:
The PHP execution timer was previously done in a signal handler and this caused crashes because most code is not async-signal-safe. This is likely the primary reason vm interrupts were added.
Sampling profilers, probably other observability tools as well. Possibly debuggers, I'd definitely ask @derickr and @bwoebi.
Theoretically, task schedulers. The interrupt points are generally designed so that you can jump away, but not for internal functions since PHP 8.4 I think? I made this change, I just don't remember which version. Dmitry has mentioned this many times when I was working on this space, but I am not personally aware of any extensions which actually use it.
Activity
ndossche commentedon Sep 13, 2025
This interupt check was added without much dicussion way back (before my time) IIRC
See also Dmitry's comment on a related pr #14627 (comment)
mvorisek commentedon Sep 14, 2025
With all interrupt checks removed, what is the performance improvement on the performance benchmarks?
dktapps commentedon Sep 14, 2025
Zend/bench.php doesn't show a significant difference in most tests, but I think largely because it doesn't really do much internal function calling. I didn't run the other benchmarks because my env doesn't have the right extensions.
FWIW I don't think killing the interrupt checks completely is the right move, but I do think it could probably be polled less often.
mvorisek commentedon Sep 14, 2025
I mean to remove them temporary to verify the impact on realworld userland app. If you know how to remove them, then you can push the php-src code to GH and GHA will output the benchmark numbers.
arnaud-lb commentedon Sep 15, 2025
Interrupts are checked quite often, including before entering userland function (via
ZEND_VM_ENTER_EX()), after returning from internal functions (via explicit check), in smart branches, loops, and some jumps (viaZEND_VM_SET_OPCODE()), etc. Most of these (including internal calls) were added in 5c68836.As @mvorisek has suggested, it would be interesting to verify the impact of interrupts by disabling them temporarily. If they are indeed the source of the overhead, a possible workaround would be to declare the function as frameless, as frameless calls do not check interrupts. This may be beneficial on its own, too.
dktapps commentedon Sep 15, 2025
I had looked into this, but the documentation on frameless functions is extremely sparse. Also, as far as I know, they have to be declared differently from regular functions, which would be a pain in my case as I still need to support PHP <8.4.
Benchmark results are as follows. The results look a bit underwhelming, but I will say that I don't think the instruction counts paint a full picture though.
PHP 8.4 base
https://github.com/php/php-src/actions/runs/17728778760/job/50375221322
Disabled internal function interrupts
https://github.com/dktapps/php-src/actions/runs/17739651420/job/50410223738
Disabled loop interrupts:
https://github.com/dktapps/php-src/actions/runs/17739667715/job/50410276410
Disabled `ZEND_VM_INTERRUPT_CHECK()`
https://github.com/dktapps/php-src/actions/runs/17739683417/job/50410330496
arnaud-lb commentedon Sep 16, 2025
Thank you! I've computed the differences:
Disabled internal function interrupts vs baseline:
Disabled
ZEND_VM_INTERRUPT_CHECK()vs baseline:These are not negligible. This is worth investigating.
morrisonlevi commentedon Dec 30, 2025
All the use-cases of interrupts need to be accounted for if you intend to remove it. The main cases I know about are: