问题

在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."\n";
} // 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."\n";
// } 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."\n";
} 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."\n";
} 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."\n";
} 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."\n";
}
}

测试结果为:

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] = '\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

“.”的VLD分析

“,”的VLD分析

“Join”的VLD分析

“Sprintf Vsprintf”的VLD分析

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

可以看出,“.=”比“.”的操作数中,使用了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结果

“.”

“,”

“.=”

“Join”

“Sprintf”

“Vsprintf”

总结

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

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

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

  • “Join/Vsprintf”性能较优

 
分类: PHP

PHP-不同Str 拼接方法性能对比 参考自https://www.cnblogs.com/xiaoerli520/p/9624309.html的更多相关文章

  1. PHP-不同Str 拼接方法性能对比

    问题 在PHP中,有多种字符串拼接的方式可供选择,共有: 1 . , .= , sprintf, vprintf, join, implode 那么,那种才是最快的,或者那种才是最适合业务使用的,需要 ...

  2. Python开发【笔记】:从海量文件的目录中获取文件名--方法性能对比

    Python获取文件名的方法性能对比 前言:平常在python中从文件夹中获取文件名的简单方法   os.system('ll /data/')   但是当文件夹中含有巨量文件时,这种方式完全是行不通 ...

  3. 关于JAVASCRIPT call 方法和 apply 方法性能对比

    JavaScript 关于call 方法和 apply 方法常用形式 call obj.call(object, args , ....); apply obj.apply(object, [args ...

  4. Linux下的crontab定时执行任务命令详解(参考:https://www.cnblogs.com/longjshz/p/5779215.html)

    在Linux中,周期执行的任务一般由cron这个守护进程来处理[ps -ef | grep cron].cron读取一个或多个配置文件,这些配置文件中包含了命令行以及调用时间. cron的配置文件成为 ...

  5. 2、wepy安装后提示Cannot read property 'addDeps' 参考自https://www.cnblogs.com/yuanchaoyong/p/11614400.html

    摘抄自https://www.cnblogs.com/yuanchaoyong/p/11614400.html wepy安装步骤 $ npm install @wepy/cli -g # 全局安装 W ...

  6. 1、windows安装npm教程 --参考自https://www.cnblogs.com/jianguo221/p/11487532.html

    windows安装npm教程   1.在使用之前,先类掌握3个东西,明白它们是用来干什么的: npm:  nodejs 下的包管理器. webpack: 它主要用途是通过CommonJS 的语法把所有 ...

  7. C#图片处理常见方法性能比较

    C#图片处理常见方法性能比较 来自:http://www.cnblogs.com/sndnnlfhvk/archive/2012/02/27/2370643.html   在.NET编程中,由于GDI ...

  8. .NET平台下几种SOCKET模型的简要性能供参考

    转载自:http://www.cnblogs.com/asilas/archive/2006/01/05/311309.html .NET平台下几种SOCKET模型的简要性能供参考 这个内容在cnbl ...

  9. PHP生成随机密码的4种方法及性能对比

    PHP生成随机密码的4种方法及性能对比 http://www.php100.com/html/it/biancheng/2015/0422/8926.html 来源:露兜博客   时间:2015-04 ...

随机推荐

  1. 精进之路之HashMap

    HashMap本质的核心就是“数组+链表”,数组对于访问速度很快,而链表的优势在于插入速度快,HashMap集二者于一身. 提到HashMap,我们不得不提各个版本对于HashMap的不同.本文中先从 ...

  2. linux centos7下源码 tar安装mysql5.7.23(5.7以上均可试用)

    1.工具:mysql-5.7.22-linux-glibc2.12-x86_64.tar.gz.centos7 2.解压后,将mysql-5.7.22-linux-glibc2.12-x86_64里面 ...

  3. holer实现外网访问本地网站

    外网访问本地网站 本地搭建了网站,只能在局域网内访问,怎样从公网也能访问内网网站? 本文将介绍使用holer实现的具体步骤. 1. 准备工作 1.1 安装并启动网站服务端 默认搭建的网站服务端端口是8 ...

  4. 判断客户端是IOS还是Android

    PHP 判断客户端是IOS还是Android <?php if(strpos($_SERVER['HTTP_USER_AGENT'], 'iPhone')||strpos($_SERVER['H ...

  5. Python_文件处理

    1.Python  文件处理 打开文件---->读取内容---->获得内容 读取文件方式:  r  只读文件  w 只写模式 a 追加模式 r+b 读写模式   w+b 写读模式  a+b ...

  6. Could not find method google() for arguments [] on repository container,rn 集成react-native-camera 出现此错误的解决方法

    (1) app/build.gradle android { compileSdkVersion buildToolsVersion "25.0.2" } compile (pro ...

  7. Android Studio 入口程序的设置方法

    在src -> main中 ,打开 AndroidManifest.xml 这个文件 下面这里有两个窗口,如果要想把哪个窗口设置成入口窗体,只要把下面红色的放在这个节点中就可以了 <act ...

  8. React Native 获取组件(Component)在屏幕上的位置

    年后主客户端的需求以及老的业务迁移RN,现在疯狂的在学RN.在迁移需求的时候遇到需要获取组件在屏幕上的绝对位置.页面如下: 就需要展开的时候获取sectionHeader(默认排序)在屏幕上的具体位置 ...

  9. Gravatar 头像使用

    Gravatar :如果在Gravatar的服务器上放置了你自己的头像,那么在任何支持Gravatar的blog或者留言本上留言时,只要提供你与这个头像关联的email地址,就能够显示出你的Grava ...

  10. spring boot 项目部署在阿里云上

    装jdk, 然后 nohup java -jar xxxx.jar> system.log 2>&1 & lsof -i:9999 kill -9 1234