php 7 mb_(多字节)函数比 5.3 慢 ~60%(windows 唯一问题)
php 7 mb_ (multibyte) functions are ~60% slower than in 5.3 (windows only issue)
我的应用程序大量使用了 mb_ 字符串函数,切换到 php7 导致应用程序整体变慢。我追踪到 mb_ 字符串函数的问题。以下是基准代码和结果:
$time = microtime();
$time = explode(' ', $time);
$start = $time[1] + $time[0];
$startms = $time[0];
for ($i=0; $i<100000; $i++) {
$a = mb_strlen("fdsfdssdfoifjosdifjosdifjosdij:ά", "UTF-8");
}
$time = microtime();
$time = explode(' ', $time);
$finish = $time[1] + $time[0];
$finishms = $time[0];
$total_time = round(($finish - $start), 4);
echo "mb_strlen: " . $total_time*1000 ." milliseconds<br/>";
$time = microtime();
$time = explode(' ', $time);
$start = $time[1] + $time[0];
$startms = $time[0];
for ($i=0; $i<100000; $i++) {
$a = mb_stripos("fdsfdssdfoifjosdifjosdifjosdij:ά", "α", 0, "UTF-8");
}
$time = microtime();
$time = explode(' ', $time);
$finish = $time[1] + $time[0];
$finishms = $time[0];
$total_time = round(($finish - $start), 4);
echo "mb_stripos: " . $total_time*1000 ." milliseconds<br/>";
$time = microtime();
$time = explode(' ', $time);
$start = $time[1] + $time[0];
$startms = $time[0];
for ($i=0; $i<100000; $i++) {
$a = mb_substr("fdsfdssdfoifjosdifjosdifjosdij:ά", $i, 1, "UTF-8");
}
$time = microtime();
$time = explode(' ', $time);
$finish = $time[1] + $time[0];
$finishms = $time[0];
$total_time = round(($finish - $start), 4);
echo "mb_substr: " . $total_time*1000 ." milliseconds<br/>";
平台是Windows7 64位,IIS 7.5:
php 5.3.28
mb_strlen: 250 milliseconds
mb_stripos: 3078.1 milliseconds
mb_substr: 281.3 milliseconds
php 7.1.1
mb_strlen: 406.3 milliseconds
mb_stripos: 4796.9 milliseconds
mb_substr: 421.9 milliseconds
我不知道是我的设置有误还是什么,但多字节函数应该更慢似乎是不可思议的。关于为什么以及如何解决这个问题的任何想法?提前谢谢你。
编辑:正如 apokryfos 的评论所暗示的,这可能是 Windows 唯一的问题。
这听起来像是一个 "performance regression" 错误。可能应该提交错误报告,以便 php 核心开发人员可以在 bugs.php.net
查看它
与此同时,我在您的代码片段中看到您只使用 UTF-8。只要你专门使用 UTF-8,你就可以使用 preg_ 来加速它,它只支持一种 unicode 字符集:UTF-8
。这是我的尝试:
function _mb_strlen(string $str, string $encoding = 'UTF-8'): int {
assert ( $encoding === 'UTF-8' );
preg_match ( '/.$/u', $str, $matches, PREG_OFFSET_CAPTURE );
return empty ( $matches ) ? 0 : ($matches [0] [1]) + 1;
}
function _mb_stripos(string $haystack, string $needle, int $offset = 0, string $encoding = 'UTF-8') {
assert ( $encoding === 'UTF-8' );
if ($offset !== 0) {
throw new LogicException ( 'NOT IMPLEMENTED' );
}
preg_match ( '/' . preg_quote ( $needle ) . '/ui', $haystack, $matches, PREG_OFFSET_CAPTURE );
return empty ( $matches ) ? false : $matches [0] [1];
}
function _mb_substr(string $str, int $start, int $length = NULL, string $encoding = 'UTF-8'): string {
assert ( $encoding === 'UTF-8' );
if ($start < 0) {
throw new LogicException ( 'NOT IMPLEMENTED' );
} elseif ($start > 0) {
$rex = '/.{' . $start . '}(.{0,';
} else {
$rex = '/(.{0,';
}
if ($length !== NULL) {
$rex .= $length;
}
$rex .= '})/u';
preg_match ( $rex, $str, $matches );
// var_dump ( $rex, $matches );
return empty ( $matches ) ? '' : $matches [1];
}
这是我在 php 7.0 和 debian 9 linux(内核 4.9)上进行 100,000 次迭代的基准测试结果:
mb_strlen 变慢了,从大约 60 毫秒到 100 毫秒
mb_stripos 快了很多,从大约 1400 毫秒到 75 毫秒
mb_substr 慢了很多,从大约 47 毫秒到大约 800 毫秒
- 但我建议你在 windows 上重新 运行 这些测试,正如你所说,你认为这可能是 windows 独有的问题
另请注意,这些函数的功能并不完整,您可以从它们抛出的 LogicException 中看出这一点。
另请注意,由于 preg_ 的限制,我不得不在
中将 mb_substr 的迭代次数限制在 65000 次
for($i = 0; $i < 65000; $i ++) {
$a = mb_substr ( "fdsfdssdfoifjosdifjosdifjosdij:ά", $i, 1, "UTF-8" );
}
因为,如果你要求 preg 查找长度超过 65,000 个字符的字符串,它会报错...
另请注意,您的基准代码可以变得更加简单,所有这些
$time = microtime();
$time = explode(' ', $time);
$start = $time[1] + $time[0];
$startms = $time[0];
for ($i=0; $i<100000; $i++) {
$a = mb_strlen("fdsfdssdfoifjosdifjosdifjosdij:ά", "UTF-8");
}
$time = microtime();
$time = explode(' ', $time);
$finish = $time[1] + $time[0];
$finishms = $time[0];
$total_time = round(($finish - $start), 4);
echo "mb_strlen: " . $total_time*1000 ." milliseconds<br/>";
可以简单地替换为
$starttime=microtime(true);
for ($i=0; $i<100000; $i++) {
$a = mb_strlen("fdsfdssdfoifjosdifjosdifjosdij:ά", "UTF-8");
}
$endtime=microtime(true);
echo "mb_strlen: " . number_format(($endtime-$starttime),3) ." seconds<br/>";
输出如下:mb_strlen: 0.085 seconds
(这意味着大约 85 毫秒)
或
echo "mb_strlen: " . number_format(($endtime - $starttime) * 1000),2) . " milliseconds<br/>";
(我可以大胆猜测它与 realloc() 性能有关,其中 linux 踩踏 windows,但我没有证据)
我可以确认你的结果在 Windows 7 上是可重现的。
经过一些实验,我找到了一个快速的解决方案,IMO应该连效果都没有。
从 mb_strlen() 函数签名可以看出,
如果省略编码参数,它将使用内部编码。
这也适用于您使用的其他功能。
mixed mb_strlen ( string $str [, string $encoding = mb_internal_encoding() ] )
我发现奇怪的是,如果您通过调用 mb_internal_encoding("UTF-8")
将内部编码设置为 UTF-8 并省略编码参数,
功能变得更快。
PHP 5.5 结果:
5.5.12
with encoding parameter:
- mb_strlen: 172 ms, result: 5
- mb_substr: 218 ms, result: う
- mb_strpos: 218 ms, result: 3
- mb_stripos: 1,669 ms, result: 3
- mb_strrpos: 234 ms, result: 3
- mb_strripos: 1,685 ms, result: 3
with internal encoding:
- mb_strlen: 47 ms, result: 5
- mb_substr: 78 ms, result: う
- mb_strpos: 62 ms, result: 3
- mb_stripos: 1,669 ms, result: 3
- mb_strrpos: 94 ms, result: 3
- mb_strripos: 1,669 ms, result: 3
PHP 7.0 结果:
7.0.12
with encoding parameter:
- mb_strlen: 640 ms, result: 5
- mb_substr: 702 ms, result: う
- mb_strpos: 686 ms, result: 3
- mb_stripos: 7,067 ms, result: 3
- mb_strrpos: 749 ms, result: 3
- mb_strripos: 7,130 ms, result: 3
with internal encoding:
- mb_strlen: 31 ms, result: 5
- mb_substr: 31 ms, result: う
- mb_strpos: 47 ms, result: 3
- mb_stripos: 7,270 ms, result: 3
- mb_strrpos: 62 ms, result: 3
- mb_strripos: 7,116 ms, result: 3
不幸的是,这个快速解决方案并不完美,因为 mb_stripos()
和 mb_strripos()
似乎没有受到影响。
他们仍然很慢。
这是代码(缩写):
echo PHP_VERSION."\n";
echo "\nwith encoding parameter:\n";
$t = microtime(true)*1000;
for($i=0; $i<100000; $i++){
$n = mb_strlen("あえいおう","UTF-8");
}
$t = microtime(true)*1000-$t;
echo "- mb_strlen: ".number_format($t)." ms, result: {$n}\n";
$t = microtime(true)*1000;
for($i=0; $i<100000; $i++){
$n = mb_substr("あえいおう",-1,1,"UTF-8");
}
$t = microtime(true)*1000-$t;
echo "- mb_substr: ".number_format($t)." ms, result: {$n}\n";
//set internal encoding
//and omit encoding parameter
mb_internal_encoding("UTF-8");
echo "\nwith internal encoding:\n";
$t = microtime(true)*1000;
for($i=0; $i<100000; $i++){
$n = mb_strlen("あえいおう");
}
$t = microtime(true)*1000-$t;
echo "- mb_strlen: ".number_format($t)." ms, result: {$n}\n";
$t = microtime(true)*1000;
for($i=0; $i<100000; $i++){
$n = mb_substr("あえいおう",-1,1);
}
$t = microtime(true)*1000-$t;
echo "- mb_substr: ".number_format($t)." ms, result: {$n}\n";
我的应用程序大量使用了 mb_ 字符串函数,切换到 php7 导致应用程序整体变慢。我追踪到 mb_ 字符串函数的问题。以下是基准代码和结果:
$time = microtime();
$time = explode(' ', $time);
$start = $time[1] + $time[0];
$startms = $time[0];
for ($i=0; $i<100000; $i++) {
$a = mb_strlen("fdsfdssdfoifjosdifjosdifjosdij:ά", "UTF-8");
}
$time = microtime();
$time = explode(' ', $time);
$finish = $time[1] + $time[0];
$finishms = $time[0];
$total_time = round(($finish - $start), 4);
echo "mb_strlen: " . $total_time*1000 ." milliseconds<br/>";
$time = microtime();
$time = explode(' ', $time);
$start = $time[1] + $time[0];
$startms = $time[0];
for ($i=0; $i<100000; $i++) {
$a = mb_stripos("fdsfdssdfoifjosdifjosdifjosdij:ά", "α", 0, "UTF-8");
}
$time = microtime();
$time = explode(' ', $time);
$finish = $time[1] + $time[0];
$finishms = $time[0];
$total_time = round(($finish - $start), 4);
echo "mb_stripos: " . $total_time*1000 ." milliseconds<br/>";
$time = microtime();
$time = explode(' ', $time);
$start = $time[1] + $time[0];
$startms = $time[0];
for ($i=0; $i<100000; $i++) {
$a = mb_substr("fdsfdssdfoifjosdifjosdifjosdij:ά", $i, 1, "UTF-8");
}
$time = microtime();
$time = explode(' ', $time);
$finish = $time[1] + $time[0];
$finishms = $time[0];
$total_time = round(($finish - $start), 4);
echo "mb_substr: " . $total_time*1000 ." milliseconds<br/>";
平台是Windows7 64位,IIS 7.5:
php 5.3.28
mb_strlen: 250 milliseconds
mb_stripos: 3078.1 milliseconds
mb_substr: 281.3 milliseconds
php 7.1.1
mb_strlen: 406.3 milliseconds
mb_stripos: 4796.9 milliseconds
mb_substr: 421.9 milliseconds
我不知道是我的设置有误还是什么,但多字节函数应该更慢似乎是不可思议的。关于为什么以及如何解决这个问题的任何想法?提前谢谢你。
编辑:正如 apokryfos 的评论所暗示的,这可能是 Windows 唯一的问题。
这听起来像是一个 "performance regression" 错误。可能应该提交错误报告,以便 php 核心开发人员可以在 bugs.php.net
查看它与此同时,我在您的代码片段中看到您只使用 UTF-8。只要你专门使用 UTF-8,你就可以使用 preg_ 来加速它,它只支持一种 unicode 字符集:UTF-8
。这是我的尝试:
function _mb_strlen(string $str, string $encoding = 'UTF-8'): int {
assert ( $encoding === 'UTF-8' );
preg_match ( '/.$/u', $str, $matches, PREG_OFFSET_CAPTURE );
return empty ( $matches ) ? 0 : ($matches [0] [1]) + 1;
}
function _mb_stripos(string $haystack, string $needle, int $offset = 0, string $encoding = 'UTF-8') {
assert ( $encoding === 'UTF-8' );
if ($offset !== 0) {
throw new LogicException ( 'NOT IMPLEMENTED' );
}
preg_match ( '/' . preg_quote ( $needle ) . '/ui', $haystack, $matches, PREG_OFFSET_CAPTURE );
return empty ( $matches ) ? false : $matches [0] [1];
}
function _mb_substr(string $str, int $start, int $length = NULL, string $encoding = 'UTF-8'): string {
assert ( $encoding === 'UTF-8' );
if ($start < 0) {
throw new LogicException ( 'NOT IMPLEMENTED' );
} elseif ($start > 0) {
$rex = '/.{' . $start . '}(.{0,';
} else {
$rex = '/(.{0,';
}
if ($length !== NULL) {
$rex .= $length;
}
$rex .= '})/u';
preg_match ( $rex, $str, $matches );
// var_dump ( $rex, $matches );
return empty ( $matches ) ? '' : $matches [1];
}
这是我在 php 7.0 和 debian 9 linux(内核 4.9)上进行 100,000 次迭代的基准测试结果:
mb_strlen 变慢了,从大约 60 毫秒到 100 毫秒
mb_stripos 快了很多,从大约 1400 毫秒到 75 毫秒
mb_substr 慢了很多,从大约 47 毫秒到大约 800 毫秒
- 但我建议你在 windows 上重新 运行 这些测试,正如你所说,你认为这可能是 windows 独有的问题
另请注意,这些函数的功能并不完整,您可以从它们抛出的 LogicException 中看出这一点。
另请注意,由于 preg_ 的限制,我不得不在
中将 mb_substr 的迭代次数限制在 65000 次for($i = 0; $i < 65000; $i ++) {
$a = mb_substr ( "fdsfdssdfoifjosdifjosdifjosdij:ά", $i, 1, "UTF-8" );
}
因为,如果你要求 preg 查找长度超过 65,000 个字符的字符串,它会报错...
另请注意,您的基准代码可以变得更加简单,所有这些
$time = microtime();
$time = explode(' ', $time);
$start = $time[1] + $time[0];
$startms = $time[0];
for ($i=0; $i<100000; $i++) {
$a = mb_strlen("fdsfdssdfoifjosdifjosdifjosdij:ά", "UTF-8");
}
$time = microtime();
$time = explode(' ', $time);
$finish = $time[1] + $time[0];
$finishms = $time[0];
$total_time = round(($finish - $start), 4);
echo "mb_strlen: " . $total_time*1000 ." milliseconds<br/>";
可以简单地替换为
$starttime=microtime(true);
for ($i=0; $i<100000; $i++) {
$a = mb_strlen("fdsfdssdfoifjosdifjosdifjosdij:ά", "UTF-8");
}
$endtime=microtime(true);
echo "mb_strlen: " . number_format(($endtime-$starttime),3) ." seconds<br/>";
输出如下:mb_strlen: 0.085 seconds
(这意味着大约 85 毫秒)
或
echo "mb_strlen: " . number_format(($endtime - $starttime) * 1000),2) . " milliseconds<br/>";
(我可以大胆猜测它与 realloc() 性能有关,其中 linux 踩踏 windows,但我没有证据)
我可以确认你的结果在 Windows 7 上是可重现的。 经过一些实验,我找到了一个快速的解决方案,IMO应该连效果都没有。
从 mb_strlen() 函数签名可以看出, 如果省略编码参数,它将使用内部编码。 这也适用于您使用的其他功能。
mixed mb_strlen ( string $str [, string $encoding = mb_internal_encoding() ] )
我发现奇怪的是,如果您通过调用 mb_internal_encoding("UTF-8")
将内部编码设置为 UTF-8 并省略编码参数,
功能变得更快。
PHP 5.5 结果:
5.5.12
with encoding parameter:
- mb_strlen: 172 ms, result: 5
- mb_substr: 218 ms, result: う
- mb_strpos: 218 ms, result: 3
- mb_stripos: 1,669 ms, result: 3
- mb_strrpos: 234 ms, result: 3
- mb_strripos: 1,685 ms, result: 3
with internal encoding:
- mb_strlen: 47 ms, result: 5
- mb_substr: 78 ms, result: う
- mb_strpos: 62 ms, result: 3
- mb_stripos: 1,669 ms, result: 3
- mb_strrpos: 94 ms, result: 3
- mb_strripos: 1,669 ms, result: 3
PHP 7.0 结果:
7.0.12
with encoding parameter:
- mb_strlen: 640 ms, result: 5
- mb_substr: 702 ms, result: う
- mb_strpos: 686 ms, result: 3
- mb_stripos: 7,067 ms, result: 3
- mb_strrpos: 749 ms, result: 3
- mb_strripos: 7,130 ms, result: 3
with internal encoding:
- mb_strlen: 31 ms, result: 5
- mb_substr: 31 ms, result: う
- mb_strpos: 47 ms, result: 3
- mb_stripos: 7,270 ms, result: 3
- mb_strrpos: 62 ms, result: 3
- mb_strripos: 7,116 ms, result: 3
不幸的是,这个快速解决方案并不完美,因为 mb_stripos()
和 mb_strripos()
似乎没有受到影响。
他们仍然很慢。
这是代码(缩写):
echo PHP_VERSION."\n";
echo "\nwith encoding parameter:\n";
$t = microtime(true)*1000;
for($i=0; $i<100000; $i++){
$n = mb_strlen("あえいおう","UTF-8");
}
$t = microtime(true)*1000-$t;
echo "- mb_strlen: ".number_format($t)." ms, result: {$n}\n";
$t = microtime(true)*1000;
for($i=0; $i<100000; $i++){
$n = mb_substr("あえいおう",-1,1,"UTF-8");
}
$t = microtime(true)*1000-$t;
echo "- mb_substr: ".number_format($t)." ms, result: {$n}\n";
//set internal encoding
//and omit encoding parameter
mb_internal_encoding("UTF-8");
echo "\nwith internal encoding:\n";
$t = microtime(true)*1000;
for($i=0; $i<100000; $i++){
$n = mb_strlen("あえいおう");
}
$t = microtime(true)*1000-$t;
echo "- mb_strlen: ".number_format($t)." ms, result: {$n}\n";
$t = microtime(true)*1000;
for($i=0; $i<100000; $i++){
$n = mb_substr("あえいおう",-1,1);
}
$t = microtime(true)*1000-$t;
echo "- mb_substr: ".number_format($t)." ms, result: {$n}\n";