Почему 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 строка точно такая же, как strpos
ing 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)