Ausweg aus dem Referenzdilemma

Einklappen
X
 
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • Ausweg aus dem Referenzdilemma

    Hallo.
    Nocheinmal wende ich man an Euch

    Ich verstehe folgende Aussage nicht:
    Ausweg aus dem Referenzdilemma
    Wie also kann man sicherstellen, daß eine Referenz auch als solche behandelt wird? In PHP gibt es dafür spezielle Syntax, bei der das Kaufmanns-Und den Referenz-Charakter einer Zuweisung oder einer Variablen-Betrachtung anzeigt:
    PHP-Code:
    $a = &$b;
    $c =& $a;
    $c 42
    Die beiden obigen Zuweisung bewirken gleichermaßen, daß jeweils beide Variablen nicht nur denselben Wert haben, sondern auch eine Wertänderung einer der Variablen automatisch die exakt selbe Wertänderung der anderen Variablen bewirkt. Die letzte Anweisung bewirkt folglich, daß alle drei Variablen den Wert 42 annehmen.

    Auch bei Funktions- und Methodenaufrufen wird normalerweise Kopiersemantik angewandt. Um explizit Referenzsemantik zu fordern, stellt man hierbei bei einer Funktions-/Methodendefinition einer zu referenzierenden Parameter-Variablen ein Kaufmanns-Und voran:
    PHP-Code:
    function Plus($var) { $var++; }
    function 
    Minus(&$var) { $var--; }
    $var 42;
    Plus($var);
    Minus($var);
    echo 
    $var
    Die Ausgabe obigen Codes ist natürlich 41, da nur die Funktion Minus den Wert der Variablen $var ändert. In Java wäre das nicht so offensichtlich, da hier u.U. eine Klassenvariable namens var existieren könnte, die implizit gemeint sein könnte. In PHP müßte man dann aber $this->var schreiben.

    Unter 20.7.2 findet sich noch eine Übung zu komplexeren Anwendungen der Referenzsemantik.
    Wovon ist gerade die Rede?
    Obiger Code:
    PHP-Code:
    $a = &$b;
    $c =& $a;
    $c 42
    bezieht sich auf eine Klasse, richtig?
    Wenn ja, warum sollte $a = 42 sein?
    Sie wird später definiert.
    Also müssen die Variablen ja Objekte sein die angelegt wurden.
    Folglich nehmen Objekte, auch wenn sie vorher definiert werden,
    die Werte an, welche durch andere Objekte springen können (wenn definiert)
    und so einen Wert ändern, trotz das diese vorher definiert wurden ?!?

    Oder wie jetzt?
    Das bringt mich zum Grübeln...

  • #2
    Zitat von phpMorpheus2 Beitrag anzeigen
    Obiger Code:
    PHP-Code:
    $a = &$b;
    $c =& $a;
    $c 42
    bezieht sich auf eine Klasse, richtig?
    Nein.

    Wenn ja, warum sollte $a = 42 sein?
    Sie wird später definiert.
    Gerade deshalb, weil $c eine Referenz auf $a darstellt.

    Zum Lesen: http://www.php.net/manual/de/language.references.php
    I don't believe in rebirth. Actually, I never did in my whole lives.

    Kommentar


    • #3
      Das bringt mich zum Grübeln...
      Das hat nix mit "Objecte" zu tun. Objecte werden seit PHP5 immer per Referenz übergeben. Es sei denn du Clonst sie.
      Wir werden alle sterben

      Kommentar


      • #4
        Danke. Jetzt habe ich es verstanden.
        Jede Referenz verändert also die ursprüngliche Variable.

        Kommentar


        • #5
          Ich hatte nicht gewusst,
          das PHP4 den Zuweisungsoperator =& verwendet,
          PHP5 jedoch nur = zum referenzieren benötigt.
          Daher hat mich =& und = verwirrt.
          Jetzt weiß ich, dass es die selben Zuweisungsoperatoren sind, = jedoch neuer ist.

          Kommentar


          • #6
            Zitat von phpMorpheus2 Beitrag anzeigen
            Ich hatte nicht gewusst,
            das PHP4 den Zuweisungsoperator =& verwendet,
            PHP5 jedoch nur = zum referenzieren benötigt.
            Daher hat mich =& und = verwirrt.
            Jetzt weiß ich, dass es die selben Zuweisungsoperatoren sind, = jedoch neuer ist.
            Das ist so allgemeingültig falsch.

            Objekte werden unter PHP 5 immer per Referenz übergeben.
            Für Skalare gilt das natürlich so nicht. Da braucht es immer noch das &, wenn mit Referenzen gearbeitet werden soll.
            I don't believe in rebirth. Actually, I never did in my whole lives.

            Kommentar


            • #7
              Semi-Offtopic, weil es hier irgendwie passt:

              Ein Array wird ebenfalls standardmäßig per Referenz übergeben, wenn sein Inhalt im Funktionskörper nicht verändert wird. Es scheint sich sogar negativ auf die Geschwindigkeit auszuwirken, dem Array (das nicht verändert wird) in der Funktionssignatur ein & voranzustellen.

              Das ist natürlich eine nachvollziehbare Optimierung, aber weiß jemand, wo man das nachlesen kann?

              Gruß Marc

              Kommentar


              • #8
                Zitat von mermshaus Beitrag anzeigen
                Ein Array wird ebenfalls standardmäßig per Referenz übergeben, wenn sein Inhalt im Funktionskörper nicht verändert wird.
                Wie willst du das feststellen, wenn diese Referenz verfällt, sobald sich etwas ändert?
                [FONT="Helvetica"]twitter.com/unset[/FONT]

                Shitstorm Podcast – Wöchentliches Auskotzen

                Kommentar


                • #9
                  Es scheint sich sogar negativ auf die Geschwindigkeit auszuwirken, dem Array (das nicht verändert wird) in der Funktionssignatur ein & voranzustellen.
                  Unglaublich, aber wahr!

                  Testcode:

                  PHP-Code:
                  function test1(Array $array)
                  {
                      return 
                  count($array);
                  }

                  function 
                  test2(Array &$array)
                  {
                      return 
                  count($array);

                  }


                  $array range(1,300000);





                  $start microtime(true);
                  for (
                  $i=0;$i<20;$i++)
                  {
                    
                  test1($array);
                  }
                  $used microtime(true)-$start;
                  echo 
                  sprintf('%01.5f <hr>',$used);


                  $start microtime(true);
                  for (
                  $i=0;$i<20;$i++)
                  {
                    
                  test2($array);
                  }
                  $used microtime(true)-$start;
                  echo 
                  sprintf('%01.5f <hr>',$used); 
                  Wir werden alle sterben

                  Kommentar


                  • #10
                    Zitat von unset Beitrag anzeigen
                    Wie willst du das feststellen, wenn diese Referenz verfällt, sobald sich etwas ändert?
                    Ja, das hätte ich nicht so absolut ausdrücken sollen. Das ist die Vermutung aus den Benchmark-Zeiten.

                    Bei Strings ist es auch so:

                    Edit: Okay, vielleicht jetzt nicht gerade *die* Entdeckung.

                    PHP-Code:
                    ini_set('memory_limit''64M');

                    function 
                    test1($s)
                    {
                        return 
                    strlen($s);
                    }

                    function 
                    test2(&$s)
                    {
                        return 
                    strlen($s);
                    }


                    $s str_repeat(' '300000);





                    $start microtime(true);
                    for (
                    $i=0;$i<20;$i++)
                    {
                      
                    test1($s);
                    }
                    $used microtime(true)-$start;
                    echo 
                    sprintf('%01.5f <hr>',$used);


                    $start microtime(true);
                    for (
                    $i=0;$i<20;$i++)
                    {
                      
                    test2($s);
                    }
                    $used microtime(true)-$start;
                    echo 
                    sprintf('%01.5f <hr>',$used); 

                    Kommentar


                    • #11
                      Zitat von mermshaus Beitrag anzeigen
                      Ja, das hätte ich nicht so absolut ausdrücken sollen. Das ist die Vermutung aus den Benchmark-Zeiten.
                      Mir ging es eher um die Aussage, dass Arrays als Referenz übergeben werden, solange sich nichts an ihnen ändert. Wie hast du das herausgefunden?
                      [FONT="Helvetica"]twitter.com/unset[/FONT]

                      Shitstorm Podcast – Wöchentliches Auskotzen

                      Kommentar


                      • #12
                        Das nennt sich Copy-On-Write und ist eine gängige Technik. Das Anlegen einer Kopie kostet Zeit und Speicher. Deshalb wird darauf verzichtet bis zum ersten Schreibzugriff.

                        Kommentar


                        • #13
                          @unset:

                          Um die Aussage ging es mir auch, denn genau weiß (wusste ) ich nicht, ob das so ist, weil ich noch keine Dokumentation gefunden habe, die das bestätigt. Es ist nur der sinnvolle Schluss aus den Benchmarkergebnissen.

                          Bemerkt habe ich das, als ich einen Absatz zum Thema Codeoptimierung durch Übergabe von primitiven Datentypen (dazu habe ich das Array gezählt) als Referenz schreiben wollte und quasi den Benchmark von combie gebaut habe, um zu gucken, wie unglaublich viel Zeit man spart.

                          @onemorenerd:

                          Hast du auch eine Erklärung, wieso die Funktion test2() in combies Benchmark so lange braucht?

                          Kann man Primitive etwa gar nicht als "echte" Referenzen übergeben? Kopiert PHP die entsprechenden Daten intern trotzdem beim Aufruf hin und bei Funktionsende wieder zurück und gaukelt die entsprechende Funktionalität bloß vor? PHP: Passing by Reference - Manual

                          Das wäre eine krasse Bildungslücke.

                          Edit: Heißen die deshalb Primitive?
                          Edit2: Habe mir nie viel hierbei gedacht: "References in PHP are a means to access the same variable content by different names. They are not like C pointers; instead, they are symbol table aliases."
                          Zuletzt geändert von mermshaus; 24.06.2009, 11:51. Grund: Au wei au wei

                          Kommentar


                          • #14
                            Diesen extremen Unterschied in combies Benchmark zweifle ich an. Hier mal ein anderer, bei dem die Tests nicht sequentiell sondern durch einzelne Requests und dadurch unter identischen Bedingungen ausgeführt werden. usleep() verwende ich als noop um zu verhindern, dass der Parser die Funktion oder den If-Block wegoptimiert.
                            PHP-Code:
                            <?php

                            function t_ ($a)                           { usleep(0); }
                            function 
                            t_ref (&$a)                       { usleep(0); }
                            function 
                            t_hint (array $a)                 { usleep(0); }
                            function 
                            t_hint_ref (array &$a)            { usleep(0); }
                            function 
                            t_read ($a)                       { if ($ausleep(0); }
                            function 
                            t_ref_read ($a)                   { if ($ausleep(0); }
                            function 
                            t_hint_read (array $a)            { if ($ausleep(0); }
                            function 
                            t_hint_ref_read (array &$a)       { if ($ausleep(0); }
                            function 
                            t_write ($a)                      { usleep(0); $a 0; }
                            function 
                            t_ref_write ($a)                  { usleep(0); $a 0; }
                            function 
                            t_hint_write (array $a)           { usleep(0); $a 0; }
                            function 
                            t_hint_ref_write (array &$a)      { usleep(0); $a 0; }
                            function 
                            t_read_write ($a)                 { if ($ausleep(0); $a 0; }
                            function 
                            t_ref_read_write ($a)             { if ($ausleep(0); $a 0; }
                            function 
                            t_hint_read_write (array $a)      { if ($ausleep(0); $a 0; }
                            function 
                            t_hint_ref_read_write (array &$a) { if ($ausleep(0); $a 0; }

                            header('Content-Type: text/plain');
                            if (isset(
                            $_GET['func']) && substr($_GET['func'], 02) == 't_' && isset($_GET['size']) && isset($_GET['runs'])) {
                                
                            $orig range(1, (int)$_GET['size']);
                                
                            $start microtime(true);
                                for (
                            $i 0$i < (int)$_GET['runs']; $i++) {
                                    
                            $copy $orig;
                                    
                            $_GET['func']($copy);
                                }
                                echo 
                            sprintf("%01.5f µs - %d Bytes",
                                    
                            microtime(true) - $start,
                                    
                            memory_get_peak_usage(true));
                            }
                            else {
                                
                            $url   'http://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'];
                                
                            $runs  = isset($_GET['runs']) ? (int)$_GET['runs'] : 1000;
                                
                            $size  = isset($_GET['size']) ? (int)$_GET['size'] : 1000;
                                
                            $funcs get_defined_functions();

                                echo 
                            "runs: $runs\nsize: $size\n";

                                foreach (
                            $funcs['user'] as $func) {
                                    echo 
                            file_get_contents($url.'?func='.$func.'&size='.$size.'&runs='.$runs)." - $func\n";
                                }
                            }
                            Code:
                            runs: 10000
                            size: 1000
                            0.09862 µs - 262144 Bytes - t_
                            0.75749 µs - 262144 Bytes - t_ref
                            0.09803 µs - 262144 Bytes - t_hint
                            0.75746 µs - 262144 Bytes - t_hint_ref
                            0.09851 µs - 262144 Bytes - t_read
                            0.09817 µs - 262144 Bytes - t_ref_read
                            0.09805 µs - 262144 Bytes - t_hint_read
                            0.75787 µs - 262144 Bytes - t_hint_ref_read
                            0.09879 µs - 262144 Bytes - t_write
                            0.09921 µs - 262144 Bytes - t_ref_write
                            0.09975 µs - 262144 Bytes - t_hint_write
                            0.76339 µs - 262144 Bytes - t_hint_ref_write
                            0.09973 µs - 262144 Bytes - t_read_write
                            0.10127 µs - 262144 Bytes - t_ref_read_write
                            0.10139 µs - 262144 Bytes - t_hint_read_write
                            0.76083 µs - 262144 Bytes - t_hint_ref_read_write
                            Mein Fazit:
                            Type Hints allein kosten fast nichts.
                            Referenzen allein kosten fast nichts.
                            Type Hints auf Referenzen sind teuer.

                            Warum das so ist kann ich aus dem Stand nicht sagen. Wenn du es unbedingt wissen willst, schau in den Source Code des Interpreters. Mir ist der Unterschied zu gering, ich mach mir da keinen Kopf.

                            Übrigens kannst du bei diesem Benchmark mal langsam Size hoch schrauben und wirst sehen, dass Type Hints auf Referenzen auch mehr Speicher brauchen.

                            http://derickrethans.nl/files/phparc...es-article.pdf Unbedingt lesen!

                            Kommentar


                            • #15
                              [COLOR=Red]Edit: Kommando zurück. Im Benchmark-Code war noch eine Zeile $copy = $orig, die ich übersehen hatte. Das heißt, es existieren bereits zwei Bezeichner, die auf denselben Inhalt zeigen. Wenn dann auf einen der beiden noch eine Referenz gesetzt wird, dann wird wohl kopiert, iirc (siehe Artikel). Jedenfalls sind dann die Benchmark-Zeiten gleich ganz anders, wenn die entsprechende Zeile auskommentiert und $orig übergeben wird.[/COLOR]


                              Den Artikel hatte ich auch gefunden und ich denke mal, Figure 7 sollte unserem Problem hier entsprechen. Allerdings sehe ich da nicht, wo bei Aufruf der Inhalt der per Referenz an die Funktion übergebenen Variable kopiert wird (oder ähnliches). Das sieht für mich so aus, als würde da nur ein zweiter "Zeiger" auf denselben Speicherinhalt gesetzt. Wo geht da die Zeit verloren (siehe unten)?

                              Vielen Dank für den Benchmark! Der sieht von Aufbau sehr gelungen aus. Allerdings hast du in den Funktionsdeklarationen ein paar Referenzen vergessen und ich glaube außerdem, dass das Schreiben eher mit $a[0] = 0; geprüft werden sollte, da er sonst nicht zu "copy on write" gezwungen ist. (Könnte zudem sprintf die memory_get_peak_usage bei kleiner $size beeinflussen? Das habe ich jetzt nicht getestet.)

                              Mit den beschriebenen Änderungen ergibt sich:

                              Code:
                              runs: 10000
                              size: 1000
                              
                              0.70719 µs - 262144 Bytes - t_
                              2.92888 µs - 262144 Bytes - t_ref
                              0.62768 µs - 262144 Bytes - t_hint
                              2.35741 µs - 262144 Bytes - t_hint_ref
                              
                              0.64666 µs - 262144 Bytes - t_read
                              2.45519 µs - 262144 Bytes - t_ref_read
                              0.64916 µs - 262144 Bytes - t_hint_read
                              2.31166 µs - 262144 Bytes - t_hint_ref_read
                              
                              2.33536 µs - 262144 Bytes - t_write
                              2.43142 µs - 262144 Bytes - t_ref_write
                              2.43154 µs - 262144 Bytes - t_hint_write
                              2.25035 µs - 262144 Bytes - t_hint_ref_write
                              
                              2.05961 µs - 262144 Bytes - t_read_write
                              2.41068 µs - 262144 Bytes - t_ref_read_write
                              2.45428 µs - 262144 Bytes - t_hint_read_write
                              2.45996 µs - 262144 Bytes - t_hint_ref_read_write
                              Wenn ich keinen Unsinn gemacht habe, sind die Referenzen also immer langsam.

                              Habe mir ein wenig was abgeguckt und noch mal ein anderes Beispiel versucht:

                              PHP-Code:
                              header('content-type: text/plain');

                              $array range(130000);            echo memory_get_peak_usage(), " - start\n";

                              if (
                              is_array($array)) { usleep(0); } echo memory_get_peak_usage(), " - is_array(original)\n";

                              $a = &$array;                        echo memory_get_peak_usage(), " - copy to ref\n";

                              $a[0] = 0;                           echo memory_get_peak_usage(), " - write ref\n";

                              if (
                              $a[0]) { usleep(0); }            echo memory_get_peak_usage(), " - read ref\n";

                              if (
                              is_array($a)) { usleep(0); }     echo memory_get_peak_usage(), " - is_array(ref)\n"
                              Ausgabe:

                              Code:
                              3187556 - start
                              3187660 - is_array(original)
                              3187724 - copy to ref
                              3187724 - write ref
                              3187724 - read ref
                              5118904 - is_array(ref)
                              Ich kann mir noch keinen Reim drauf machen, weiß aber aktuell auch nicht mehr, was sich eigentlich zu messen lohnt.

                              Edit: http://bugs.php.net/bug.php?id=34540 *schulterzuck*

                              Vorläufiges Fazit: Es ist vermutlich immer eine sehr ineffiziente Idee ist, Arrays als Referenz zu übergeben.
                              Zuletzt geändert von mermshaus; 24.06.2009, 15:52. Grund: Sorry für das Chaos

                              Kommentar

                              Lädt...
                              X