Property aanroepen
Je kunt dus dit doen:
Code (php)
Of dit:
Code (php)
Maakt het iets uit?
Gewijzigd op 24/01/2023 22:36:38 door Ozzie PHP
Code (php)
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
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
<?php
$error_reporting_level = E_ERROR | E_WARNING;
error_reporting($error_reporting_level);
$composer_autoload = __DIR__ . '/vendor/autoload.php';
if (file_exists($composer_autoload)) {
require_once $composer_autoload;
}
class Foo
{
public function doThat()
{
//
}
public function doThis()
{
//
}
public function printMe()
{
return 10;
}
}
class Object1
{
public $foo;
public function __construct($foo)
{
$this->foo = $foo;
}
public function example1()
{
$this->foo->doThis();
$this->foo->doThat();
if ($this->foo->printMe() > 5) {
echo 'ok';
}
}
public function example2()
{
$foo = $this->foo;
$foo->doThis();
$foo->doThat();
if ($foo->printMe() > 5) {
echo 'ok';
}
}
}
$foo = new Foo;
$obj = new Object1($foo);
$start = microtime(true);
for ($i = 0; $i < 100000; $i++) {
$obj->example1();
}
$stop = microtime(true);
dump($stop - $start);
$start = microtime(true);
for ($i = 0; $i < 100000; $i++) {
$obj->example2();
}
$stop = microtime(true);
dump($stop - $start);
?>
$error_reporting_level = E_ERROR | E_WARNING;
error_reporting($error_reporting_level);
$composer_autoload = __DIR__ . '/vendor/autoload.php';
if (file_exists($composer_autoload)) {
require_once $composer_autoload;
}
class Foo
{
public function doThat()
{
//
}
public function doThis()
{
//
}
public function printMe()
{
return 10;
}
}
class Object1
{
public $foo;
public function __construct($foo)
{
$this->foo = $foo;
}
public function example1()
{
$this->foo->doThis();
$this->foo->doThat();
if ($this->foo->printMe() > 5) {
echo 'ok';
}
}
public function example2()
{
$foo = $this->foo;
$foo->doThis();
$foo->doThat();
if ($foo->printMe() > 5) {
echo 'ok';
}
}
}
$foo = new Foo;
$obj = new Object1($foo);
$start = microtime(true);
for ($i = 0; $i < 100000; $i++) {
$obj->example1();
}
$stop = microtime(true);
dump($stop - $start);
$start = microtime(true);
for ($i = 0; $i < 100000; $i++) {
$obj->example2();
}
$stop = microtime(true);
dump($stop - $start);
?>
Gewijzigd op 25/01/2023 08:06:20 door Jan Koehoorn
Ik heb zelf ook even getest met jouw voorbeeld en het gaat inderdaad om minimale verschillen. Als je het aantal aanroepen per method ophoogt, dan zie je dat de 2e methode met de gekopieerde variabele (meestal) net iets sneller is. Het gaat dan uiteraard om micro-optimalisatie.
Ik was hier wel nieuwsgierig naar. Het scheelt dus niet veel, maar toch iets. Je zal er de oorlog niet mee winnen, maar als je dus een property binnen een method meerdere keren aanroept, kan het in ieder geval geen kwaad om een lokale variabele te gebruiken.
Ozzie PHP op 24/01/2023 22:35:31:
Als je binnen een method meerdere keren dezelfde property aanroept, biedt het dan een voordeel om die property binnen die method in een variabele op te slaan?
Ik denk dat sommige Preciezen met opgeheven vinger naar het single-responsibility principle (SRP) zouden wijzen omdat zo'n methode al gauw te veel doet.
Ozzie PHP op 24/01/2023 22:35:31:
Een methode in een klasse is geen applicatie, maar daar lijkt dit voorbeeld wel op.
Gelukkig reken ik mezelf meer tot de Rekkelijken. ;-) Als je performance belangrijk vindt, stap dan om te beginnen af van naïeve getters en setters. De snelheidswinst van directe toegang tot class properties is veel groter.
Bedoel je daarmee om met public properties te werken?
Ja.
Oké, maar daarmee introduceer je weer mogelijke andere nadelen. Dat is dus een beetje stuivertje wisselen.
Je kunt deze trade-offs eigenlijk niet (gemakkelijk) eerlijk testen. Je gooit er nu meer geheugen tegenaan voor de snelheidswinst, maar dat is nadelig voor processen die je buiten de test laat. Eén proces voor één client wordt sneller, maar in een multithreaded multiprocessing omgeving met meerdere clients betaal je de prijs dan elders.
Er zijn bijvoorbeeld "oplossingen" die WordPress sneller maken: bij 1 of 2 bezoekers wordt de snelheid dan bijvoorbeeld meer dan verdubbeld, maar bij 10 of meer gelijktijdige bezoekers stort het kaartenhuis al in.
Eens. De snelheidswinst is ook niet bepaald spectaculair te noemen. Wellicht merk je er iets van bij een miljoen aanroepen, maar dat is in de praktijk niet realistisch.
Quote:
Als je binnen een method meerdere keren dezelfde property aanroept, biedt het dan een voordeel om die property binnen die method in een variabele op te slaan? Met voordeel bedoel ik, is het efficiënter?
Het antwoord is ja, het is efficiënter.
Door $this->foo op te slaan in een tijdelijke variabele $foo voorkomt je dat PHP elke keer een extra lookup moet doen naar het geheugenadres dat je eigenlijk wilt gebruiken. Ten koste van een extra pointer, dat wel. Hoe vaker je het geheugen benadert, via de property of een method, hoe meer je bespaart.
Een plek waar ik het gebruikte was in een HTML5-object dat een DOM-object extends.
Natuurlijk moet je wel opletten dat je alleen de pointer kopieert, en niet een heel object waar Ward voor waarschuwt. Dus geen 'deep copy', je wilt alleen maar de 'reference' kopiëren. Bijvoorbeeld met een array:
Code (php)
Hoewel het voor zich zou moeten spreken, zet ik het er voor de zekerheid toch maar even bij.
Toevoeging op 26/01/2023 21:15:43:
Quote:
Met variabelen lokaal dupliceren introduceer je ook een nadeel: je verbruikt meer geheugen.
In dit geval gaat het om één geheugen pointer per variabele.
Ik heb het niet gecontroleerd voor PHP als geïnterpreteerde taal, maar normaal gesproken is een pointer gelijk aan de bitbreedte van je systeem. Een 64-bit machine gebruikt dan 8 bytes per pointer. Dat is echt geen probleem voor de hedendaagse machines met gigabytes aan werkgeheugen.
Code (php)
In dit voorbeeldje spreek ik 3 keer de database controller class aan. Ik sla die class op in de lokale variabele $db. Is die $db nu een pointer naar $this->db of is het een kopie van de database controller class? Ik zou denken dat laatste. Zodra de method is uitgevoerd, zou $db volgens mij weer opgeschoond moeten worden en is het geheugen weer leeg. Als ik by reference gebruik, dan verwijs ik toch nog steeds naar dezelfde pointer? Zou het dan juist niet performance kosten? Ik creëer dan namelijk een extra variabele, en bij iedere aanroep van die variabele moet de oorspronkelijke pointer (de class property) worden opgezocht zou ik denken.
Toevoeging op 27/01/2023 00:14:59:
UPDATE:
@Ward
>> Met variabelen lokaal dupliceren introduceer je ook een nadeel: je verbruikt meer geheugen.
Het lijkt erop dat als ik een variabele lokaal dupliceer het toch geen kopie betreft (en waarschijnlijk dus ook niet een verdubbeling van geheugen).
Ik heb hier een testje gemaakt met de onderstaande code:
Code (php)
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
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
<?php
class Bar {
private $msg;
public function test($msg) {
$this->msg = $msg;
}
public function shout() {
echo $this->msg;
}
}
class Foo {
private $bar;
public function __construct($bar) {
$this->bar = $bar;
}
public function say($msg) {
$bar = $this->bar;
$bar->test($msg);
$this->bar->shout();
}
}
$test_me = new Foo(new Bar);
$test_me->say('ha ha');
?>
class Bar {
private $msg;
public function test($msg) {
$this->msg = $msg;
}
public function shout() {
echo $this->msg;
}
}
class Foo {
private $bar;
public function __construct($bar) {
$this->bar = $bar;
}
public function say($msg) {
$bar = $this->bar;
$bar->test($msg);
$this->bar->shout();
}
}
$test_me = new Foo(new Bar);
$test_me->say('ha ha');
?>
Kijk naar de method 'say' in de class 'Foo'.
Ik kopieer hier d.m.v. $bar = $this->bar een class property naar een lokale variabele. Via deze lokale variabele stel ik de message ($msg) in. Vervolgens gebruik ik de class property (dus niet de lokale variabele) om de $msg weer te geven. Dat blijkt gewoon te werken. Klik hier voor het voorbeeld.
Blijkbaar verwijzen $bar en $this->bar naar hetzelfde object en wordt er dus helemaal niks gekopieerd. Of zie ik het nu verkeerd?
géén object maar een referentie (een reference of alias). Met $bar = $this->bar dupliceer je de referentie, niet het object.
Dat is anders wanneer je werkt met primitieven zoals arrays. Bijvoorbeeld na $server = $_SERVER is $server een complete array, geen referentie.
Als je wilt weten hoe codefragmenten omgaan met geheugengebruik, kun je dat met memory_get_usage() doormeten.
$this->bar is hier inderdaad Dat is anders wanneer je werkt met primitieven zoals arrays. Bijvoorbeeld na $server = $_SERVER is $server een complete array, geen referentie.
Als je wilt weten hoe codefragmenten omgaan met geheugengebruik, kun je dat met memory_get_usage() doormeten.
N.a.v. jouw eerdere opmerking had ik al getest met memory_get_usage echter leverde dat (eindstand minus beginstand) telkens 0 op.
De vraag is nu wel ...
Ik kopieer dus via de lokale variabele de referentie naar een object. Toch werkt die lokale variabele iets sneller dan de class property. Ik ben wel benieuwd hoe dat dan kan. Is die lokale variabele een pointer naar de class property die naar het object verwijst? Of verwijst die lokale variabele rechtstreeks naar het object. Ik vermoed dan het laatste en dat zou ook de (zeer minieme) snelheidswinst kunnen verklaren.
Maar als ik het dus goed begrijp (ik hoor graag of dat mijn conclusie terecht is) kost het gebruik van een lokale variabele indien het gaat om een class property die verwijst naar een object géén extra significant geheugenverbruik, omdat het een pointer betreft naar hetzelfde object. Bij meerdere aanroepen binnen 1 method verlopen de aanroepen naar hetzelfde object via een lokale variabele ietsje sneller. Dus het kan zeker geen kwaad om bij meerdere aanroepen binnen 1 method gebruik te maken van een lokale variabele.
Gewijzigd op 27/01/2023 09:41:54 door Ozzie PHP
Maar wat ik (en anderen) missen is de logica van PHP waarom een array wel 'by value' wordt gekopieerd. Een array is immers geen primitief type:
Gezien dat PHP afstamt van C, moet je het toch met me eens zijn dat dit gedrag onlogisch is. Scalars zijn primitieve typen, waarbij de value zo klein is dat het in een CPU register past, net als pointers (die op hun beurt weer geen PHP references zijn). Die values worden direct gekopieerd. In C geldt dit niet voor arrays en zelfs niet voor strings. Maar in PHP wel:
Code (php)
PHP verbergt standaard vrij veel voor de programmeur, waardoor programma's al snel minder efficiënt worden dan ze zouden kunnen zijn. Het voorkomt ook fouten, vooral als programmeurs niet helemaal op de hoogte zijn van de details. Maar PHP zit er vol mee waardoor het veel minder simpel is dan het lijkt.