【问题标题】:isset() vs strlen() - a fast/clear string length calculationisset() vs strlen() - 快速/清晰的字符串长度计算
【发布时间】:2011-10-20 19:39:24
【问题描述】:

我遇到了这段代码...

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:我总是被教导要对微优化感兴趣。可能是因为我是在计算机资源很少的时候受教的。我对它可能并不重要的想法持开放态度,答案中有一些很好的论据反对它。 我已经开始了一个新问题来探索这个......https://stackoverflow.com/questions/6983208/is-micro-optimisation-important-when-coding

【问题讨论】:

  • 我认为第二个更快,因为 PHP 内部会跟踪字符串的长度。
  • 嗯,字符串是如何在 PHP 中实现的 - 以空结尾或以长度为前缀?那会有很大的不同。
  • codepad.org/IGKfD2Mk 我复制/粘贴了您的代码,但结果完全不同:D
  • @yokoloko 奇怪,想知道为什么?有趣 - isset() 仍然更快。
  • @Kerrek SB:两者都没有,它们在结构中使用 char* 和单独的长度值进行跟踪。请参阅 svn.php.net/viewvc/php/php-src/trunk/Zend/… ,查找 _zvalue_value 结构。因此,在这个问题的上下文中, strlen() 是 O(1) 并且 isset() 'trick' 不仅没有必要,而且速度更慢,意图也不太清楚。

标签: php coding-style


【解决方案1】:

好的,所以我运行了测试,因为我几乎不相信 isset() 方法更快,但是是的,而且相当快。 isset() 方法始终快 6 倍左右。

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

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

为了将其与原始问题联系起来,从技术角度来看,我认为 isset() 方法没有任何问题;但是对于不习惯该成语的人来说,imo很难阅读。此外,isset() 方法将在时间上保持不变,而 strlen() 方法将在改变 PHP 中内置的函数数量时为 O(n)。这意味着,如果您构建 PHP 并在许多函数中静态编译,所有函数调用(包括 strlen())都会变慢;但 isset() 将是恒定的。然而,这种差异实际上可以忽略不计;我也不知道维护了多少个函数指针表,所以如果用户定义的函数也有影响。我似乎记得它们在不同的表中,因此与本案无关,但距离我上次真正处理这个已经有一段时间了。

对于其余部分,我认为 isset() 方法没有任何缺点。当不考虑像explode+count之类的故意复杂的方法时,我不知道获取字符串长度的其他方法。

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

【讨论】:

  • +1 以获得深入的解释 - 尽管我认为优化在大多数情况下仍然毫无意义,并且从维护的角度来看,使用 strlen 更好。
  • +50 以获得深入的答案。 Pekka 关于可读性的观点很重要——对我来说,理想的答案是对代码的优化和可读性感兴趣。我开始这个问题是为了探索pekka的观点stackoverflow.com/questions/6983208/…
  • 再次检查了 Windows 和 Linux 上的 PHP 7.1,仍然正确,isset() 比 strlen 稍快。我一遍又一遍地测试,它总是更快,实际上在更多的迭代中甚至更快,也就是说,10,000 次迭代时它只快 10%,但 1000 万次时它快了近 50%。
【解决方案2】:

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

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

【讨论】:

  • 你为什么说它没有后果?在具有许多(循环)字符串长度比较的任务中,我肯定会使用更快的!
  • @scube 向我展示了您需要在 PHP 脚本中进行这种微优化的真实情况。
  • 我总是更喜欢清晰度/可读性,而不是这些小的速度改进;因此,我总是会选择 strlen(),不管它是否会慢几毫秒。如果像这样的性能差异是至关重要的,那么 PHP 对于您的任务来说是错误的语言。
  • @Pekka 我删除了我的评论,因为我测试了它(将 isset() 包装在可读函数中或别名 isset()) - 我们失去了使用 isset() 的次要速度优势。跨度>
  • @scube 做数学。根据下面的测试,使用isset() 会为 100,000 次迭代节省 0.3 秒。因此,在您的时事通讯系统中,这种“优化”总共会产生 0.15 秒,而发送消息的实际过程将花费每个收件人的一百倍。
【解决方案3】:

您的代码不完整。

在这里,我为你修好了:

if(isset($string[255])) {
    // something taking 1 millisecond
}

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

看到区别了吗?

【讨论】:

    【解决方案4】:

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

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

    第三,您应该知道,即使存在可测量的差异,这种差异也只有在代码运行在具有数百万次迭代的紧密循环中时才会显示出来。在普通的 Web 应用程序中,这种差异是无法测量的,它会在方差的噪音中消失。

    第四,请注意,如今开发时间比服务器负载要昂贵得多。开发人员只需多花半秒时间了解 isset 代码的作用,就比节省 CPU 负载要昂贵得多。此外,通过应用实际产生影响的优化(如缓存),可以更好地节省服务器负载。

    【讨论】:

    • 1,感谢您的帮助 - 真的很有帮助。 2,这可能是我们在不同系统上看到 40 倍到 2 倍差异的原因。 3/4,点数非常多 - 请在上面的问题中查看我的 EDIT3。
    【解决方案5】:

    这是最新的测试:

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

    运行 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" }

    【讨论】:

      【解决方案6】:

      缺点是 isset 根本不明确,而 strlen 非常清楚您的意图是什么。如果有人阅读了您的代码并且必须了解您在做什么,这可能会令他感到困惑并且不是很清楚。

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

      我刚刚测试过 strlen 比 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
         }
      }
      

      【讨论】:

      • 如果你运行 Facebook,你就会使用 HipHop :-)
      • Facebook 只是大公司的一个例子......我在我的 comp 上测试了我的答案
      • 嗯,实际上...如果您正在使用 HipHop,那么(我想,我还没有检查过 HipHop,但它似乎只是合乎逻辑的)strlen(xyz)将被映射到 xyz.size(),这在 STL afaik 的所有现代实现上都是 O(1)。
      • 我尝试使用更大的字符串,这就是结果。但是不要问我为什么codepad.org/WLKnR4JW(根据你的测试
      【解决方案7】:

      在现代面向对象的 Web 应用程序中,您在一个小类中轻松编写的一行代码可以运行数百次以构建一个网页。
      您可能希望使用 XDebug 分析您的网站,并且您可能会惊讶于每个类的方法执行了多少次。
      那么在现实世界的场景中,您可能不仅要处理小字符串,还要处理高达 3MB 或更大的非常大的文档。
      您可能还会遇到包含非拉丁字符的文本。
      所以最终,最初只是一点点性能损失可能会导致网页渲染达到 100 毫秒。

      所以我对这个问题非常感兴趣,并编写了一个小测试,测试 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]) 检查。

      我肯定要处理我的整个对象库。

      【讨论】:

      • 我在具有完全不同的硬件设置以及不同版本的操作系统(Centos6 和 Centos7)的不同机器上运行测试脚本,我发现 empty() 函数在执行时间上有所不同,但在有些机器运行得更快,而在其他机器上运行俄语文本时运行速度较慢。
      猜你喜欢
      • 2019-03-11
      • 2015-04-27
      • 1970-01-01
      • 2011-05-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多