Skip to content

Performance: array_reduce is drastically slower (~1000x) than implementing the same logic with foreach #8283

@Firehed

Description

@Firehed

Description

The following code:

<?php

declare(strict_types=1);

// generate a large sample of pseudo-random data which may have duplicates
$data = [];
for ($i = 0; $i < 40000; $i++) {
    $data[] = crc32((string)$i);
}


echo 'Foreach';

$start = hrtime(true);
$result = [];
foreach ($data as $item) {
    @$result[$item]++;
}
$durationNs = hrtime(true) - $start;
echo sprintf('%f s', $durationNs / 1_000_000_000);
echo "\n\n";

unset($start, $result, $item, $durationNs);
echo 'Array reduce';

$start = hrtime(true);
$result = array_reduce($data, function ($carry, $item) {
    @$carry[$item]++;
    return $carry;
}, []);
$durationNs = hrtime(true) - $start;
echo sprintf('%f s', $durationNs / 1_000_000_000);

Resulted in this output:

Foreach 0.007446s

Array reduce 8.685649s

But I expected this output instead:

(comparable runtimes)

Possibly impactful opcache ini settings:

 $ php -i | rg opcache
Additional .ini files parsed => /usr/local/etc/php/8.1/conf.d/ext-opcache.ini
    with Zend OPcache v8.1.3, Copyright (c), by Zend Technologies
Zend OPcache
opcache.blacklist_filename => no value => no value
opcache.consistency_checks => 0 => 0
opcache.dups_fix => Off => Off
opcache.enable => On => On
opcache.enable_cli => Off => Off
opcache.enable_file_override => Off => Off
opcache.error_log => no value => no value
opcache.file_cache => no value => no value
opcache.file_cache_consistency_checks => On => On
opcache.file_cache_only => Off => Off
opcache.file_update_protection => 2 => 2
opcache.force_restart_timeout => 180 => 180
opcache.huge_code_pages => Off => Off
opcache.interned_strings_buffer => 8 => 8
opcache.jit => tracing => tracing
opcache.jit_bisect_limit => 0 => 0
opcache.jit_blacklist_root_trace => 16 => 16
opcache.jit_blacklist_side_trace => 8 => 8
opcache.jit_buffer_size => 0 => 0
opcache.jit_debug => 0 => 0
opcache.jit_hot_func => 127 => 127
opcache.jit_hot_loop => 64 => 64
opcache.jit_hot_return => 8 => 8
opcache.jit_hot_side_exit => 8 => 8
opcache.jit_max_exit_counters => 8192 => 8192
opcache.jit_max_loop_unrolls => 8 => 8
opcache.jit_max_polymorphic_calls => 2 => 2
opcache.jit_max_recursive_calls => 2 => 2
opcache.jit_max_recursive_returns => 2 => 2
opcache.jit_max_root_traces => 1024 => 1024
opcache.jit_max_side_traces => 128 => 128
opcache.jit_prof_threshold => 0.005 => 0.005
opcache.lockfile_path => /tmp => /tmp
opcache.log_verbosity_level => 1 => 1
opcache.max_accelerated_files => 10000 => 10000
opcache.max_file_size => 0 => 0
opcache.max_wasted_percentage => 5 => 5
opcache.memory_consumption => 128 => 128
opcache.opt_debug_level => 0 => 0
opcache.optimization_level => 0x7FFEBFFF => 0x7FFEBFFF
opcache.preferred_memory_model => no value => no value
opcache.preload => no value => no value
opcache.preload_user => no value => no value
opcache.protect_memory => Off => Off
opcache.record_warnings => Off => Off
opcache.restrict_api => no value => no value
opcache.revalidate_freq => 2 => 2
opcache.revalidate_path => Off => Off
opcache.save_comments => On => On
opcache.use_cwd => On => On
opcache.validate_permission => Off => Off
opcache.validate_root => Off => Off
opcache.validate_timestamps => On => On

Note that opcache_get_status() returns false when running this script via CLI, which is where I encountered this.

General observation: this doesn't appear to be something that gets incrementally slower the further along the array the algorithm is. By adding an echo '.' in either section, it's apparent that even the very first item (with an empty $carry) is drastically slower in array_reduce than in the body of foreach.

I know they're not 100% comparable, but both should have O(n) complexity so I would not expect three orders of magnitude performance difference to produce the same result.

PHP Version

8.1.3

Operating System

macOS 12.3

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions