UTF-8: как быстрее измерить длину строки в PHP

Я потихоньку буду писать как получается оптимизировать самые важные функции работы с UTF-8 в PHP. Оформлять буду как продолжение своей эпопеи перевода наше внутренней «Вики» на UTF-8 (в ближайшее время добавлю оглавление).

Сейчас, после перевода «Вики» мы столкнулись с тем, что некоторые операции занимают непозволительно много времени. Кое-какие проблемы решаются моими итераторами для строки (прямым и обратным), но и другие оптимизации приходится делать.

Простые оптимизации я уже применил: выкинул из класса UTF-8 проверки на self::ON (по этой константе я определял включена ли в проекте поддержка UTF-8, сейчас в ней необходимости уже нет) и сделал другие микрооптимизации, теперь пора посмотреть на методы моего класса UTF.

Одна из самых частоиспользуемых функций — strlen, которая определяет длину строки. Я всегда считал, что самый быстрый способ подсчитать длину UTF-строки в PHP, это вызывать strlen(utf8_decode(…)).

Мои тесты показывают что это не так. Самый быстрый способ (по крайней мере в PHP 5.3.2) — это mb_strlen, strlen/utf8_decode — на втором месте, потом идёт sizeof+preg_split, а самый медленный — iconv_strlen.

Для интереса я вкомпилил в PHP функцию для быстрого подсчёта длины строки в UTF-8, найденную в интернете, выигрыш по сравнению с mb_strlen незначителен:
bolk-dev ~/uni/ $ php ../test/test_strlen.php | sort -k2 -t:
Fast UTF-8: 0.68232107162476
mb_strlen: 0.88710689544678
strlen/utf8_decode: 1.5211808681488
Это тысяча итераций на файле в 400КБ.

Я оставил функцию mb_strlen.

Добавлено позднее: другая быстрая функция дала ещё худший результат: 0,79, против прежнего варианта в 0,68.

Добавлено ещё позже: а вот третья функция показала действительно впечатляющие результаты:
bolk-dev ~/uni/ $ php ../test/test_strlen.php | sort -k2 -t:
More Fast UTF-8:  0.12948799133301
mb_strlen: 0.88710689544678
strlen/utf8_decode: 1.5211808681488
Это уже дело — она почти в 7 раз быстрее лучшего результата PHP.

Вкратце, алгоритм такой: строка циклом выравнивается по границе четырёх (на 64-битных системах — 8) байт, далее читается сразу по четыре (или восемь) байт и внутри каждого кусочка одним выражением считается сколько там байт, с которых начинается символ. Поэтому на моей 64-битной системе семикратный выигрыш (на 32 битах будет примерно в три раза).

Добавлено через несколько дней: есть и ещё более быстрая функция.
16 декабря 2010 15:45

upas (upas.ya.ru)
16 декабря 2010, 16:24

хм. а на длинных строках (100кб например) какое распределение скоростей?
интерес академический.

bolk (bolknote.ru)
16 декабря 2010, 16:50, ответ предназначен upas (upas.ya.ru):

А 400КБ это недостаточно длинная строка?

tony2001 (инкогнито)
16 декабря 2010, 17:32

Мне думается, что для высокой нагрузки и конкретных задач имеет смысл писать свои решения на C.
PHP - это универсальное решение, mbstring использует libmbfl, которая так же универсальное решение.
Если вам нужно работать только с UTF8, то написать экстеншен на С для работы только с UTF8 - это довольно небольшая задача. А если у вас действительно много работы со строками, то вынос этой задачи в спец. экстеншен - это логичный (и единственно верный!=)) выход.

kipelovets (инкогнито)
16 декабря 2010, 17:34

по ссылке на третью функцию прекрасное:
wonderful example of using a goto to jump from the middle of one loop into the middle of another

bolk (bolknote.ru)
16 декабря 2010, 17:37, ответ предназначен tony2001

Я так и собираюсь сделать :) Назвал модуль FastUTF (futf), буду потихоньку писать.

tony2001 (инкогнито)
16 декабря 2010, 17:47, ответ предназначен kipelovets

oh, wow!

upas (upas.ya.ru)
16 декабря 2010, 17:58

А 400КБ это недостаточно длинная строка?
ой. 8)
а на 3х мегабайтах?

bolk (bolknote.ru)
16 декабря 2010, 18:52, ответ предназначен upas (upas.ya.ru):

Вот так выглядит:

mb_strlen: 6.9003229141235
strlen/utf8_decode: 12.082993984222
More Fast UTF-8: 0.97903895378113

Чистяков Денис (инкогнито)
11 февраля 2011, 10:56

А почему в тестах не учавствовола: *iconv_strlen*?

bolk (bolknote.ru)
11 февраля 2011, 14:08, ответ предназначен Чистякову Денису

iconv очень медленная библиотека.

rin-nas (openid.yandex.ru/rin-nas/)
3 июня 2011, 01:41

Выложил свою библиотеку: http://code.google.com/p/php5-utf8/
Для PHP 5.2.x strlen/utf8_decode было быстрее, чем mb_strlen.
Сделаю у себя тесты, при необходимости внесу правки.

bolk (bolknote.ru)
3 июня 2011, 11:03, ответ предназначен rin-nas (openid.yandex.ru/rin-nas/):

Ок, круто!

rin-nas (openid.yandex.ru/rin-nas/)
5 июня 2011, 17:18

Сделал тесты, внёс правки, несколько методов ускорилось
http://code.google.com/p/php5-utf8/source/list

Euphoria (инкогнито)
9 июля 2012, 15:14

# utf8len
function utf8len($x){preg_match_all('/./iu', $x, $a);return count($a[0]);}

А как вам этот вариант?

bolk (bolknote.ru)
9 июля 2012, 16:04, ответ предназначен Euphoria

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

Euphoria (инкогнито)
9 июля 2012, 17:07

в 30 раз медленнее, чем strlen(utf8_decode

Ваше имя или адрес блога (можно OpenID):

Текст вашего комментария, не HTML:

Кому бы вы хотели ответить (или кликните на его аватару)