Производительность str_replace в PHP - PullRequest
5 голосов
/ 13 декабря 2011

Здесь у меня есть 2 метода, использующих str_replace для замены строк в данной фразе.

// Method 1
$phrase  = "You should eat fruits, vegetables, and fiber every day.";
$healthy = array("fruits", "vegetables", "fiber");
$yummy   = array("pizza", "beer", "ice cream");
$phrase = str_replace($healthy, $yummy, $phrase);

// Method 2
$phrase  = "You should eat fruits, vegetables, and fiber every day.";
$phrase = str_replace("fruits", "pizza", $phrase);
$phrase = str_replace("vegetables", "beer", $phrase);
$phrase = str_replace("fiber", "ice cream", $phrase);

Какой метод более эффективен (с точки зрения времени выполнения и используемых ресурсов)?

Предположим, что реальная фраза намного длиннее (например, 50 000 символов), а слова для замены содержат гораздо больше пар.

Я думаю, что метод 2 вызывает str_replace 3 раза, который будет стоить больше вызовов функций;с другой стороны, метод 1 создает 2 массива, а str_replace необходимо проанализировать 2 массива во время выполнения.

Ответы [ 4 ]

5 голосов
/ 13 декабря 2011

Я бы предпочел использовать метод 1 в качестве более чистого и более организованного, а также метод 1 дает возможность использовать пары из другого источника, например таблицы плохих слов в базе данных. Метод 2 потребует еще один цикл сортировки ..

<?php
$time_start = microtime(true);
for($i=0;$i<=1000000;$i++){
    // Method 1
    $phrase  = "You should eat fruits, vegetables, and fiber every day.";
    $healthy = array("fruits", "vegetables", "fiber");
    $yummy   = array("pizza", "beer", "ice cream");
    $phrase = str_replace($healthy, $yummy, $phrase);
}
$time_end = microtime(true);
$time = $time_end - $time_start;
echo "Did Test 1 in ($time seconds)\n<br />";



$time_start = microtime(true);
for($i=0;$i<=1000000;$i++){
    // Method2
    $phrase  = "You should eat fruits, vegetables, and fiber every day.";
    $phrase = str_replace("fruits", "pizza", $phrase);
    $phrase = str_replace("vegetables", "beer", $phrase);
    $phrase = str_replace("fiber", "ice cream", $phrase);

}
$time_end = microtime(true);
$time = $time_end - $time_start;
echo "Did Test 2 in ($time seconds)\n";
?>  

Пройти тест 1 дюйм (3,6321988105774 секунд)

Тест 2 в (2,8234610557556 секунд)


Редактировать: При дальнейшей проверке строки, повторяющейся до 50 Кб, меньше итераций и советов от ajreal, разница настолько мала.
<?php
$phrase  = str_repeat("You should eat fruits, vegetables, and fiber every day.",50000);
$healthy = array("fruits", "vegetables", "fiber");
$yummy   = array("pizza", "beer", "ice cream");

$time_start = microtime(true);
for($i=0;$i<=10;$i++){
    // Method 1
    $phrase = str_replace($healthy, $yummy, $phrase);
}
$time_end = microtime(true);
$time = $time_end - $time_start;
echo "Did Test 1 in ($time seconds)\n<br />";



$time_start = microtime(true);
for($i=0;$i<=10;$i++){
    // Method2
    $phrase = str_replace("fruits", "pizza", $phrase);
    $phrase = str_replace("vegetables", "beer", $phrase);
    $phrase = str_replace("fiber", "ice cream", $phrase);

}
$time_end = microtime(true);
$time = $time_end - $time_start;
echo "Did Test 2 in ($time seconds)\n";
?>  

Пройти тест 1 дюйм (1,1450328826904 секунды)

Пройти тест 2 в (1,3119208812714 секунд)

3 голосов
/ 13 декабря 2011

Даже если старый, этот эталонный тест неверен.

Благодаря анонимному пользователю:

"Этот тест неверен, потому что когда тест 3 запускается, $ фразу использует результаты теста 2,в котором нечего заменить.

Когда я добавляю $ фразу = "Вы должны есть фрукты, овощи и клетчатку каждый день.") Сделал тест 2 в (5,7581660747528 секунд) сделал тест 3 в (7,5069718360901 секунд) "

        <?php
        $time_start = microtime(true);

        $healthy = array("fruits", "vegetables", "fiber");
        $yummy   = array("pizza", "beer", "ice cream");

        for($i=0;$i<=1000000;$i++){
            // Method 1
            $phrase  = "You should eat fruits, vegetables, and fiber every day.";
            $phrase = str_replace($healthy, $yummy, $phrase);
        }
        $time_end = microtime(true);
        $time = $time_end - $time_start;
        echo "Did Test 1 in ($time seconds)<br /><br />";



        $time_start = microtime(true);
        for($i=0;$i<=1000000;$i++){
            // Method2
            $phrase  = "You should eat fruits, vegetables, and fiber every day.";
            $phrase = str_replace("fruits", "pizza", $phrase);
            $phrase = str_replace("vegetables", "beer", $phrase);
            $phrase = str_replace("fiber", "ice cream", $phrase);

        }
        $time_end = microtime(true);
        $time = $time_end - $time_start;
        echo "Did Test 2 in ($time seconds)<br /><br />";




        $time_start = microtime(true);
        for($i=0;$i<=1000000;$i++){
                foreach ($healthy as $k => $v) {
                  if (strpos($phrase, $healthy[$k]) === FALSE)  
                  unset($healthy[$k], $yummy[$k]);
                }                                          
                if ($healthy) $new_str = str_replace($healthy, $yummy, $phrase);

        }
        $time_end = microtime(true);
        $time = $time_end - $time_start;
        echo "Did Test 3 in ($time seconds)<br /><br />";

        ?>  

сделал тест 1 в (3,55785729885101 секунд)

сделал тест 2 в (3,8501658439636 секунд)

Тест 3 в (0,13844394683838 секунд)

2 голосов
/ 10 декабря 2017

Хотя в вопросе об этом прямо не говорится, ОП заявляет:

Предположим, что реальная фраза намного длиннее (например, 50 000 символов), и слова для замены имеют гораздо больше пар.

В этом случае, если вам не нужны (или вы не хотите) замены внутри замен, может быть гораздо эффективнее использовать решение preg_replace_callback, чтобы вся строка обрабатывалась только один раз, а не один раз для каждой пары замена.

Вот обобщенная функция, которая в моем случае со строкой 1,5 Мб и ~ 20 000 пар замен была примерно в 10 раз быстрее, хотя из-за необходимости разбивать замены на куски из-за ошибок «регулярное выражение слишком велико» может иметь Произошли замены внутри замен неопределенно (однако в моем конкретном случае это было невозможно).

В моем конкретном случае я смог дополнительно оптимизировать это до примерно 100-кратного прироста производительности, потому что все мои строки поиска следовали определенному шаблону. (PHP версии 7.1.11 в Windows 7 32-разрядная версия.)

function str_replace_bulk($search, $replace, $subject, &$count = null) {
  // Assumes $search and $replace are equal sized arrays
  $lookup = array_combine($search, $replace);
  $result = preg_replace_callback(
    '/' .
      implode('|', array_map(
        function($s) {
          return preg_quote($s, '/');
        },
        $search
      )) .
    '/',
    function($matches) use($lookup) {
      return $lookup[$matches[0]];
    },
    $subject,
    -1,
    $count
  );
  if (
    $result !== null ||
    count($search) < 2 // avoid infinite recursion on error
  ) {
    return $result;
  }
  // With a large number of replacements (> ~2500?), 
  // PHP bails because the regular expression is too large.
  // Split the search and replacements in half and process each separately.
  // NOTE: replacements within replacements may now occur, indeterminately.
  $split = (int)(count($search) / 2);
  error_log("Splitting into 2 parts with ~$split replacements");
  $result = str_replace_bulk(
    array_slice($search, $split),
    array_slice($replace, $split),
    str_replace_bulk(
      array_slice($search, 0, $split),
      array_slice($replace, 0, $split),
      $subject,
      $count1
    ),
    $count2
  );
  $count = $count1 + $count2;
  return $result;
}
1 голос
/ 23 августа 2016

@ djot у вас ошибка в

<?php
     foreach ($healthy as $k => $v) {
        if (strpos($phrase, $healthy[$k]) === FALSE)  
             unset($healthy[$k], $yummy[$k]);
        }  

Здесь у нас есть фиксированная версия и лучший / простой новый тест 4

<?php 
 $time_start = microtime(true);

        $healthy = array("fruits", "vegetables", "fiber");
        $yummy   = array("pizza", "beer", "ice cream");

        for($i=0;$i<=1000000;$i++){
            // Method 1
            $phrase  = "You should eat fruits, vegetables, and fiber every day.";
            $phrase = str_replace($healthy, $yummy, $phrase);
        }
        $time_end = microtime(true);
        $time = $time_end - $time_start;
        echo "Did Test 1 in ($time seconds)". PHP_EOL. PHP_EOL;



        $time_start = microtime(true);
        for($i=0;$i<=1000000;$i++){
            // Method2
            $phrase  = "You should eat fruits, vegetables, and fiber every day.";
            $phrase = str_replace("fruits", "pizza", $phrase);
            $phrase = str_replace("vegetables", "beer", $phrase);
            $phrase = str_replace("fiber", "ice cream", $phrase);

        }
        $time_end = microtime(true);
        $time = $time_end - $time_start;
        echo "Did Test 2 in ($time seconds)" . PHP_EOL. PHP_EOL;




        $time_start = microtime(true);
        for($i=0;$i<=1000000;$i++){
            $a = $healthy;
            $b = $yummy;
                foreach ($healthy as $k => $v) {
                  if (strpos($phrase, $healthy[$k]) === FALSE)  
                  unset($a[$k], $b[$k]);
                }                                          
                if ($a) $new_str = str_replace($a, $b, $phrase);

        }
        $time_end = microtime(true);
        $time = $time_end - $time_start;
        echo "Did Test 3 in ($time seconds)". PHP_EOL. PHP_EOL;



        $time_start = microtime(true);
        for($i=0;$i<=1000000;$i++){
            $ree = false;
            foreach ($healthy as $k) {
              if (strpos($phrase, $k) !== FALSE)  { //something to replace
                  $ree = true;
                  break;
              }
            }                                          
            if ($ree === true) {
                $new_str = str_replace($healthy, $yummy, $phrase);
            }
        }
        $time_end = microtime(true);
        $time = $time_end - $time_start;
        echo "Did Test 4 in ($time seconds)". PHP_EOL. PHP_EOL;

Пройти тест 1 в (0,38219690322876 секунд)

Тест 2 в (0,42352104187012 секунд)

Тест 3 в (0,47777700424194 секунд)

Тест 4 в (0,19691610336304 секунд)

...