php如何封装类等级的拼,PHP-不同Str 拼接方法性能对比

问题

在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

/**

* 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

3831071

“.”的VLD分析

3831071

“,”的VLD分析

3831071

“Join”的VLD分析

3831071

“Sprintf Vsprintf”的VLD分析

3831071

3831071

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

可以看出,“.=”比“.”的操作数中,使用了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等耗费内存的现象得到缩减,进而性能提升。

相关鸟哥博客:

VLD结果

“.”

3831071

“,”

3831071

“.=”

3831071

“Join”

3831071

“Sprintf”

3831071

“Vsprintf”

3831071

总结

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

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

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

“Join/Vsprintf”性能较优

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值