zoukankan      html  css  js  c++  java
  • PHP-不同Str 拼接方法性能对比 参考自https://www.cnblogs.com/xiaoerli520/p/9624309.html

    问题

    在PHP中,有多种字符串拼接的方式可供选择,共有:

    1
    . , .= , sprintf, vprintf, join, implode

    那么,那种才是最快的,或者那种才是最适合业务使用的,需要进一步探究。

    用到的工具

    PHP7.1.16 PHP5.4 VLD XDebug phpunit4 以及自己写的一个Benchmark工具。

    PHP54环境

    PHPUnit测试结果

    使用以下代码,分别测试了上面的几种字符串拼接方式(拼接方式无法对变量赋值,故用处不大,没有测,join和implode是相等的,仅仅测试了其中一个)

    测试条件:2C-4T 8G 拼接5W次,重复10次取平均值。

    测试代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    <?php
    /**
    * Created by PhpStorm.
    * User: shixi_qingzhe
    * Date: 18/5/18
    * Time: 下午9:31
    * 几种字符串拼接的测试,表面测试
    *
    *
    * 最快字符串拼接方式 . , .= , sprintf , join, implode, concat
    */
    require_once __DIR__ . '/lib.php';

    class ConcatTest extends PHPUnit_Framework_TestCase
    {
    private $Count = 50000;

    private $reTryTime = 10;

    private $tempStr = '19826318654817aasdasdadasd';

    public function testDotEqual()
    {
    $timer = Benchmark::getInstance();
    $timeAVG = $timer->tryManyTimes(function () {
    $result = "";
    for ($i = 0; $i < $this -> Count; $i++) {
    $result .= $this->tempStr;
    }
    }, $this -> reTryTime);
    echo "time By .= is : ".$timeAVG." ";
    }

    // public function testDouhao()
    // {
    // $timer = Benchmark::getInstance();
    // $timeAvg = $timer -> tryManyTimes(function () {
    // $result = "";
    // for ($i = 0; $i < $this -> Count;$i++) {
    // $result = $result.$this -> tempStr;
    // }
    // }, $this -> reTryTime);
    // echo "time By , is : ".$timeAvg." ";
    // }


    public function testDot()
    {
    $timer = Benchmark::getInstance();
    $timeAvg = $timer -> tryManyTimes(function () {
    $result = "";
    for ($i = 0; $i < $this -> Count;$i++) {
    $result = $result.$this -> tempStr;
    }
    }, $this -> reTryTime);
    echo "time By . is : ".$timeAvg." ";
    }

    public function testSprintf()
    {
    $timer = Benchmark::getInstance();
    $timeAvg = $timer -> tryManyTimes(function() {
    $result = "";
    for ($i = 0;$i < $this -> Count;$i++) {
    $result = sprintf("%s%s", $result, $this -> tempStr);
    }
    }, $this -> reTryTime);
    echo "time By sprintf() is : ".$timeAvg." ";
    }

    public function testVsprintf()
    {
    $timer = Benchmark::getInstance();
    $timeAvg = $timer -> tryManyTimes(function() {
    $arg = [];
    for ($i = 0;$i < $this -> Count;$i++) {
    $arg[] = $this -> tempStr;
    }
    $result = vsprintf("%s", $arg);
    }, $this -> reTryTime);
    echo "time By vsprintf() is : ".$timeAvg." ";
    }

    public function testJoin()
    {
    $timer = Benchmark::getInstance();
    $timeAvg = $timer -> tryManyTimes(function () {
    // alloc the array
    $resultArr = [];
    for ($i = 0;$i < $this -> Count;$i++) {
    $resultArr[] = $this -> tempStr;
    }
    $result = implode('',$resultArr);
    }, $this -> reTryTime);
    echo "time by Join() / Implode() is : ".$timeAvg." ";
    }
    }

    测试结果为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    bogon:a_compare root# /usr/local/Cellar/php54/bin/php /usr/local/Cellar/phpunit4 ConcatTest.php
    PHPUnit 4.0.20 by Sebastian Bergmann.

    .time By .= is : 0.0050616264343262
    .time By . is : 6.156693148613
    .time By sprintf() is : 8.7994904279709
    .time By vsprintf() is : 0.014266705513
    .time by Join() / Implode() is : 0.0092714786529541


    Time: 2.49 minutes, Memory: 13.00Mb

    可以看出,执行速度最快的方法是 “.=” 方法,其次是给出数组参数并将其粘和的Vsprintf、以及Implode。

    性能最差的是“.”方法。

    因此可以得出一个结论,在PHP54的条件下,使用“.=”的方法来拼接字符串,效率是最高的。

    对于implode sprintf等方法可以深入PHP的源码查看一下。对于“.=”等运算符,可以使用VLD打印出执行过程中的OPcode,来解释相关原因。

    Sprintf方法源码分析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /private/var/root/Downloads/php-5.4.45/main/php_sprintf.c
    // php54 和 php7本方法代码相同
    PHPAPI int
    php_sprintf (char*s, const char* format, ...)
    {
    va_list args;
    int ret;

    va_start (args, format);
    s[0] = '';
    ret = vsprintf (s, format, args);
    va_end (args);
    return (ret < 0) ? -1 : ret;
    }

    可以看出,实际实现是通过C语言库中的stdarg.h中的va_list配合va_start实现参数个数不定,并且将参数化为数组,然后调用C语言本身具有的vsprintf(format, argArr)进行拼接。

    可以从以上phpunit执行结果中获得与vsprintf的对比,可以得知 绝大部分性能消耗在va_start O(n)。具体va_start为什么会消耗性能还是有待考察的。

    Implode方法源码分析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    /private/var/root/Downloads/php-5.4.45/ext/standard/string.c

    PHP5 Version 时间复杂度为O(n2) = memcpy( O(n) ) * zend_hash_get_current_data_ex(O(n))

    PHPAPI void php_implode(zval *delim, zval *arr, zval *return_value TSRMLS_DC)
    {
    zval **tmp;
    HashPosition pos;
    smart_str implstr = {0};
    int numelems, i = 0;
    zval tmp_val;
    int str_len;

    numelems = zend_hash_num_elements(Z_ARRVAL_P(arr));

    if (numelems == 0) {
    RETURN_EMPTY_STRING();
    }

    zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos);

    while (zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **) &tmp, &pos) == SUCCESS) {
    switch ((*tmp)->type) {
    case IS_STRING:
    smart_str_appendl(&implstr, Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp));
    break;

    case IS_LONG: {
    char stmp[MAX_LENGTH_OF_LONG + 1];
    str_len = slprintf(stmp, sizeof(stmp), "%ld", Z_LVAL_PP(tmp));
    smart_str_appendl(&implstr, stmp, str_len);
    }
    break;

    case IS_BOOL:
    if (Z_LVAL_PP(tmp) == 1) {
    smart_str_appendl(&implstr, "1", sizeof("1")-1);
    }
    break;

    case IS_NULL:
    break;

    case IS_DOUBLE: {
    char *stmp;
    str_len = spprintf(&stmp, 0, "%.*G", (int) EG(precision), Z_DVAL_PP(tmp));
    smart_str_appendl(&implstr, stmp, str_len);
    efree(stmp);
    }
    break;

    case IS_OBJECT: {
    int copy;
    zval expr;
    zend_make_printable_zval(*tmp, &expr, &copy);
    smart_str_appendl(&implstr, Z_STRVAL(expr), Z_STRLEN(expr));
    if (copy) {
    zval_dtor(&expr);
    }
    }
    break;

    default:
    tmp_val = **tmp;
    zval_copy_ctor(&tmp_val);
    convert_to_string(&tmp_val);
    smart_str_appendl(&implstr, Z_STRVAL(tmp_val), Z_STRLEN(tmp_val));
    zval_dtor(&tmp_val);
    break;

    }

    if (++i != numelems) {
    smart_str_appendl(&implstr, Z_STRVAL_P(delim), Z_STRLEN_P(delim));
    }
    zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos);
    }
    smart_str_0(&implstr);

    if (implstr.len) {
    RETURN_STRINGL(implstr.c, implstr.len, 0);
    } else {
    smart_str_free(&implstr);
    RETURN_EMPTY_STRING();
    }
    }

    其实,认为直接从代码层面分析还是不够直观,或者比较菜导致看不懂源码,此时可以通过VLD扩展来分析每个步骤执行的时候都有哪些操作指令被执行,这样能够更加直观的看到性能差异。

    “.=”的VLD分析

    相关差异测试VLD代码已经上传到GitHub:

    测试参数:php -dvld.active=1 XXX.php

    image

    “.”的VLD分析

    image

    “,”的VLD分析

    image

    “Join”的VLD分析

    image

    “Sprintf Vsprintf”的VLD分析

    image

    image

    可以看出,语法结构层面比函数调用的操作数要少,因此,如果在业务中,如果能用语法结构的字符串拼接尽量使用语法结构。

    可以看出,“.=”比“.”的操作数中,使用了ASSIGN_CONCAT 代替了“.”使用的ASSIGN + CONCAT的两个操作,使得消耗的时间更少。

    还可以看出,如果在直接echo并且结束程序的情况下,“,”的性能最佳,其只使用了几个性能较好的ECHO操作就完成了。

    PHP7 环境

    测试条件,测试代码均相同,测试结果如下:

    1
    2
    3
    4
    5
    6
    7
    8
    bogon:ConcatDeepCompare root# /usr/local/Cellar/phpunit4 ConcatTest.php 
    PHPUnit 4.0.20 by Sebastian Bergmann.

    .time By .= is : 0.0073251962661743
    .time By . is : 1.7567269802094
    .time By sprintf() is : 1.8133352994919
    .time By vsprintf() is : 0.0089122295379639
    .time by Join() / Implode() is : 0.0079930782318115

    对比以上PHP54的结果,可以发现PHP7的测试结果平均时长比PHP54短了5倍左右,得益于PHP7对Zval的优化,使得COW等耗费内存的现象得到缩减,进而性能提升。

    相关鸟哥博客:

    http://www.laruence.com/2018/04/08/3170.html Zval

    http://www.laruence.com/2018/04/08/3179.html Reference

    VLD结果

    “.”

    image

    “,”

    image

    “.=”

    image

    “Join”

    image

    “Sprintf”

    image

    “Vsprintf”

    image

    总结

    • “.=”在可用性以及性能是最佳的

    • “,” 在仅仅echo 输出的情况下性能最优

    • “.” 在可用的情况下性能最差

    • “Join/Vsprintf”性能较优

     
    分类: PHP
  • 相关阅读:
    二叉树遍历
    nginx反向代理signalr
    SignalR入坑笔记
    CSS一些特殊图形
    Powershell下git中文乱码
    使用VisualStudio直接运行简单的C#语句
    wpf实现一个windows定时关机的工具
    Asp.NetCoreWebApi
    MySql权限丢失问题解决
    Systemd 入门教程:命令篇
  • 原文地址:https://www.cnblogs.com/si812cn/p/10528081.html
Copyright © 2011-2022 走看看