isset()vs strlen() - 快速/清晰的字符串长度计算

时间:2011-08-05 12:01:23

标签: php coding-style

我遇到了这段代码......

if(isset($string[255])) {
    // too long
}

isset()比

快6到40之间
if(strlen($string) > 255) {
    // too long
}

isset()的唯一缺点是代码不清楚 - 我们无法立即知道正在做什么(参见pekka的回答)。我们可以在一个函数中包装isset(),即strlt($ string,255),但我们会失去isset()的速度优势。

如何在保留代码可读性的同时使用更快的isset()函数?

编辑:测试以显示速度http://codepad.org/ztYF0bE3

strlen() over 1000000 iterations 7.5193998813629
isset() over 1000000 iterations 0.29940009117126

EDIT2:这就是为什么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/

EDIT3:我一直被教导对mirco-optimization感兴趣。可能是因为我在计算机上的资源很少的时候被教过。我对这个可能不重要的想法持开放态度,在答案中有一些好的反对意见。 我已经开始探索这个新问题... https://stackoverflow.com/questions/6983208/is-micro-optimisation-important-when-coding

8 个答案:

答案 0 :(得分:51)

好的,所以我运行测试,因为我几乎不相信isset()方法更快,但是是的,并且相当如此。 isset()方法一直快6倍左右。

我尝试过各种大小的字符串并运行不同的迭代次数;比率保持不变,顺便说一下总运行长度(对于不同大小的字符串),因为isset()和strlen()都是O(1)(这有意义 - isset只需要在一个C数组,strlen()只返回为字符串保留的大小计数。

我在php源代码中查找了它,我想我大致理解为什么。 isset(),因为它不是一个函数而是一个语言结构,在Zend VM中有自己的操作码。因此,不需要在函数表中查找它,它可以进行更专业的参数解析。对于那些感兴趣的人,代码在zend_builtin_functions.c中用于strlen()和zend_compile.c用于isset()。

为了将其与原始问题联系起来,从技术角度来看,我没有看到isset()方法存在任何问题;但对于那些不熟悉习语的人来说,更难以阅读。此外,isset()方法将在时间上保持不变,而strlen()方法在改变构建到PHP中的函数量时将是O(n)。意思是,如果你构建PHP并在许多函数中静态编译,所有函数调用(包括strlen())都会变慢;但是isset()将是不变的。然而,这种差异在实践中可以忽略不计;我也不知道维护了多少个函数指针表,因此如果用户定义的函数也有影响。我似乎记得他们在不同的桌子上,因此与这种情况无关,但是自从我上次真正使用它以来已经有一段时间了。

对于其他人,我没有看到isset()方法有任何缺点。我不知道其他方法来获得字符串的长度,而不是考虑有意思的复杂的事情,比如爆炸+计数等等。

最后,我还测试了上面关于将isset()包装到函数中的建议。这比strlen()方法慢,因为你需要另一个函数调用,因此需要另一个哈希表查找。额外参数的开销(对于要检查的大小)可以忽略不计;就像没有通过引用传递时复制字符串一样。

答案 1 :(得分:20)

任何速度差异都绝对没有影响。最多只需几毫秒。

使用对您和其他任何处理代码的人来说最易读的样式 - 我个人会强烈投票给第二个例子,因为与第一个例子不同,它使得意图(检查字符串的长度)绝对清晰。

答案 2 :(得分:12)

您的代码不完整。

在这里,我为你解决了这个问题:

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

看到区别?

答案 3 :(得分:4)

首先,我想指出an answer by Artefacto解释为什么函数调用会带来语言结构的开销。

其次,我想让你意识到XDebug大大降低了函数调用的性能,所以如果你运行XDebug,你可能会得到令人费解的数字。 Reference (Second section of question).因此,在生产中(希望没有安装XDebug),差异甚至更小。它从6x下降到2x。

第三,您应该知道,即使存在可测量的差异,只有当此代码在具有数百万次迭代的紧密循环中运行时,才会显示出这种差异。在普通的网络应用程序中,差异将无法衡量,它将在方差噪声下进行。

第四,请注意,现在开发时间比服务器负载贵得多。开发人员只需花费半秒钟就可以了解设置代码的功能,而不是节省CPU负载。此外,通过应用实际产生差异的优化(如缓存),可以更好地节省服务器负载。

答案 4 :(得分:2)

这是最新的测试:

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”] =&gt; float(2.1457672119141E-6)[“return”] =&gt; int(20)[“fn”] =&gt; string(6)“strlen”} array(3){[“time”] =&gt; float(1.1920928955078E-5)[“return”] =&gt; int(20)[“fn”] =&gt; string(12)“get_len_loop”}

跑2:

array(3){[“time”] =&gt; float(4.0531158447266E-6)[“returned”] =&gt; int(20)[“fn”] =&gt; string(6)“strlen”} array(3){[“time”] =&gt; float(1.5020370483398E-5)[“return”] =&gt; int(20)[“fn”] =&gt; string(12)“get_len_loop”}

RUN 3:

array(3){[“time”] =&gt; float(4.0531158447266E-6)[“returned”] =&gt; int(20)[“fn”] =&gt; string(6)“strlen”} array(3){[“time”] =&gt; float(1.2874603271484E-5)[“return”] =&gt; int(20)[“fn”] =&gt; string(12)“get_len_loop”}

跑4:

array(3){[“time”] =&gt; float(3.0994415283203E-6)[“return”] =&gt; int(20)[“fn”] =&gt; string(6)“strlen”} array(3){[“time”] =&gt; float(1.3828277587891E-5)[“return”] =&gt; int(20)[“fn”] =&gt; string(12)“get_len_loop”}

RUN 5:

array(3){[“time”] =&gt; float(5.0067901611328E-6)[“return”] =&gt; int(20)[“fn”] =&gt; string(6)“strlen”} array(3){[“time”] =&gt; float(1.4066696166992E-5)[“return”] =&gt; int(20)[“fn”] =&gt; string(12)“get_len_loop”}

答案 5 :(得分:1)

缺点是isset根本不明确,而strlen非常清楚你的意图是什么。如果有人阅读你的代码并且必须了解你正在做的事情,那么可能会让他感到烦恼而且不是很清楚。

除非你正在运行facebook,否则我怀疑strlen将是你的服务器将花费大部分资源的地方,你应该继续使用strlen。

我刚刚测试过strlen的速度要快得多。

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
   }
}

答案 6 :(得分:1)

在现代的面向对象的Web应用程序中,您在一个小类中轻松编写的一行可以运行几百次来构建一个Web页面。 您可能希望使用 XDebug 对您的网站进行分析,您可能会惊讶地发现每个类的方法执行的次数。
然后在现实世界的场景中,你可能不会只使用很少的字符串,但也可以使用大到3MB或更大的大文档 您可能还会遇到非拉丁字符的文字 因此,最初只是一点点性能损失可能导致网页渲染几百毫秒。

所以我对这个问题很感兴趣,写了一个小测试来测试4个不同的方法来检查一个字符串是否真的是空的“”或实际上是否包含类似“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很难与非拉丁字符一起工作,我从网页上复制了一个俄文文本来比较字符串“0”和更大的俄文文本之间的结果。

$steststring = "0";

$steststring2 = "Hotel Majestic в городе Касабланка располагается всего в нескольких минутах от "
  . "следующих достопримечательностей и объектов: "
  . "Playas Ain Diab y La Corniche и Центральный рынок Касабланки. "
  . "Этот отель находится вблизи следующих достопримечательностей и объектов: "
  . "Площадь Мухаммеда V и Культурный комплекс Сиди-Бельот.";

为了看到真正的差异,我将每个测试功能调用了数百万次。

$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”。
  • 检查非拉丁字符文本所需的mb_strlen()函数在较大的文本上表现较差。
  • 支票$string !== ""表现非常出色。甚至比empty()功能更好。
  • 但最好的效果会给isset($string[0])检查。

我一定要完成整个对象库。

答案 7 :(得分:-2)

如果你想保持清晰,你可以这样做:

function checklength(&$str, $len)
{
     return isset($str[$len]);
}