为了提升 PHP M3u8 的解析速度,我在上一篇博客里评选出了 PHP 世界里判断是否由某字符串开头最快的冠军,这一次又要举行另外一个比赛了:从某个固定格式的字符串里解析出想要的数据最快的函数。
还是以 M3u8 格式举例子。如果我想从字符串 #EXT-X-VERSION:3
取出 3
这个版本号,可能大家最好想到的是利用正则来实现:
preg_match('/^#EXT-X-VERSION:(\d)$/', '#EXT-X-VERSION:3', $m);
echo $m[1]; // print "3"
不过,既然 PHP 也属于 C Family,也比较容易想到另外一个函数也可以实现同样的需求:
$m = sscanf('#EXT-X-VERSION:3', '#EXT-X-VERSION:%d');
echo $m[0]; // print "3"
从语法上来看,两者都还比较简单,sscanf
略胜一筹,但我们最关心的速度又如何呢?做个实验比一比:
<?php
$iter = 999999;
$f = '#EXT-X-VERSION:%d';
$p = '/^#EXT-X-VERSION:(\d)$/';
$s = '#EXT-X-VERSION:3';
$start = microtime(true);
for ($i = 0; $i < $iter; ++$i) {
$m = sscanf($s, $f);
$version = $m[0];
}
echo microtime(true) - $start, PHP_EOL;
$start = microtime(true);
for ($i = 0; $i < $iter; ++$i) {
preg_match($p, $s, $m);
$version = $m[1];
}
echo microtime(true) - $start, PHP_EOL;
结果怎样大家可以在自己电脑上实验一番,在我的电脑上 sscanf
胜,但差距也不是很大,在一个数量级内。
另外我不得不补充一点,sscanf
用法还是很灵活的,比如在解析 #EXT-X-BYTERANGE:1000@500
时,因为 @
以及它后面的参数是可以省略的,如果用 preg_match
来处理:
preg_match('/^#EXT-X-BYTERANGE:(\d+)(@(\d+))?$/', $line, $matches);
$length = (int) $matches[1];
if (!empty($matches[3])) {
$offset = (int) $matches[3];
}
而使用 sscanf 的话:
list($length, $offset) = sscanf('#EXT-X-BYTERANGE:1000@500', '#EXT-X-BYTERANGE:%d@%d');
当没有 @500
时,$offset
自动就是 null,所以一行代码就可以搞定,就冲这点 sscanf
也加分不少。不过我们还是再来测试一下这种方式的速度对比:
<?php
$iter = 999999;
$f = '#EXT-X-BYTERANGE:%d@%d';
$p = '/^#EXT-X-BYTERANGE:(\d+)(@(\d+))?$/';
$s = '#EXT-X-BYTERANGE:1000';
// $s = '#EXT-X-BYTERANGE:1000@500';
$start = microtime(true);
for ($i = 0; $i < $iter; ++$i) {
list($length, $offset) = sscanf($s, $f);
}
echo microtime(true) - $start, PHP_EOL;
$start = microtime(true);
for ($i = 0; $i < $iter; ++$i) {
preg_match($p, $s, $matches);
$length = (int) $matches[1];
if (!empty($matches[3])) {
$offset = (int) $matches[3];
}
}
echo microtime(true) - $start, PHP_EOL;
结果依然是 sscanf 获胜。
对于解析 #EXT-X-VERSION:3
这种格式比较简单的,方法还有很多,比如利用 explode
, substr
, strstr
等函数,我打算再加赛一场,看看是否能跟 sscanf 一争高下。在看结果之前,不妨自己猜猜看谁是冠军:
<?php
$iter = 999999;
$f = '#EXT-X-BYTERANGE:%d';
$p = '/^#EXT-X-BYTERANGE:(\d+)$/';
$s = '#EXT-X-BYTERANGE:1000';
$start = microtime(true);
for ($i = 0; $i < $iter; ++$i) {
list($length) = sscanf($s, $f);
}
echo 'sscanf: ', microtime(true) - $start, PHP_EOL;
$start = microtime(true);
for ($i = 0; $i < $iter; ++$i) {
preg_match($p, $s, $matches);
$length = (int) $matches[1];
}
echo 'preg_match: ', microtime(true) - $start, PHP_EOL;
$start = microtime(true);
for ($i = 0; $i < $iter; ++$i) {
$length = (int) explode(':', $s)[1];
}
echo 'explode: ', microtime(true) - $start, PHP_EOL;
$start = microtime(true);
for ($i = 0; $i < $iter; ++$i) {
$length = (int) substr(strstr($s, ':'), 1);
}
echo 'substr+strstr: ', microtime(true) - $start, PHP_EOL;
$start = microtime(true);
for ($i = 0; $i < $iter; ++$i) {
$length = (int) substr(strrchr($s, ':'), 1);
}
echo 'substr+strrchr: ', microtime(true) - $start, PHP_EOL;
$start = microtime(true);
for ($i = 0; $i < $iter; ++$i) {
$length = (int) substr(strpbrk($s, ':'), 1);
}
echo 'substr+strpbrk: ', microtime(true) - $start, PHP_EOL;
$start = microtime(true);
for ($i = 0; $i < $iter; ++$i) {
$length = (int) substr($s, strpos($s, ':') + 1);
}
echo 'substr+strpos: ', microtime(true) - $start, PHP_EOL;
$start = microtime(true);
for ($i = 0; $i < $iter; ++$i) {
$length = (int) substr($s, strlen($t) + 1);
}
echo 'substr+strlen: ', microtime(true) - $start, PHP_EOL;
$start = microtime(true);
for ($i = 0; $i < $iter; ++$i) {
$length = (int) ltrim(strstr($s, ':'), ':');
}
echo 'ltrim+strstr: ', microtime(true) - $start, PHP_EOL;
$start = microtime(true);
for ($i = 0; $i < $iter; ++$i) {
$length = (int) ltrim(strrchr($s, ':'), ':');
}
echo 'ltrim+strrch: ', microtime(true) - $start, PHP_EOL;
$start = microtime(true);
for ($i = 0; $i < $iter; ++$i) {
$length = (int) ltrim(strpbrk($s, ':'), ':');
}
echo 'ltrim+strpbrk: ', microtime(true) - $start, PHP_EOL;
得益于上一届的冠军 strpos
出色的性能,strpos
+ substr
组合以大优势获得了冠军!substr
+ strrchr
组合获得亚军,substr
+ strstr
组合获得季军。不得不说 substr
还是很牛逼的,前三名它都有出镜。
2017-11-17 补充:今天又试验了 substr
+ strlen
的组合,以不大的优势超越了曾经的冠军获得了第一名!以上代码已经更新。
当然这些方法只能用在特殊情况,正常解析带格式的字符串还是得用回 preg_match
和 sscanf
。事实上,sscanf
也是可以使用正则的,但要注意不是所有正则符号都能用,跟一般的正则还是有所区别:
$filename = sscanf ('a.jpg', '%[^.].jpg')[0];
// 注意这里如果格式参数写 '%s.jpg',结果是 "a.jpg" 而不是 "a"
// 因为 %s 必须得遇到空格类字符才会停止解析
将 sscanf
加正则的格式的方式与 preg_match
以及 sscanf
自身比一比:
$f2 = '#EXT-X-BYTERANGE:%[0-9]';
...
$start = microtime(true);
for ($i = 0; $i < $iter; ++$i) {
$length = (int) sscanf($s, $f2)[0];
}
echo 'sscanf 2: ', microtime(true) - $start, PHP_EOL;
再猜猜看结果是什么??
公布结果:
带正则 sscanf
稍快于 preg_match
,稍慢于不带正则的 sscanf
。
经过这次的比赛,再次告诉我们一个真理:正则能别用就别用啊。
PHP 从固定格式字符串里解析数据最快的方式 by Chris Yue is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

写作累,服务器还越来越贵
求分担,祝愿好人一生平安
天使打赏人