Почему mb strpos () настолько значительно медленнее, чем strpos ()?

я критиковал ответ, который предложил preg_match над === при поиске смещений подстроки во избежание несоответствия типов.

однако позже автор ответа обнаружил, что preg_match на самом деле значительно быстрее, чем многобайтовая работа mb_strpos. Нормальный strpos быстрее, чем обе функции, но, конечно, не может иметь дело с многобайтовыми строками.

Я понимаю, что mb_strpos нужно сделать что-то больше чем strpos. Однако, если regex может это сделать почти так же быстро as strpos, что mb_strpos это занимает так много времени?

у меня есть сильное подозрение, что это ошибка оптимизации. Могут ли, например, расширения PHP быть медленнее, чем его собственные функции?

mb_strpos($str, "颜色", 0 ,"GBK"): 15.988190889 (89%)
preg_match("/颜色/", $str): 1.022506952 (6%)
strpos($str, "dh"): 0.934401989 (5%)

функции были запущены 106 раза. Абсолютное время(ы) составляет сумму времени 106 запуск функции, а не в среднем для одного.

тестовая строка $str = "代码dhgd颜色代码";. The

4 ответов


чтобы понять, почему функции имеют другую среду выполнения, вам нужно понять, что они на самом деле делают. Потому что суммируя их как " они ищут игла на сена’ недостаточно.

strpos

если вы посмотрите на реализация strpos, он использует zend_memstr внутри, который реализует довольно наивный алгоритм поиска игла in сена: в основном, он использует memchr найти первый байт игла на сена и затем использует memcmp чтобы проверить, все ли игла начинается с этой позиции. Если нет, он повторяет поиск первого байта игла из позиции предыдущего совпадения первого байта.

зная это, мы можем сказать, что strpos выполняет только поиск последовательности байтов в байте последовательность использовать наивный алгоритм поиска.

mb_strpos

эта функция является многобайтовым аналогом strpos. Это делает поиск немного более сложным, поскольку вы не можете просто смотреть на байты, не зная, к какому символу они принадлежат.

mb_strpos использует mbfl_strpos, что делает намного больше по сравнению с простым алгоритмом zend_memstr, это похоже на 200 строк сложного кода (mbfl_strpos) сравненный до 30 линий Слика код (zend_memstr).

мы можем пропустить часть, где как игла и сена при необходимости преобразуются в UTF-8 и подошли к большой кусок кода.

сначала у нас есть два цикла настройки, а затем есть цикл, который продолжает указатель в соответствии с заданным смещение где вы можете видеть, что они знают о фактических символах и как они пропускают весь кодированный UTF-8 символы: поскольку UTF-8-это кодировка символов переменной ширины, где первый байт каждого закодированного символа обозначает всю длину закодированного символа. Эта информация хранится в u8_tbl массив.

наконец,цикл, где происходит фактический поиск. И здесь у нас есть что-то интересное, потому что тест на игла в определенном положении в сена пробуется в обратном порядке. А если один байт не совпал, то скачок таблица jtbl используется для поиска следующей возможной позиции для игла на сена. Это на самом деле реализация алгоритм поиска строк Бойера-Мура.

Итак, теперь мы знаем, что mb_strpos ...

  • преобразует строки в UTF-8, Если необходимо
  • осознает фактические символы
  • использует поиск Boyer-Moore алгоритм

preg_match

как preg_match использует библиотека PCRE. его стандартный алгоритм сопоставления использует недетерминированный конечный автомат (NFA) чтобы найти совпадение, проводя поиск по глубине дерева шаблонов. Это, по сути, наивный подход.


Я preg_match для того чтобы сделать анализ более акцентирован.

принял ваше замечание, что mb_strpos относительно медленнее по сравнению с strpos, это приводит к предположению, что из - за потребленный времени mb_strpos не более strpos.

Я думаю, что это наблюдение верно.

затем вы спросили, Что такое "больше", что вызывает разницу во времени.

Я пытаюсь дать простой ответ: что "больше" - потому что strpos работает на двоичных строк (один символ = 8 бит = 1 байт = 1 байт). mb_strpos работает на кодированных последовательностях символов (как почти все mb_* функции делают), которые могут быть x битами, возможно, даже переменной длины на каждый символ.

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

это работа перевода и-в зависимости от кодирования-также требует определенного алгоритма поиска.

рядом с этим расширение mb также должно иметь некоторые структуры в памяти для организации различных кодировок символов, будь то таблицы перевода и / или конкретные алгоритмы. См. дополнительный параметр, который вы вводите - например, имя кодировки.

это гораздо больше работы, чем просто делаю простые байтовые сравнения.

например кодировка символов GBK довольно интересно, когда нужно кодировать или декодировать определенный характер. Строковая функция mb в этом случае должна учитывать все эти особенности, чтобы узнать, находится ли и в какой позиции символ. Поскольку PHP имеет только двоичные строки в userland, из которых вы бы вызвали эту функцию, вся работа должна быть выполнена над каждой отдельной функцией вызов.

чтобы проиллюстрировать это еще больше, если вы посмотрите список поддерживаемых кодировок (mb_list_encodings), вы также можете найти некоторые, как в base64, UUENCODE, HTML-СУЩНОСТИ и Quoted-Printable. Как вы можете себе представить, все они обрабатываются по-разному.

например, один числовой HTML-объект может быть размером до 1024 байт, если не больше. Крайний пример, который я знаю и люблю этот. Однако для этой кодировки он должен обрабатываться .


причина медлительности

взглянув на исходные файлы 5.5.6 PHP, задержка, по-видимому, возникает по большей части в mbfilter.c, где - как hakre предположить - и сена и иглы должны быть проверены и преобразованы, каждый раз mb_strpos (или, я думаю, большинство mb_* семья) вызывается:

если haystack не имеет формата по умолчанию, Закодируйте его по умолчанию формат:

if (haystack->no_encoding != mbfl_no_encoding_utf8) {
        mbfl_string_init(&_haystack_u8);
        haystack_u8 = mbfl_convert_encoding(haystack, &_haystack_u8, mbfl_no_encoding_utf8);
        if (haystack_u8 == NULL) {
                result = -4;
                goto out;
        }
} else {
        haystack_u8 = haystack;
}

если игла не находится в формате по умолчанию, Закодируйте ее в формат по умолчанию:

if (needle->no_encoding != mbfl_no_encoding_utf8) {
        mbfl_string_init(&_needle_u8);
        needle_u8 = mbfl_convert_encoding(needle, &_needle_u8, mbfl_no_encoding_utf8);
        if (needle_u8 == NULL) {
                result = -4;
                goto out;
        }
} else {
        needle_u8 = needle;
}

согласно быстрой проверке с valgrind, преобразование кодировки составляет огромную часть mb_strposвремя выполнения, около 84% от общего числа, или пять шестых:

218,552,085  ext/mbstring/libmbfl/mbfl/mbfilter.c:mbfl_strpos [/usr/src/php-5.5.6/sapi/cli/php]
183,812,085  ext/mbstring/libmbfl/mbfl/mbfilter.c:mbfl_convert_encoding [/usr/src/php-5.5.6/sapi/cli/php]

что, по-видимому, согласуется с таймингами OP mb_strpos и strpos.

кодирование не является, mb_strpos ' ing строка точно такая же, как strposing a чуть больше строка. Хорошо, строка до четырех раз дольше если у вас действительно неудобные струны, но даже тогда вы получите задержку в четыре раза, а не в двадцать. Дополнительное замедление 5-6X возникает из-за времени кодирования.

ускорение mb_strpos...

так что вы можете сделать? Вы можете пропустить эти два шага, гарантировав, что у вас есть внутренние строки уже в" базовом " формате, в котором mbfl* сделайте преобразование и сравните, что mbfl_no_encoding_utf8 (UTF-8):

  • храните свои данные в UTF-8.
  • преобразуйте пользовательский ввод в UTF-8 как можно скорее.
  • преобразовать, при необходимости, обратно в клиентскую кодировку, если это необходимо.

тогда ваш псевдо-код:

$haystack = "...";
$needle   = "...";

$res = mb_strpos($haystack, $needle, 0, $Encoding);

будет:

$haystack = "...";
$needle   = "...";

mb_internal_encoding('UTF-8') or die("Cannot set encoding");
$haystack   = mb_convert_encoding($haystack, 'UTF-8' [, $SourceEncoding]);
$needle     = mb_convert_encoding($needle, 'UTF-8', [, $SourceEncoding]);

$res = mb_strpos($haystack, $needle, 0);

...когда это того стоит

конечно, это удобно, только если "время установки "и обслуживание всей базы UTF-8 заметно меньше, чем" время выполнения " выполнения преобразований неявно в каждом


проблемы с mb_ производительность может быть вызвана messed php-mbstring установка пакета (в linux). Установка его явно для точной версии установки php помогла мне.

sudo apt-get install php7.1-mbstring

...

Before: Time: 16.17 seconds, Memory: 36.00MB OK (3093 tests, 40272 assertions)
After:  Time:  1.81 seconds, Memory: 36.00MB OK (3093 tests, 40272 assertions)