在发现子字符串偏移时,我曾批评preg_match
建议===
超过preg_match
,以避免类型不匹配。
然而,后来答案的作者发现,mb_strpos
实际上显着比多字节操作strpos
更快。正常mb_strpos
比两个函数都快,但当然不能处理多字节字符串。
我了解strpos
需要更多而不是strpos
。但是,如果正则表达式可以几乎和mb_strpos
一样快那么mb_strpos($str, "颜色", 0 ,"GBK"): 15.988190889 (89%)
preg_match("/颜色/", $str): 1.022506952 (6%)
strpos($str, "dh"): 0.934401989 (5%)
会花多少时间呢?
我怀疑这是一个优化错误。例如,PHP扩展可能比其原生函数慢吗?
$str = "代码dhgd颜色代码";
功能运行10 6 次。绝对时间(s)计算函数的10 6 运行的时间总和,而不是一个函数的平均值。
测试字符串为preg_match
。 an answer(向下滚动以跳过测试类)。
注意:根据其中一位评论员(以及常识),strpos
在比较时也不使用多字节,与{{1}存在相同的错误风险}}
答案 0 :(得分:17)
要了解函数具有不同运行时的原因,您需要了解它们实际执行的操作。因为将它们总结为'他们在 haystack 中搜索 needle 是不够的。
strpos
如果您查看implementation of strpos
,则会使用zend_memstr
internally,它会实现漂亮naive algorithm for searching for needle in haystack:基本上,它使用memchr
来查找的第一个字节 haystack 中的针,然后使用memcmp
检查整个针是否从该位置开始。如果没有,它会重复从第一个字节的上一个匹配位置搜索 needle 的第一个字节。
了解这一点,我们可以说 strpos
只使用朴素的搜索算法搜索字节序列中的字节序列。
mb_strpos
此函数是strpos
的多字节对应函数。这使得搜索变得更加复杂,因为您无法在不知道它们属于哪个字符的情况下查看字节。
mb_strpos
使用mbfl_strpos
,与zend_memstr
的简单算法相比,它做得更多,就像200行复杂代码(mbfl_strpos
)相比,30光滑的代码行(zend_memstr
)。
我们可以跳过part where both needle and haystack are converted to UTF-8 if necessary,然后转到major chunk of code。
首先我们有两个设置循环,然后有loop that proceeds the pointer according to the given offset,你可以看到它们知道实际字符以及它们如何跳过整个编码的UTF-8字符:因为UTF-8是一个可变宽度的字符编码,其中每个编码字符的第一个字节表示编码字符的整个长度。此信息存储在u8_tbl
数组中。
最后,loop where the actual search happens。在这里我们有一些有趣的东西,因为 haystack 中某个位置的 needle 测试是相反的。如果一个字节不匹配,跳转表jtbl
用于在 haystack 中查找 needle 的下一个可能位置。这实际上是Boyer–Moore string search algorithm的实现。
现在我们知道mb_strpos
...
preg_match
至于preg_match
,它使用PCRE library。 Its standard matching algorithm uses a nondeterministic finite automaton (NFA)找到进行模式树深度优先搜索的匹配项。这基本上是一种天真的搜索方法。
答案 1 :(得分:13)
我要离开preg_match
以使分析更加突出。
根据您的观察mb_strpos
与strpos
相比相对较慢,它会让您假设 - 由于消耗的时间 - mb_strpos
的作用超过strpos
我认为这种观察是正确的。
然后您询问导致时差的“更多”是什么。
我尝试给出一个简单的答案:“更多”是因为strpos
对二进制字符串进行操作(一个字符= 8位= 1个八位字节= 1个字节)。 mb_strpos
对编码的字符序列进行操作(几乎所有mb_*
函数都可以),可以是X位,甚至可能是每个字符的可变长度。
由于这总是关于特定的字符编码,干草堆和针头串(可能)都需要首先验证该编码,然后找到字符串位置的整个操作需要在特定的字符编码。
这是翻译工作,并且 - 取决于编码 - 还需要特定的搜索算法。
接下来,mb扩展还需要在内存中有一些结构来组织不同的字符编码,无论是转换表和/或特定算法。请参阅您注入的额外参数 - 例如编码的名称。
这比仅进行简单的逐字节比较要多得多。
例如,当您需要对某个字符进行编码或解码时,GBK character encoding非常有趣。在这种情况下,mb字符串函数需要考虑所有这些细节,以确定字符是否以及在哪个位置。由于PHP在用户区中只有二进制字符串,您可以从中调用该函数,因此需要在每个函数调用上完成整个工作。
为了进一步说明这一点,如果您查看支持的编码列表(mb_list_encodings
),您还可以找到一些像 BASE64 , UUENCODE , HTML-ENTITIES 和 Quoted-Printable 。正如您可能想象的那样,所有这些都以不同的方式处理。
例如,单个数字HTML实体最大可达1024字节(如果不是更大)。我知道和喜爱的一个极端例子是this one。但是,对于该编码,它必须由mb_strpos
算法处理。
答案 2 :(得分:9)
看一下5.5.6 PHP源文件,延迟似乎大部分出现在 mbfilter.c 中,其中 - as hakre surmised - 两者< / em> haystack和needle需要进行验证和转换,每次 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
&#39}字符串与strpos
略长字符串完全相同。好吧,如果你有非常笨拙的字符串,那么字符串最多可以是的四倍,但即使这样,你也会得到一个四倍的延迟,而不是二十倍。额外的5-6倍减速是由编码时间引起的。
mb_strpos
... 那你能做什么?您可以通过确保内部已经包含&#34; basic&#34;中的字符串来跳过这两个步骤。 mbfl*
执行转换和比较的格式,mbfl_no_encoding_utf8
(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);
当然,如果&#34;设置时间&#34;并且整个UTF-8基地的维护明显小于&#34;运行时间&#34;在每个mb_*
函数中隐式进行转换。
答案 3 :(得分:0)
mb_
性能问题可能是由混乱的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)