isset()vs strlen() – 一个快速/明确的string长度计算
我遇到这个代码…
if(isset($string[255])) { // too long }
isset()的速度在6到40之间
if(strlen($string) > 255) { // too long }
isset()的唯一缺点是代码不清楚 – 我们不能立即告诉正在做什么(见pekka的答案)。 我们可以在一个函数strlt($ string,255)中包装isset(),但是我们放弃了isset()的速度优势。
我们如何使用更快的isset()函数,同时保持代码的可读性?
编辑:testing显示速度http://codepad.org/ztYF0bE3
strlen() over 1000000 iterations 7.5193998813629 isset() over 1000000 iterations 0.29940009117126
编辑2:这是为什么isset()更快
$string = 'abcdefg'; var_dump($string[2]); Output: string(1) “c” $string = 'abcdefg'; if (isset($string[7])){ echo $string[7].' found!'; }else{ echo 'No character found at position 7!'; }
这比使用strlen()更快,因为“…调用一个函数比使用语言结构更昂贵” 。http://www.phpreferencebook.com/tips/use-isset-instead-of-strlen/
编辑3:我总是被教导有兴趣在微调优化。 可能是因为我在电脑上的资源很小的时候就被教过了。 我接受这个观点,认为这个问题可能并不重要,在答案中有一些反对意见。 我已经开始探索这个新的问题… https://stackoverflow.com/questions/6983208/is-micro-optimisation-important-when-coding
好,所以我跑了testing,因为我几乎不相信isset()方法更快,但是是的,而且是相当的。 isset()方法的速度始终快大约6倍。
我已经尝试了各种大小的string,并运行不同的迭代次数; 因为isset()和strlen()都是O(1)(这是有道理的 – isset只需要做一个lookup就可以了一个C数组,而strlen()只返回为string保留的大小数)。
我查了一下php的源码,我想我粗略的理解了为什么。 isset(),因为它不是一个函数,而是一个语言结构,在Zend VM中有自己的操作码。 因此,它不需要在函数表中查找,它可以做更多的专门的参数parsing。 代码在zend_builtin_functions.c中为strlen()和zend_compile.c为isset(),对于那些感兴趣的人。
为了把这个问题和原来的问题联系起来,从技术的angular度来看,我没有看到isset()方法的问题。 但是对于不习惯习惯用语的人来说,阅读起来更难。 此外,isset()方法将会保持不变,而strlen()方法在改变构build到PHP中的函数的数量时将是O(n)。 意思是,如果你构buildPHP并在许多函数中进行静态编译,所有函数调用(包括strlen())将会变慢。 但isset()将是常量。 然而,这种差异在实践中可以忽略不计; 我也不知道有多less个函数指针表被维护,所以如果用户定义的函数也有影响的话。 我似乎记得他们在不同的桌子上,因此与这种情况无关,但是自从我上次真正与此合作以来已经有一段时间了。
其余的我没有看到isset()方法的缺点。 我不知道其他的方法来获得一个string的长度,当不考虑像explode + count这样的有目的的复杂的东西。
最后,我还testing了上面的将isset()包装到函数中的build议。 这甚至比strlen()方法还要慢,因为你需要另一个函数调用,因此需要另一个哈希表查找。 额外参数的开销(对于检查的大小)可以忽略不计; 就像在不通过引用传递时复制string一样。
任何速度上的差异都是毫无意义的。 最好几毫秒。
使用任何风格对您和其他人来说都是最好的可读代码 – 我个人会强烈地投票赞成第二个例子,因为与第一个例子不同的是,它的意图(检查string的长度)是绝对清晰的。
你的代码是不完整的。
在这里,我为你解决了这个问题:
if(isset($string[255])) { // something taking 1 millisecond }
VS
if(strlen($string) > 255) { // something taking 1 millisecond }
现在你没有一个空的循环,而是一个现实的循环。 让我们认为需要1毫秒来做一些事情。
一个现代的CPU可以在1毫秒内完成很多事情 – 这是给出的。 但是随机硬盘访问或数据库请求等事情需要几毫秒 – 也是一个现实的情况。
现在让我们再次计算时间:
realistic routine + strlen() over 1000000 iterations 1007.5193998813629 realistic routine + isset() over 1000000 iterations 1000.29940009117126
看到不同?
首先,我想指出Artefacto的一个答案,解释为什么函数调用会在语言结构上带来开销。
其次,我想让你意识到XDebug大大降低了函数调用的性能,所以如果你正在运行XDebug,你可能会得到错误的数字。 参考(问题的第二部分)。 所以,在生产(你希望没有安装XDebug的地方),差距就更小了。 它从6倍下降到2倍。
第三,你应该知道,即使有一个可测量的差异,只有当代码以数百万次的迭代运行在一个紧密的循环中时,才会出现这种差异。 在一个正常的networking应用程序中,差异是不可测量的,它将在方差的噪声下进行。
第四,请注意,现在开发时间比服务器负载要贵得多。 开发人员花费的时间只有半秒多,因此了解isset代码的作用要比节省CPU负担要贵得多。 此外,通过应用实际上有所作为的优化(如caching),可以更好地节省服务器负载。
这是最新的testing:
function benchmark_function($fn,$args=null) { if(!function_exists($fn)) { trigger_error("Call to undefined function $fn()",E_USER_ERROR); } $t = microtime(true); $r = call_user_func_array($fn,$args); return array("time"=>(microtime(true)-$t),"returned"=>$r,"fn"=>$fn); } function get_len_loop($s) { while($s[$i++]){} return $i-1; } echo var_dump(benchmark_function("strlen","kejhkhfkewkfhkwjfjrw"))."<br>"; echo var_dump(benchmark_function("get_len_loop","kejhkhfkewkfhkwjfjrw"));
返回结果:
运行1:
array(3){[“time”] => float(2.1457672119141E-6)[“returned”] => int(20)[“fn”] => string(6)“strlen”} array(3){ [“time”] => float(1.1920928955078E-5)[“returned”] => int(20)[“fn”] => string(12)“get_len_loop”}
运行2:
array(3){[“time”] => float(4.0531158447266E-6)[“returned”] => int(20)[“fn”] => string(6)“strlen”} array(3){ [“time”] => float(1.5020370483398E-5)[“returned”] => int(20)[“fn”] => string(12)“get_len_loop”}
RUN 3:
array(3){[“time”] => float(4.0531158447266E-6)[“returned”] => int(20)[“fn”] => string(6)“strlen”} array(3){ [“time”] => float(1.2874603271484E-5)[“returned”] => int(20)[“fn”] => string(12)“get_len_loop”}
运行4:
array(3){[“time”] => float(3.0994415283203E-6)[“returned”] => int(20)[“fn”] => string(6)“strlen”} array(3){ [“time”] => float(1.3828277587891E-5)[“returned”] => int(20)[“fn”] => string(12)“get_len_loop”}
运行5:
array(3){[“time”] => float(5.0067901611328E-6)[“returned”] => int(20)[“fn”] => string(6)“strlen”} array(3){ [“time”] => float(1.4066696166992E-5)[“returned”] => int(20)[“fn”] => string(12)“get_len_loop”}
缺点是isset是不明确的,而strlen真的很清楚你的意图是什么。 如果有人阅读你的代码,必须明白你在做什么,这可能会让他感到不适,而且不太清楚。
除非你正在运行的Facebook我怀疑strlen将是你的服务器将花费他的大部分资源,你应该继续使用strlen。
我刚刚testingstrlen是更快的isset。
0.01 seconds for 100000 iterations with isset
0.04 seconds for 100000 iterations with strlen
但是不会改变我刚才所说的。
刚刚问一些人的脚本问:
$string = 'xdfksdjhfsdljkfhsdjklfhsdlkjfhsdjklfhsdkljfhsdkljfhsdljkfsdhlkfjshfljkhfsdljkfhsdkljfhsdkljfhsdklfhlkjfhkljfsdhfkljsdhfkljsdhfkljhsdfjklhsdjklfhsdkljfhklsdhfkljsdfhdjkshfjlhdskljfhsdkljfhsdjkfhsjkldhfklsdjhfkjlsfhdjkflsdhfjklfsdljfsdlkdlfkjflfkjsdfkl'; for ($i = 0; $i < 100000; $i++) { if (strlen($string) == 255) { // if (isset($string[255])) { // do nothing } }
如果你想保持清晰,你可以做这样的事情:
function checklength(&$str, $len) { return isset($str[$len]); }
在现代的面向对象的Web应用程序中,您可以轻松地在一个小类中编写一行代码,运行几百次来构build一个Web页面。
你可能想用XDebug来分析你的Web站点,你可能会惊讶一个类的每个方法被执行多less次。
那么在现实世界的情况下,你可能无法使用小string,但也可以使用大到3MB或更大的大文档。
你也可能碰到不带拉丁字符的文字。
所以最终最初只是一点点的性能损失,可能会导致网页渲染几百毫秒。
所以我对这个问题非常感兴趣,并写了一个testing,testing4个不同的方法来检查一个string是否真的是空的,或者实际上包含了像“0”这样的东西。
function stringCheckNonEmpty0($string) { return (empty($string)); } function stringCheckNonEmpty1($string) { return (strlen($string) > 0); } function stringCheckNonEmpty1_2($string) { return (mb_strlen($string) > 0); } function stringCheckNonEmpty2($string) { return ($string !== ""); } function stringCheckNonEmpty3($string) { return (isset($string[0])); }
我发现PHP很难与非拉丁字符一起工作,我从网页上复制了俄文文本,比较了小string“0”和较大的俄文文本之间的结果。
$ steststring =“0”; $ steststring2 =“Hotel MajesticвгородеКасабланкарасполагаетсявсеговнесколькихминутахот” 。 “следующихдостопримечательностейиобъектов:” 。 “Playas Ain Diab y La CornicheиЦентральныйрынокКасабланки。” 。 “该站点的内容需要改进: 。 “ПлощадьМухаммедаVиКультурныйкомплексСиди-Бельот。”;
为了看到真正的差异,我把每个testing函数调用了几百万次。
$iruncount = 10000000; echo "test: empty(\"0\"): starting ...\n"; $tmtest = 0; $tmteststart = microtime(true); $tmtestend = 0; for($irun = 0; $irun < $iruncount; $irun++) stringCheckNonEmpty0($steststring); $tmtestend = microtime(true); $tmtest = $tmtestend - $tmteststart; echo "test: empty(\"0\"): '$tmtest' s\n";
检测结果
$ php test_string_check.php test0.1: empty("0"): starting ... test0.1: empty("0"): '7.0262970924377' s test0.2: empty(russian): starting ... test0.2: empty(russian): '7.2237210273743' s test1.1.1: strlen("0"): starting ... test1.1.1: strlen("0"): '11.045154094696' s test1.1.2: strlen(russian): starting ... test1.1.2: strlen(russian): '11.106546878815' s test1.2.1: mb_strlen("0"): starting ... test1.2.1: mb_strlen("0"): '11.320801019669' s test1.2.2: mb_strlen(russian): starting ... test1.2.2: mb_strlen(russian): '23.082058906555' s test2.1: ("0" !== ""): starting ... test2.1: ("0" !== ""): '7.0292129516602' s test2.2: (russian !== ""): starting ... test2.2: (russian !== ""): '7.1041729450226' s test3.1: isset(): starting ... test3.1: isset(): '6.9401099681854' s test3.2: isset(russian): starting ... test3.2: isset(russian): '6.927631855011' s $ php test_string_check.php test0.1: empty("0"): starting ... test0.1: empty("0"): '7.0895299911499' s test0.2: empty(russian): starting ... test0.2: empty(russian): '7.3135821819305' s test1.1.1: strlen("0"): starting ... test1.1.1: strlen("0"): '11.265664100647' s test1.1.2: strlen(russian): starting ... test1.1.2: strlen(russian): '11.282053947449' s test1.2.1: mb_strlen("0"): starting ... test1.2.1: mb_strlen("0"): '11.702164888382' s test1.2.2: mb_strlen(russian): starting ... test1.2.2: mb_strlen(russian): '23.758249998093' s test2.1: ("0" !== ""): starting ... test2.1: ("0" !== ""): '7.2174110412598' s test2.2: (russian !== ""): starting ... test2.2: (russian !== ""): '7.240779876709' s test3.1: isset("0"): starting ... test3.1: isset("0"): '7.2104151248932' s test3.2: isset(russian): starting ... test3.2: isset(russian): '7.2232971191406' s
结论
- 传统的
emtpy()
函数执行得很好,但在像“0”这样的string上失败。 - 使用非拉丁字符检查文本所必需的
mb_strlen()
函数在较大的文本上执行得更糟。 - Check
$string !== ""
performance得非常好。 甚至比empty()
函数更好。 - 但最好的性能提供了
isset($string[0])
检查。
我一定要处理我的整个对象库。