PHP verwendet ein Copy-on-Modification-System.
Tut $a = (string) $a;
($a ist bereits eine Zeichenfolge) irgendetwas ändern und kopieren?
Insbesondere ist dies mein Problem:
Parameter 1 ist mixed
/ Ich möchte erlauben, Nicht-Strings zu übergeben und sie in Strings umzuwandeln.
Aber manchmal sind diese Zeichenfolgen sehr groß. Also möchte ich das Kopieren eines Params weglassen, das ist schon ein String.
Kann ich Version verwenden Foo
oder muss ich Version verwenden Bar
?
class Foo {
private $_foo;
public function __construct($foo) {
$this->_foo = (string) $foo;
}
}
class Bar {
private $_bar;
public function __construct($bar) {
if (is_string($bar)) {
$this->_bar = $bar;
} else {
$this->_bar = (string) $bar;
}
}
}
Die Antwort lautet: Ja, es kopiert die Zeichenfolge. Irgendwie… Nicht wirklich. Nun, es hängt von Ihrer Definition von “Kopie” ab …
>= 5.4
Um zu sehen, was passiert, schauen wir uns die Quelle an. Der Executor handhabt einen variablen Cast in 5.5 hier.
zend_make_printable_zval(expr, &var_copy, &use_copy);
if (use_copy) {
ZVAL_COPY_VALUE(result, &var_copy);
// if optimized out
} else {
ZVAL_COPY_VALUE(result, expr);
// if optimized out
zendi_zval_copy_ctor(*result);
}
Wie Sie sehen können, verwendet der Anruf zend_make_printable_zval()
was nur kurzschließt, wenn das zval bereits eine Zeichenfolge ist.
Der Code, der zum Kopieren ausgeführt wird, ist also (der Else-Zweig):
ZVAL_COPY_VALUE(result, expr);
Schauen wir uns nun an Die Definition von ZVAL_COPY_VALUE
:
#define ZVAL_COPY_VALUE(z, v) \
do { \
(z)->value = (v)->value; \
Z_TYPE_P(z) = Z_TYPE_P(v); \
} while (0)
Beachten Sie, was das tut. Die Saite selbst ist NICHT kopiert (die in der ->value
Block des zval). Es wird nur referenziert (der Zeiger bleibt derselbe, also ist der String-Wert derselbe, keine Kopie). Aber es erstellt eine neue Variable (den zval-Teil, der den Wert umschließt).
Jetzt kommen wir in die zendi_zval_copy_ctor
Anruf. Was intern einige interessante Dinge selbst macht. Notiz:
case IS_STRING:
CHECK_ZVAL_STRING_REL(zvalue);
if (!IS_INTERNED(zvalue->value.str.val)) {
zvalue->value.str.val = (char *) estrndup_rel(zvalue->value.str.val, zvalue->value.str.len);
}
break;
Im Grunde bedeutet dies, dass, wenn es sich um eine Internet-Zeichenfolge handelt, diese nicht kopiert wird. aber wenn nicht, dann wird kopiert… Also, was ist ein internierter String und was bedeutet das?
<= 5.3
In 5.3 gab es keine internierten Strings. Der String wird also immer kopiert. Das ist eigentlich der einzige Unterschied…
Benchmark-Zeit:
Nun, in einem Fall wie diesem:
$a = "foo";
$b = (string) $a;
In 5.4 wird keine Kopie des Strings erstellt, aber in 5.3 wird eine Kopie erstellt.
Aber in so einem Fall:
$a = str_repeat("a", 10);
$b = (string) $a;
Eine Kopie Wille treten bei allen Versionen auf. Das liegt daran, dass in PHP nicht alle Strings interniert werden …
Probieren wir es in einem Benchmark aus: http://3v4l.org/HEelW
$a = "foobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisout";
$b = str_repeat("a", 300);
echo "Static Var\n";
testCopy($a);
echo "Dynamic Var\n";
testCopy($b);
function testCopy($var) {
echo memory_get_usage() . "\n";
$var = (string) $var;
echo memory_get_usage() . "\n";
}
Ergebnisse:
-
5.4 – 5.5 Alpha 1 (ohne andere Alphas, da die Unterschiede gering genug sind, um keinen grundlegenden Unterschied zu machen)
Static Var
220152
220200
Dynamic Var
220152
220520
Die statische Variable wurde also um 48 Bytes erhöht und die dynamische Variable um 368 Bytes.
-
5.3.11 bis 5.3.22:
Static Var
624472
625408
Dynamic Var
624472
624840
Die statische Variable wurde um 936 Byte erhöht, während die dynamische Variable um 368 Byte erhöht wurde.
Beachten Sie also, dass in 5.3 sowohl die statischen als auch die dynamischen Variablen kopiert wurden. Der String wird also immer dupliziert.
Aber in 5.4 mit statischen Strings wurde nur die zval-Struktur kopiert. Das bedeutet, dass der String selbst, der interniert wurde, gleich bleibt und nicht kopiert wird …
Eine andere Sache
Eine andere Sache, die zu beachten ist, ist, dass alle oben genannten Punkte strittig sind. Sie übergeben die Variable als Parameter an die Funktion. Dann casten Sie innerhalb der Funktion. Copy-on-Write wird also von Ihrer Leitung ausgelöst. Das wird also laufen stets (na ja, in 99,9 % der Fälle) eine variable Kopie auslösen. Sie sprechen also bestenfalls (internierte Zeichenfolgen) von einer Zval-Duplizierung und dem damit verbundenen Overhead. Im schlimmsten Fall redest du von einer Saitenverdopplung…
Ihr Code tut nicht wirklich:
$a = (string)$a;
Es ist eher so, da die Copy-on-Write-Semantik gilt, wenn die Zeichenfolge als Funktionsargument übergeben wird:
$b = (string)$a;
Es gibt einen ziemlich großen Unterschied zwischen diesen beiden Aussagen. Der erste hat keine Auswirkungen auf das Gedächtnis, während der zweite dies tut … normalerweise.
Der folgende Code macht ungefähr das, was Ihr Code machen würde; Eine Zeichenfolge wird übergeben, und Sie wandeln sie um und weisen sie einer anderen Variablen zu. Es verfolgt Zunahmen im Speicher.
<?php
$x = 0;
$y = 0;
$x = memory_get_usage();
$s = str_repeat('c', 1200);
$y = memory_get_usage();
echo $y - $x, PHP_EOL;
$s1 = (string)$s;
$x = memory_get_usage();
echo $x - $y, PHP_EOL;
Ergebnisse (5.4.9):
1360
1360
Ergebnisse (5.3.19):
1368
1368
Die Zuweisung kopiert im Grunde den gesamten Zeichenfolgenwert.
String-Literale verwenden
Bei Verwendung eines String-Literals hängt das Verhalten von der Version ab:
<?php
$x = 0;
$y = 0;
$x = memory_get_usage();
$s="cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";
$y = memory_get_usage();
echo $y - $x, PHP_EOL;
$s1 = (string)$s;
$x = memory_get_usage();
echo $x - $y, PHP_EOL;
Ergebnisse (5.4.9):
152
136
Ergebnisse (5.3.19):
1328
1328
Der Grund dafür ist, dass Zeichenfolgenliterale von der Engine anders behandelt werden, wie Sie aus der Antwort von ircmaxell lesen können.
Überraschenderweise erstellt es eine Kopie:
$string = "TestMe";
debug_zval_dump($string);
$string2 = $string;
debug_zval_dump($string);
$string3 = $string;
debug_zval_dump($string);
$string4 = (string) $string;
debug_zval_dump($string);
$string5 = (string) $string;
debug_zval_dump($string);
Ausgabe:
string(6) "TestMe" refcount(2)
string(6) "TestMe" refcount(3)
string(6) "TestMe" refcount(4)
string(6) "TestMe" refcount(4)
string(6) "TestMe" refcount(4)
Ein weiterer Beweis:
echo memory_get_usage(), PHP_EOL;
$s = str_repeat('c', 100000);
echo memory_get_usage(), PHP_EOL;
$s1 = $s;
echo memory_get_usage(), PHP_EOL;
$s2 = (string) $s;
echo memory_get_usage(), PHP_EOL;
Ausgabe:
627496
727664
727760 # small increase, new allocated object, but no string copy
827928 # oops, we copied the string...