PHP字符串是否不可变?

16

或者:我应该在PHP中优化我的字符串操作吗?我尝试向PHP手册询问,但没有得到任何有关此事的提示。

注:以上内容已翻译并保留了HTML标签。


4
除非出现问题,否则建议您不要担心这个。在现代 Web 应用程序中,字符串操作很少会成为瓶颈。 - Eli
这对我来说唯一需要考虑的情况是在循环中按索引设置大型字符串的每个字符时。由于 PHP 字符串是不可变的,因此这种方法非常有效,但前提是要将字符串初始化为正确的大小。我想我是使用 str_repeat 进行初始化的。 - Hans
6个回答

23

PHP已经进行了优化-变量是使用写时复制copy-on-write分配的,对象是通过引用传递的。在PHP 4中没有这样做,但无论如何,没有人应该为新代码使用PHP 4。


我已经向一位PHP开发人员确认了这一点。 - phatduckk
15
我很希望Rich B停止将合法的(英式)英语拼写转化为美式英语……并非我们所有人都来自美国。 - James B
我个人希望他能像树一样离开。 - user42092
3
@Ant P: 这份手册的表述比较含糊。他们想要表达的是,对象并不是存储在一个被传递的变量中,而是传递了指向实际数据的指针。但是这个指针仍然是按值传递而不是按引用传递的。一个简单的例子可以证明这一点: function f($obj) { $obj = 'foo'; } $obj = new stdClass; f($obj); var_dump($obj);。如果 $ obj 是按引用传递的,它将输出 'foo' 而不是 'stdClass' ;) - NikiC
@Jason 你可能想阅读http://blog.golemon.com/2007/01/youre-being-lied-to.html来正确理解我的评论的含义 ;) - NikiC
显示剩余5条评论

5

在许多编程语言中,最重要的速度优化技术之一是实例重用。在这种情况下,速度增加至少有两个因素:

1. 实例化较少意味着构建所需的时间更短。

2. 应用程序使用的内存越少,可能发生的CPU缓存未命中就越少。

对于速度是第一优先级的应用程序来说,CPU和RAM之间存在一个非常紧密的瓶颈。瓶颈的原因之一是RAM的延迟。

PHP、Ruby、Python等语言与缓存未命中相关,因为它们至少将解释程序的某些(可能全部)运行时数据存储在RAM中。

字符串实例化是一种经常进行的操作,数量相对较大,并且可能对速度产生明显影响。

这里是一个测量实验的run_test.bash:

#!/bin/bash

for i in `seq 1 200`;
do
        /usr/bin/time -p -a -o ./measuring_data.rb  php5 ./string_instantiation_speedtest.php
done

这里是./string_instantiation_speedtest.php和测量结果:

<?php

// The comments on the
// next 2 lines show arithmetic mean of (user time + sys time) for 200 runs.
$b_instantiate=False; // 0.1624 seconds
$b_instantiate=True;  // 0.1676 seconds
// The time consumed by the reference version is about 97% of the
// time consumed by the instantiation version, but a thing to notice is
// that the loop contains at least 1, probably 2, possibly 4,
// string instantiations at the array_push line.
$ar=array();
$s='This is a string.';
$n=10000;
$s_1=NULL;

for($i=0;$i<$n;$i++) {
    if($b_instantiate) {
        $s_1=''.$s;
    } else {
        $s_1=&$s;
    }
    // The rand is for avoiding optimization at storage.
    array_push($ar,''.rand(0,9).$s_1);
} // for

echo($ar[rand(0,$n)]."\n");

?>

我的结论是,通过引用传递字符串值是有意义的,这个实验和我用Ruby 1.8做的另一个实验都证明了这一点。
让“通过引用传递字符串”在整个应用程序范围内生效的一种可能的方法是,在需要使用修改版本的字符串时始终创建一个新的字符串实例。
为了增加局部性和速度,人们可能希望减少每个操作数消耗的内存量。下面的实验演示了字符串连接的情况:
<?php

// The comments on the
// next 2 lines show arithmetic mean of (user time + sys time) for 200 runs.
$b_suboptimal=False; // 0.0611 seconds
$b_suboptimal=True;  // 0.0785 seconds
// The time consumed by the optimal version is about 78% of the
// time consumed by the suboptimal version.
//
// The number of concatenations is the same and the resultant
// string is the same, but what differs is the "average" and maximum
// lengths  of the tokens that are used for assembling the $s_whole.
$n=1000;
$s_token="This is a string with a Linux line break.\n";
$s_whole='';

if($b_suboptimal) {
    for($i=0;$i<$n;$i++) {
        $s_whole=$s_whole.$s_token.$i;
    } // for
} else {
    $i_watershed=(int)round((($n*1.0)/2),0);
    $s_part_1='';
    $s_part_2='';
    for($i=0;$i<$i_watershed;$i++) {
        $s_part_1=$s_part_1.$i.$s_token;
    } // for
    for($i=$i_watershed;$i<$n;$i++) {
        $s_part_2=$s_part_2.$i.$s_token;
    } // for
    $s_whole=$s_part_1.$s_part_2;
} // else

// To circumvent possible optimization one actually "uses" the
// value of the $s_whole.
$file_handle=fopen('./it_might_have_been_a_served_HTML_page.txt','w');
fwrite($file_handle, $s_whole);
fclose($file_handle);

?>

例如,如果组装包含大量文本的HTML页面,则需要考虑生成的HTML不同部分如何连接的顺序。
可用基于BSD许可的PHP实现Ruby实现的分水岭字符串连接算法。相同的算法可以(已经被我)推广以加速任意精度整数的乘法。

4

数组和字符串具有写时复制的行为。它们是可变的,但当你最初将它们赋值给一个变量时,该变量将包含完全相同的字符串或数组实例。只有在修改数组或字符串时才会进行复制。

示例:

$a = array_fill(0, 10000, 42);  //Consumes 545744 bytes
$b = $a;                        //   "         48   "
$b[0] = 42;                     //   "     545656   "

$s = str_repeat(' ', 10000);    //   "      10096   "
$t = $s;                        //   "         48   "
$t[0] = '!';                    //   "      10048   "

3
快速搜索可能会表明它们是可变的,但首选做法是将它们视为不可变的。

为什么呢?是因为删除同一字符串的第一个字符很困难吗?你会如何去做这个操作? - Wolfgang Adamec
你如何知道它们是可变的?这必须是可测试的。你如何看到PHP是否创建了一个新的实例? - Rudie
在终端中运行显示可变性:php -r "$a = 'abc'; $a{1} = 'z'; echo $a;" - Henry
@Henry,提供的代码会抛出错误。我想到了以下 PHP 代码:php -r "$a = 'abc'; $a[1] = 'z'; echo $a;" - hkiame

0

PHP 7.4 使用可变字符串:

<?php
$str = "Hello\n";
echo $str;
$str[2] = 'y';
echo $str;

输出:

Hello
Heylo

测试:PHP沙盒


-5

PHP字符串是不可变的。

试试这个:

    $a="string";
    echo "<br>$a<br>";
    echo str_replace('str','b',$a);
    echo "<br>$a";

它回显:

string
bing
string

如果一个字符串是可变的,它将会继续显示"bing"。

11
str_replace()函数返回一个修改后的字符串副本,并不能证明该字符串是不可变的。而以下代码演示了字符串是可变的: $a = 'hello '; echo $a; $a[3] = 'd'; echo $a; - Anon343224user

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接