Open3

substr vs str_starts_with

at yasuat yasu

substr vs str_starts_with

先頭一文字目だけの比較をするとき、 substr($string, 0, 1) === "s" とか書くのですが、 str_starts_with -- php.net というメソッドもあって、どっちの方がパフォーマンスがいいのかチェック。

substr_vs_str_starts_with.php

<?php

function getVeryManyStrings(int $max)
{
    $r = [];
    for ($i = 0; $i < $max; $i++) {
        $r[] = base64_encode(random_bytes(32));
    }
    return $r;
}

print("Generate very many strings\n");
$check1 = getVeryManyStrings(100_000_000);

print("Start !!!\n\n");
$start = microtime(true);
array_map(fn($k) => substr($k, 0, 1) === 's', $check1);
$end = microtime(true);

array_map(fn($k) => str_starts_with($k, 'k'), $check1);
$end2 = microtime(true);


print("substr is " . ($end - $start) . " sec\n");
print("str_starts_with is " . ($end2 - $end) . " sec\n");

結果

❯ php -d memory_limit=-1 substr_vs_str_starts_with.php
Generate very many strings
Start !!!

substr is 7.6892261505127 sec
str_starts_with is 6.5366988182068 sec
at yasuat yasu

おまけ

正規表現を使ったとき、どうなるか。

<?php

function getVeryManyStrings(int $max)
{
    $r = [];
    for ($i = 0; $i < $max; $i++) {
        $r[] = base64_encode(random_bytes(32));
    }
    return $r;
}

print("Generate very many strings\n");
$check1 = getVeryManyStrings(100_000_000);

print("Start !!!\n\n");
$start = microtime(true);
array_map(fn($k) => substr($k, 0, 1) === 's', $check1);
$end = microtime(true);

array_map(fn($k) => str_starts_with($k, 'k'), $check1);
$end2 = microtime(true);

array_map(fn($k) => preg_match('/^i/u', $k), $check1);
$end3 = microtime(true);

print("substr is " . ($end - $start) . " sec\n");
print("str_starts_with is " . ($end2 - $end) . " sec\n");
print("preg_match is " . ($end3 - $end2) . " sec\n");

結果

❯ php -d memory_limit=-1 substr_vs_str_starts_with.php
Generate very many strings
Start !!!

substr is 7.9892220497131 sec
str_starts_with is 6.3257839679718 sec
preg_match is 9.3445250988007 sec
at yasuat yasu

str_starts_with の実装

ソースコードを追っかける。

https://github.com/php/php-src/blob/223683dfb594d3fa680c7a72b34c11e70192dbe5/ext/standard/string.c#L1883-L1893

RETURN_BOOL(zend_string_starts_with(haystack, needle));

https://github.com/php/php-src/blob/223683dfb594d3fa680c7a72b34c11e70192dbe5/Zend/zend_string.h#L408

突き詰めた所で、実態はこれ。

https://github.com/php/php-src/blob/223683dfb594d3fa680c7a72b34c11e70192dbe5/Zend/zend_string.h#L403-L406

要は C で直接、メモリー上のバイト比較をしているので、高速に動く。

substr の実装

https://github.com/php/php-src/blob/223683dfb594d3fa680c7a72b34c11e70192dbe5/ext/standard/string.c#L2265-L2302

ZEND_ を使ってて、割りとスクリプトみたいな感じになってる。