Closed7

preg_replaceでまとめて置換するのとpreg_replace後にstr_replaceするのどっちがいいのか

ピン留めされたアイテム
白湯白湯

これまでやってきたことを整理してもう一度検証を行う。(そもそも検証コードおかしかったしね)

検証コード

<?php

declare(strict_types=1);

class Str
{
    // もともとのコード
    public static function first(string $str): string
    {
        return strtolower(str_replace('-', '_', ltrim(preg_replace('/([A-Z])/', '_\0', $str), '_')));
    }

    // ltrimしなくていいように正規表現を変えたパターン
    public static function second(string $str): string
    {
        return strtolower(str_replace('-', '_', preg_replace('/(?<=.)([A-Z])/', '_\1', $str)));
    }

    // 最終版
    public static function third(string $str): string
    {
        return strtolower(preg_replace('/(?<=.)([A-Z])|-/', '_\1', $str));
    }
}

function run(callable $callback, string $str, int $times = 1): float
{
    $input = str_repeat($str, $times);

    $start = microtime(true);

    for ($i = 0; $i < 1000000; $i++) {
        $callback($input);
    }

    $end = microtime(true);

    return $end - $start;
}

$array = [
    'snakeCase' => 'snake_hoge_fuga_piyo',
    'kebabCase' => 'kebab-hoge-fuga-piyo',
    'camelCase' => 'camelHogeFugaPiyo',
    'pascalCase' => 'PascalHogeFugaPiyo',
];

foreach ($array as $key => $str) {
    echo $key, PHP_EOL;

    echo 'first', PHP_EOL;
    echo '20文字程度: ', run([Str::class, 'first'], $str, 1), ' 秒', PHP_EOL;
    echo '100文字程度: ', run([Str::class, 'first'], $str, 5), ' 秒', PHP_EOL;
    echo '200文字程度: ', run([Str::class, 'first'], $str, 10), ' 秒', PHP_EOL;

    echo PHP_EOL;

    echo 'second', PHP_EOL;
    echo '20文字程度: ', run([Str::class, 'second'], $str, 1), ' 秒', PHP_EOL;
    echo '100文字程度: ', run([Str::class, 'second'], $str, 5), ' 秒', PHP_EOL;
    echo '200文字程度: ', run([Str::class, 'second'], $str, 10), ' 秒', PHP_EOL;

    echo PHP_EOL;

    echo 'third', PHP_EOL;
    echo '20文字程度: ', run([Str::class, 'third'], $str, 1), ' 秒', PHP_EOL;
    echo '100文字程度: ', run([Str::class, 'third'], $str, 5), ' 秒', PHP_EOL;
    echo '200文字程度: ', run([Str::class, 'third'], $str, 10), ' 秒', PHP_EOL;

    echo str_repeat('=', 40), PHP_EOL;
}

first メソッドはもともと書いていたコード。
second メソッドは ltrim を実行しなくてもいいように正規表現だけを変えたパターン。
third メソッドは正規表現だけで処理を済ませたパターン。

検証パターン

それぞれのメソッドに、20/100/200字程度の文字列を与えて、処理速度を計る。
与える文字列はスネークケース、ケバブケース、キャメルケース、パスカルケースの4種類。

ピン留めされたアイテム
白湯白湯

結果

単位: 秒(小数点以下第三位を切り捨て)

スネークケース

20字程度 100字程度 200字程度
first 0.95 1.06 1.09
second 0.84 0.86 0.93
third 0.64 0.67 0.71

ケバブケース

20字程度 100字程度 200字程度
first 1.02 1.17 1.42
second 0.88 1.04 1.28
third 0.75 1.16 1.71

キャメルケース

20字程度 100字程度 200字程度
first 1.18 1.57 2.14
second 0.96 1.42 2.04
third 0.75 1.22 1.82

パスカルケース

20字程度 100字程度 200字程度
first 1.16 1.76 2.51
second 0.95 1.60 2.37
third 0.75 1.40 2.18

基本的には third > second > first の順で時間がかかっている。(ケバブケースを変換したときだけ second が一番速い)
下手な正規表現を書かなければ、まとめて記述してもいいのかもしれない。

白湯白湯

以前作った、入力文字列をスネークケースにするメソッドを書き直してみたところ、利用する関数を少なくすることができた。

変更前

https://github.com/sayuprc/php-utility/blob/dac8943b403f7ba83dbbe9694d5bb7562ae7fd6a/src/Str.php#L33-L43

変更後

    public static function toSnake(string $str): string
    {
        return strtolower(preg_replace('/(?<=.)([A-Z])|-/', '_\1', $str));
    }

短く書けたのはよいが、正規表現ってあんまりパフォーマンス良い印象がないので、どっちが速いのか検証してみる。

検証バージョン

PHP: 8.1.10

白湯白湯

検証コード

<?php

declare(strict_types=1);

class Str
{
    public static function toSnake(string $str): string
    {
        return strtolower(str_replace('-', '_', ltrim(preg_replace('/([A-Z])/', '_\0', $str), '_')));
    }

    public static function toSnakeImprovement(string $str): string
    {
        return strtolower(preg_replace('/(?<=.)([A-Z])|-/', '_\1', $str));
    }
}

$snakeCase = 'snake_hoge_fuga_piyo';
$kebabCase = 'kebab-hoge-fuga-piyo';
$camelCase = 'camelHogeFugaPiyo';
$pascalCase = 'PascalHogeFugaPiyo';

// 改修前
$start = microtime(true);

for ($i = 0; $i < 1000000; $i++) {
    Str::toSnake($snakeCase);
    Str::toSnake($kebabCase);
    Str::toSnake($camelCase);
    Str::toSnake($pascalCase);
}

$end = microtime(true);

echo '改修前: ', $end - $start , ' 秒', PHP_EOL;

// 改修後
$start = microtime(true);

for ($i = 0; $i < 1000000; $i++) {
    Str::toSnakeImprovement($snakeCase);
    Str::toSnakeImprovement($kebabCase);
    Str::toSnakeImprovement($camelCase);
    Str::toSnakeImprovement($pascalCase);
}

$end = microtime(true);

echo '改修後: ', $end - $start , ' 秒', PHP_EOL;
白湯白湯

結果

$ php Str.php 
改修前: 3.8098320960999 秒
改修後: 2.482449054718

正規表現でまとめて置換してあげたほうが1秒以上速い。
これは何度やっても同じような結果だったので、20字程度の文字列だったら正規表現のほうが速いみたいで。

もっと長かったりするともしかしたら速度変わるのかも?
=> 100文字前後の場合、結果はしたのようになった。

php Str.php 
改修前: 5.6295590400696 秒
改修後: 4.0872659683228

相変わらず正規表現使ったほうが速い。

改修前のコードは利用している関数の数が多いので、正規表現のコストより関数実行のコストのほうが高いみたい。

白湯白湯

改修前のコードが関数利用しすぎていたので、ちょこっと変えてもう一度検証してみる。


    public static function toSnake(string $str): string
    {
        return strtolower(str_replace('-', '_', preg_replace('/(?<=.)([A-Z])/', '_\1', $str)));
    }

上記のコードと改修後のコードで速度比較する。

白湯白湯

結果

# 20字前後の文字列
$ php Str.php 
改修前: 3.2798988819122 秒
改修後: 2.5775918960571# 100字前後の文字列
$ php Str.php 
改修前: 4.7906939983368 秒
改修後: 4.2071549892426

やっぱり改修後のコードの方がちょっとだけ速かった。

このスクラップは2022/10/01にクローズされました