Kopiert (string) einen String?

Lesezeit: 2 Minuten

Benutzer-Avatar
mzimmer

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;
        }
    }
}

Benutzer-Avatar
ircmaxell

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…

  • Ich mag es, das Innere zu kennen, wie und warum etwas funktioniert; nicht nur das tut es. +1

    – David J Eddy

    28. Februar 2013 um 16:49 Uhr

  • Ich hatte das Gefühl, dass es mit der Versionsnummer zusammenhängen könnte (daher habe ich die Version gepostet), froh, dass Sie das alles geklärt haben. Dies ist die richtige (und sehr detaillierte!) Antwort.

    – Karoly Horvath

    28. Februar 2013 um 16:55 Uhr


  • Tolle Antwort! Der ‘One Other Thing’-Teil schließt ab, wonach ich gesucht habe. Vielen Dank!

    – Zimmer

    28. Februar 2013 um 18:31 Uhr

  • @revo PHP7 ändert die Dinge ein wenig, da Zeichenfolgen erstklassige Entitäten sind und nicht kopiert werden, es sei denn, sie werden geändert.

    – ircmaxell

    12. Februar 2016 um 16:03 Uhr

  • @revo ist ein Satz, was bedeutet, dass sie eine dedizierte Datenstruktur und ein Verwaltungssystem für sie haben. Das heißt, sie sind ein definierter Typ, der getrennt von den Variablen verwaltet wird (anders als in 5.x, wo er an die Variable gebunden war).

    – ircmaxell

    23. Februar 2016 um 20:59 Uhr

Benutzer-Avatar
Jack

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.

  • hmm.. das sehe ich nicht. Könntest du meine Antwort überprüfen?

    – Karoly Horvath

    28. Februar 2013 um 16:00 Uhr

  • @KarolyHorvath Nun, Ref-Zählungen sind jedoch nicht dasselbe wie der Speicherverbrauch. Ich bin mir nicht sicher, ob der Refcount hier zusammenhängt.

    – Jack

    28. Februar 2013 um 16:01 Uhr

  • diese Antwort ist falschund der Beweis hat einen Fehler … Sie haben denselben Variablennamen wiederverwendet … Siehe meine Antwort.

    – Karoly Horvath

    28. Februar 2013 um 16:25 Uhr


  • @KarolyHorvath Wie ich bereits sagte, refcount !== Speicherverbrauch; Das Erstellen eines neuen Symbols zum Verweisen auf denselben Wert zählt nicht.

    – Jack

    28. Februar 2013 um 16:28 Uhr

  • @MichelZimmer: aber wenn du bestehst $a in eine Funktion und tun Sie dies innerhalb der Funktion, $a = (string) $a ist das gleiche wie $b = (string) $a aufgrund der Copy-on-Write-Semantik. Also ja, Sie machen sich Sorgen um die zweite …

    – ircmaxell

    28. Februar 2013 um 17:42 Uhr

Benutzer-Avatar
Karl Horvath

Ü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...

1131330cookie-checkKopiert (string) einen String?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy