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
ctype_alnum 5 times slower in PHP 8.1 or greater #11997
Strangely, it was already way slower in 8.0.30 on my system. In 8.1.0 there is additional slowdown that seems to come from the indirect call when the code was refactored from macro into function: dc80ea7
However, even after reverting that change, it's only a little bit faster, still almost a factor of 5 slower...
Maybe your environment is smart enough to create an inline version of isalnum.
If I apply the following patch, the ctype_isalnum becomes almost twice as fast as the regex solution. Probably because it avoid C library call overhead:
Could even be further improved by moving the deprecated code section into a separate no-inline function such that the code cache pressure doesn't increase too much.
We could make our own version for each of the iswhatever functions, but idk if we want this? Maybe some other people can voice there opinion here :-) EDIT: this may be undesirable because they depend on the locale, even for the isalnum case...
However, a large part of the complexity of those functions is related to needing to deal with integers.
When this is removed and a proper string ZPP check can be used, it might make more sense to just inline everything instead of passing by a common generic implementation.
However, a large part of the complexity of those functions is related to needing to deal with integers.
According to callgrind, the most time is spent in the isalnum call and the if check. The overhead of the C library call is quite high.
That being said, hand-rolling our own iswhatever functions would give the biggest speedup, but would also introduce complexity because we actually have to take into account the current C locale...
@joanhey If you compiled PHP yourself, are you able to test what the performance difference is on your system when you add zend_always_inline to ctype_impl (like I did in the patch, but without the other additions).
@Girgias Probably we can just refer to the Locale class from intl. And rip out setlocale() support everywhere else.
However this also means every printf("%f") for example will be affected, which may not be desirable. I don't think the path to deprecation is as simple as it seems, as locales are still involved in core places of PHP.
I don't think it's simple, but is probably worthwhile. Also printf() for useland uses our own implementation, for C extensions I'm not sure this is something widely used within php-src (I do have a script somewhere to check) but even then they probably shoud use the custom version.
LLVM's libc implementation also does not support locales here, which is expected there since they don't support locales yet.
Honestly, I'm not certain locale support is even desirable for these newer libc implementations. The only system which is not totally bonkers broken is UTF-8, and ctypes are definitely not that, so...
It's worth noting that glibc & musl ctype implementation comes in two variants: function-based and macro-based. e.g. we have an isalnum macro and an isalnum function. Currently, the function-based version is used because ctype function are passed as a function pointer to ctype_impl.
When we use a macro to implement ctype_impl instead, the macro-based version of isalnum etc can be used. That's what my sample patch accomplishes.
OP's example code is around ~0.02s faster on my system (glibc-based) after this patch. So it's still much slower than OP claims it was or in comparison to pcre. Mainly we got rid of some call overhead and can do the check inline. Thinking about it, the benchmark code that compares pcre with ctype isn't even fair because the pcre code doesn't take into account the locale...
However, on Alpine there is indeed a much higher win and I get an enormous speedup, the time for ctype is about half the time for pcre. This is practically the same result as OP got for PHP 8.0, so I suspect OP is on Alpine or another musl-based system. On those systems the sample patch is more worthwhile.
So it's basically reverting dc80ea7, which would explain the difference in speed from 8.0 to 8.1.
I don't really care, even if I don't really like macros, but this case has always been pretty readable, even in macro form.
I wonder if it still doesn't make sense to change the behaviour to not depend on the C locale, as we have done with other functions recently (strtolower coming to mind).
So it's basically reverting dc80ea7, which would explain the difference in speed from 8.0 to 8.1.
Indeed.
I don't really care, even if I don't really like macros, but this case has always been pretty readable, even in macro form.
Yeah I'm quite neutral too, but I guess it doesn't hurt, and it does get rid of a regression.
I wonder if it still doesn't make sense to change the behaviour to not depend on the C locale, as we have done with other functions recently (strtolower coming to mind).
Good question. I don't know what the actual user expectation is of these functions. I think I'd expect them to behave like how they would in C (i.e. respecting the locale) because they borrowed the c header's name as a prefix.
Maybe something to add to php-task repo (as a function that depends on the locale, which might make sense to try and list everything that does to see what the scope of the impact would be about removing it)
Activity
ndossche commentedon Aug 19, 2023
Strangely, it was already way slower in 8.0.30 on my system. In 8.1.0 there is additional slowdown that seems to come from the indirect call when the code was refactored from macro into function: dc80ea7
However, even after reverting that change, it's only a little bit faster, still almost a factor of 5 slower...
Maybe your environment is smart enough to create an inline version of isalnum.
If I apply the following patch, the ctype_isalnum becomes almost twice as fast as the regex solution. Probably because it avoid C library call overhead:
Could even be further improved by moving the deprecated code section into a separate no-inline function such that the code cache pressure doesn't increase too much.
We could make our own version for each of the iswhatever functions, but idk if we want this? Maybe some other people can voice there opinion here :-) EDIT: this may be undesirable because they depend on the locale, even for the isalnum case...
Girgias commentedon Aug 19, 2023
Meh, I don't really mind.
However, a large part of the complexity of those functions is related to needing to deal with integers.
When this is removed and a proper string ZPP check can be used, it might make more sense to just inline everything instead of passing by a common generic implementation.
ndossche commentedon Aug 19, 2023
According to callgrind, the most time is spent in the isalnum call and the if check. The overhead of the C library call is quite high.
That being said, hand-rolling our own iswhatever functions would give the biggest speedup, but would also introduce complexity because we actually have to take into account the current C locale...
@joanhey If you compiled PHP yourself, are you able to test what the performance difference is on your system when you add
zend_always_inlinetoctype_impl(like I did in the patch, but without the other additions).Girgias commentedon Aug 20, 2023
I maybe should go forward with actually deprecating
setlocale()and forcing the C locale to be used. As this is a constant source of painbwoebi commentedon Aug 21, 2023
@Girgias Probably we can just refer to the
Localeclass from intl. And rip out setlocale() support everywhere else.However this also means every
printf("%f")for example will be affected, which may not be desirable. I don't think the path to deprecation is as simple as it seems, as locales are still involved in core places of PHP.Girgias commentedon Aug 21, 2023
I don't think it's simple, but is probably worthwhile. Also
printf()for useland uses our own implementation, for C extensions I'm not sure this is something widely used within php-src (I do have a script somewhere to check) but even then they probably shoud use the custom version.morrisonlevi commentedon Sep 5, 2023
Note that on musl's libc, these functions don't take into account locale. You can see the sources here: https://git.musl-libc.org/cgit/musl/plain/src/ctype/ though I've copied relevant pieces here:
Of course, their locale support is... well I'll just quote them:
Just adding this as more background information when making a decision.
morrisonlevi commentedon Sep 5, 2023
LLVM's libc implementation also does not support locales here, which is expected there since they don't support locales yet.
Honestly, I'm not certain locale support is even desirable for these newer libc implementations. The only system which is not totally bonkers broken is UTF-8, and ctypes are definitely not that, so...
ndossche commentedon Sep 23, 2023
I took a look at this again.
Sample patch: https://gist.github.com/nielsdos/0faacfefb4844be0cf107d775c5d0ac7
It's worth noting that glibc & musl ctype implementation comes in two variants: function-based and macro-based. e.g. we have an isalnum macro and an isalnum function. Currently, the function-based version is used because ctype function are passed as a function pointer to
ctype_impl.When we use a macro to implement
ctype_implinstead, the macro-based version of isalnum etc can be used. That's what my sample patch accomplishes.OP's example code is around ~0.02s faster on my system (glibc-based) after this patch. So it's still much slower than OP claims it was or in comparison to pcre. Mainly we got rid of some call overhead and can do the check inline. Thinking about it, the benchmark code that compares pcre with ctype isn't even fair because the pcre code doesn't take into account the locale...
However, on Alpine there is indeed a much higher win and I get an enormous speedup, the time for ctype is about half the time for pcre. This is practically the same result as OP got for PHP 8.0, so I suspect OP is on Alpine or another musl-based system. On those systems the sample patch is more worthwhile.
Any thoughts?
Girgias commentedon Sep 25, 2023
So it's basically reverting dc80ea7, which would explain the difference in speed from 8.0 to 8.1.
I don't really care, even if I don't really like macros, but this case has always been pretty readable, even in macro form.
I wonder if it still doesn't make sense to change the behaviour to not depend on the C locale, as we have done with other functions recently (strtolower coming to mind).
Fix phpGH-11997: ctype_alnum 5 times slower in PHP 8.1 or greater
ndossche commentedon Sep 25, 2023
Indeed.
Yeah I'm quite neutral too, but I guess it doesn't hurt, and it does get rid of a regression.
Good question. I don't know what the actual user expectation is of these functions. I think I'd expect them to behave like how they would in C (i.e. respecting the locale) because they borrowed the c header's name as a prefix.
Girgias commentedon Sep 26, 2023
I don't know what the user expectation is either.
Maybe something to add to php-task repo (as a function that depends on the locale, which might make sense to try and list everything that does to see what the scope of the impact would be about removing it)
Fix GH-11997: ctype_alnum 5 times slower in PHP 8.1 or greater
Merge branch 'PHP-8.1' into PHP-8.2
Merge branch 'PHP-8.2' into PHP-8.3
Merge branch 'PHP-8.3'