为什么mb_strpos()比strpos()慢得多?

Tom*_*ica 19 php regex performance strpos

我曾批评一个答案,暗示preg_match===,为了避免类型不匹配查找字符串偏移时.

不过,后来对答案的作者已经发现,preg_match实际上是显著快于多字节操作mb_strpos.Normal strpos比两个函数都快,但当然不能处理多字节字符串.

据我了解,mb_strpos需要做更多的东西strpos.然而,如果正则表达式可以做到这一点几乎一样快strpos,它是什么,mb_strpos这是否需要这么多的时间?

我怀疑这是一个优化错误.例如,PHP扩展可能比其原生函数慢吗?

mb_strpos($str, "??", 0 ,"GBK"): 15.988190889 (89%)
preg_match("/??/", $str): 1.022506952 (6%)
strpos($str, "dh"): 0.934401989 (5%)
Run Code Online (Sandbox Code Playgroud)

功能运行10 次6次.绝对时间(s)计算函数的10 6次运行的时间总和,而不是一次的平均值.

测试字符串是$str = "??dhgd????";.该测试可以在这里看到(向下滚动跳过测试类).

注意:根据评论员之一(和常识),preg_match比较时也不使用多字节,受到同样的错误风险strpos.

Gum*_*mbo 18

要理解函数具有不同运行时的原因,您需要了解它们实际执行的操作.因为总结起来为"他们寻找干草堆里 "是不够的.

strpos

如果你看的实现strpos,它使用zend_memstr在内部,它实现了一个非常天真的算法用于搜索干草堆:基本上,它使用了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是用来寻找下一个可能的位置大海捞针.这实际上是Boyer-Moore字符串搜索算法的实现.

所以现在我们知道mb_strpos......

  • 如有必要,将字符串转换为UTF-8
  • 知道实际的人物
  • 使用Boyer-Moore搜索算法

preg_match

至于preg_match它,它使用PCRE库.其标准匹配算法使用非确定性有限自动机(NFA)来找到进行模式树的深度优先搜索的匹配.这基本上是一种天真的搜索方法.


hak*_*kre 13

我要离开preg_match,让分析更加突出.

mb_strpos相比较你的观察相对较慢strpos,它会让你假设 - 因为消耗的时间 - mb_strpos不仅仅是strpos.

我认为这种观察是正确的.

然后你问什么是导致时差的"更多".

我试着给出一个简单的答案:"更多"是因为strpos对二进制字符串进行操作(一个字符= 8位= 1个八位字节= 1个字节).mb_strpos对编码的字符序列(几乎所有mb_*函数都可以)进行操作,这些字符序列 可以是X位,甚至可能是每个字符的可变长度.

由于这总是关于特定的字符编码,因此需要首先针对该编码验证haystack以及needle字符串(可能),然后需要在该特定字符编码中完成查找字符串位置的整个操作.

这是翻译工作,并且 - 取决于编码 - 还需要特定的搜索算法.

接下来,mb扩展还需要在内存中具有一些结构来组织不同的字符编码,无论是转换表和/或特定算法.请参阅您注入的额外参数 - 例如编码的名称.

这比仅进行简单的逐字节比较要多得多.

例如,当您需要对某个字符进行编码或解码时,GBK字符编码非常有趣.在这种情况下,mb字符串函数需要考虑所有这些细节,以确定字符是否以及在哪个位置.由于PHP在用户空间中只有二进制字符串,您可以从中调用该函数,因此需要在每个函数调用上完成整个工作.

为了进一步说明这一点,如果查看支持的编码列表(mb_list_encodings),您还可以找到一些类似BASE64,UUENCODE,HTML-ENTITIESQuoted-Printable的内容.正如您可能想象的那样,所有这些都有不同的处

例如,单个数字HTML实体最大可达1024字节(如果不是更大).我知道和喜爱的一个极端例子就是这个.但是,对于该编码,它必须由mb_strpos算法处理.


LSe*_*rni 9

缓慢的原因

在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;
}
Run Code Online (Sandbox Code Playgroud)

除非needle是默认格式,否则将其编码为默认格式:

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;
}
Run Code Online (Sandbox Code Playgroud)

根据快速检查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]
Run Code Online (Sandbox Code Playgroud)

这似乎与OP的mb_strpos对战时间一致strpos.

编码不考虑,mb_strpos"荷兰国际集团字符串是完全一样的strpos"荷兰国际集团一个略长的字符串.好吧,如果你有一些非常笨拙的字符串,那么这个字符串的长度可以达到四倍,但即使这样,你也会得到一个四倍的延迟,而不是二十倍.编码时间增加了5-6倍的减速.

加速mb_strpos......

所以,你可以做什么?您可以跳过这两个步骤,确保您已在内部使用"基本"格式的字符串mbfl*进行转换和比较,即mbfl_no_encoding_utf8(UTF-8):

  • 保持您的数据为UTF-8.
  • 尽快将用户输入转换为UTF-8.
  • 如有必要,转换回客户端编码(如果需要).

那你的伪代码:

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

$res = mb_strpos($haystack, $needle, 0, $Encoding);
Run Code Online (Sandbox Code Playgroud)

变为:

$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);
Run Code Online (Sandbox Code Playgroud)

......什么时候值得

当然,如果整个UTF-8基础的"设置时间"和维护明显小于在每个mb_*功能中隐式进行转换的"运行时间",这是很方便的.