Eliminate anti-pattern isset($arr[$key]) || array_key_exists($key, $arr)

Created on 8 March 2023, almost 2 years ago
Updated 21 March 2023, almost 2 years ago

Problem/Motivation

In old versions of PHP, array_key_exists($key, $arr) was (much) slower than isset($arr[$key]) and consequently the following pattern, although functionally redundant, was adopted for performance reasons in cases where the first test was likely to succeed:

isset($arr[$key]) || array_key_exists($key, $arr)

This is now an anti-pattern, as the performance issue with array_key_exists() has already been resolved in all versions of PHP that we care about, and consequently the inclusion of the redundant isset() will now be having a negative net effect on performance, rather than a positive one. Equally importantly, we can get rid of code which looks wrong.

Refer to https://bugs.php.net/bug.php?id=71239 for upstream confirmation:

array_key_exists() has an optimized opcode implementation since PHP 7.4.

Heed comment #5 below, however -- in namespaced code, it's necessary to either call \array_key_exists() or else use function array_key_exists; otherwise the opcode will not be used.

πŸ“Œ Task
Status

Fixed

Version

10.1 ✨

Component
OtherΒ  β†’

Last updated 2 days ago

Created by

πŸ‡³πŸ‡ΏNew Zealand jweowu

Live updates comments and jobs are added and updated live.
  • Performance

    It affects performance. It is often combined with the Needs profiling tag.

Sign in to follow issues

Comments & Activities

  • Issue created by @jweowu
  • Status changed to Needs review almost 2 years ago
  • πŸ‡³πŸ‡ΏNew Zealand jweowu

    This patch omits the instance in drupal_static() which is accounted for already in the patch for πŸ› drupal_static() edge case bug and inconsistent comments Fixed (which inspired this new issue).

  • πŸ‡¬πŸ‡§United Kingdom catch
    +++ b/core/lib/Drupal/Component/DependencyInjection/Container.php
    @@ -320,7 +320,7 @@ public function has($id): bool {
       public function getParameter($name): array|bool|string|int|float|NULL {
    -    if (!(isset($this->parameters[$name]) || array_key_exists($name, $this->parameters))) {
    +    if (!array_key_exists($name, $this->parameters)) {
           if (!$name) {
    

    Should we change this to \array_key_exists() in namespaced code at the same time?

  • πŸ‡³πŸ‡ΏNew Zealand jweowu

    I'm not seeing anything in the Drupal coding standards indicating that function calls in the global name-space should have a backslash prefix. It does say this about global classes, but that's all; and a cursory grep is only showing a tiny number of such function calls anywhere in the codebase.

    I don't mind making that change if need be, but it seems out of scope?

  • Status changed to Needs work almost 2 years ago
  • πŸ‡³πŸ‡ΏNew Zealand jweowu

    I was wrong -- I see now that this tweak is actually required for namespaced code, so I'll go ahead and re-roll the patch.

    https://www.php.net/manual/en/migration74.other-changes.php says:

    A specialized VM opcode for the array_key_exists() function has been added, which improves performance of this function if it can be statically resolved. In namespaced code, this may require writing \array_key_exists() or explicitly importing the function.

    https://bugs.php.net/bug.php?id=79675 indicates use function array_key_exists; is the correct way to explicitly import the function, and following the cues from that bug I can confirm the other approach practically, courtesy of pecl install vld-beta:

    $ php -dvld.active=1 -dvld.execute=0 -r 'array_key_exists(1, []);'
    
    line      #* E I O op                           fetch          ext  return  operands
    -------------------------------------------------------------------------------------
        1     0  E >   ARRAY_KEY_EXISTS                                 ~0      1, <array>
              1        FREE                                                     ~0
              2      > RETURN                                                   null
    
    $ php -dvld.active=1 -dvld.execute=0 -r 'namespace a; array_key_exists(1, []);'
    
    line      #* E I O op                           fetch          ext  return  operands
    -------------------------------------------------------------------------------------
        1     0  E >   INIT_NS_FCALL_BY_NAME                                    'a%5Carray_key_exists'
              1        SEND_VAL_EX                                              1
              2        SEND_VAL_EX                                              <array>
              3        DO_FCALL                                      0
              4      > RETURN                                                   null
    
    $ php -dvld.active=1 -dvld.execute=0 -r 'namespace a; \array_key_exists(1, []);'
    
    line      #* E I O op                           fetch          ext  return  operands
    -------------------------------------------------------------------------------------
        1     0  E >   ARRAY_KEY_EXISTS                                 ~0      1, <array>
              1        FREE                                                     ~0
              2      > RETURN                                                   null
    
  • πŸ‡³πŸ‡ΏNew Zealand jweowu
  • πŸ‡¬πŸ‡§United Kingdom catch

    #5 is right, we probably should have a coding standard, but until then using the explicit namespace as a performance optimisation is fine - sorry could have been more explicit why we needed to do it but glad you found it, and hadn't come across vld before - looks handy!

  • Status changed to Needs review almost 2 years ago
  • πŸ‡³πŸ‡ΏNew Zealand jweowu

    I've added the backslash prefix irrespective of namespacing, as I feel that this opcode/no-opcode performance gotcha is ridiculously esoteric, and so it's probably better if the backslash is employed in all cases (there should be a follow-up issue for code not affected by this patch) so that that it'll look wrong to ever see this function called without a backslash.

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    @jweowu, thanks for this initiative. It's good that PHP fixed this performance bug and will allow us to have code which is simpler. A good initiative for symfony based Drupals that require the very latest PHP.

  • Status changed to RTBC almost 2 years ago
  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    Applied patch #8

    Searched for ") || array_key_exists('" and got 0 results in the repo.

    • catch β†’ committed e48191a9 on 10.1.x
      Issue #3346645 by jweowu, catch, smustgrave: Eliminate anti-pattern...
  • Status changed to Fixed almost 2 years ago
  • πŸ‡¬πŸ‡§United Kingdom catch

    This is good for a readability improvement and now micro-optimization now that PHP has the opcode implementation. We can try to make fully qualified functions a coding standard in 🌱 Call PHP native functions fully qualified (like \array_key_exists()) Active .

    Committed/pushed to 10.1.x, thanks!

  • Automatically closed - issue fixed for 2 weeks with no activity.

Production build 0.71.5 2024