Voor eens en altijd: Single quotes vs. Double quotes
Veel mensen zullen je het advies geven om single quotes te gebruiken in plaats van double quotes wanneer je in PHP bezig bent met strings. Men zegt daarbij dat dat sneller is, anderen zullen zeggen dat het leesbaarder is. Dit laatste is erg aan mening onderhevig, maar het eerste deel kunnen we bekijken en eventueel verifieren.
Dus! We gaan vandaag eens even lekker over-the-top-de-nerd-uithangen bij Scriptorama en zullen met behulp van een handy-dandy PHP disassembler PRECIES uitzoeken hoe het nu zit met die vermadelijde quotes!
"The Vulcan Logic Disassembler hooks into the Zend Engine and dumps all the opcodes (execution units) of a script. It was written as as a beginning of an encoder, but I never got the time for that. It can be used to see what is going on in the Zend Engine."
De Vulcan Logic Disassembler kun je in principe installeren via PECL, maar bij mijn installatie werkte dat niet helemaal lekker. Dus heb ik er voor gekozen om het zelf te compileren. Je kunt CVS informatie vinden op de site van Derick Rethans, een van de auteurs van zowel PHP als VLD.
Zoals je misschien wel weet neemt PHP de broncode die jij hebt geschreven en zet deze om naar lijsten van voor PHP uitvoerbare instructies. Deze instructies heten opcodes (operation codes). Elk van deze instructies heeft een bepaalde naam en verwacht bepaalde argumenten. Je kunt het enigzins zien als een eigen versie van assembler. Op het moment dat PHP de broncode heeft omgezet van gewone tekst naar een uitvoerbaar formaat, haakt VLD in en toont deze uitvoerbare opcodes in een leesbaar formaat. De code zelf wordt dan niet uitgevoerd. Dat kan er dan zo uitzien:
-
function helloWorld()
-
{
-
echo "Hello, world.";
-
}
-
-
helloWorld();
wordt:
-
mathieu@loina:~/Articles/quotes $ php test.php
-
filename: /home/mathieu/Articles/quotes/test.php
-
function name: (null)
-
number of ops: 5
-
line # op fetch ext operands
-
-------------------------------------------------------------------------------
-
2 0 NOP
-
7 1 SEND_VAL 15
-
2 DO_FCALL 1 'helloworld', 0
-
10 3 RETURN 1
-
4 ZEND_HANDLE_EXCEPTION
-
-
Function helloWorld:
-
filename: /home/mathieu/Articles/quotes/test.php
-
function name: helloWorld
-
number of ops: 4
-
line # op fetch ext operands
-
-------------------------------------------------------------------------------
-
2 0 RECV 1
-
4 1 ECHO 'Hello%2C+world.'
-
5 2 RETURN null
-
3 ZEND_HANDLE_EXCEPTION
-
-
End of function helloWorld.
Show me that string, baby!
De volgende stap is dan om te kijken wat er precies gebeurd wanneer we een single quoted string toewijzen aan een variabele en wat er gebeurd als we een double quoted string toewijzen:
-
$singleQuoted = 'Hallo, wereld!';
-
$doubleQuoted = "Hallo, wereld!";
-
mathieu@loina:~/Articles/quotes $ php test2.php
-
filename: /home/mathieu/Articles/quotes/test2.php
-
function name: (null)
-
number of ops: 4
-
line # op fetch ext operands
-
-------------------------------------------------------------------------------
-
2 0 ASSIGN !0, 'Hallo%2C+wereld%21'
-
4 1 ASSIGN !0, 'Hallo%2C+wereld%21'
-
6 2 RETURN 1
-
3 ZEND_HANDLE_EXCEPTION
Deze twee notaties genereren dus exact dezelfde instructies. Het is in dit geval zeer waarschijnlijk dat PHP slim genoeg was om al tijdens het verwerken van de broncode te zien dat er binnen de double quoted string geen variabelen staan en daarom de exact zelfde instructies genereert.
Throw some variables in the mix!
Dan wordt de volgende vraag natuurlijk wat er precies gebeurd als een variabele wordt toegevoegd. Eerst maar eens de single quoted versie:
-
$doel = 'wereld!';
-
-
$singleQuoted = 'Hallo, ' . $doel;
-
mathieu@loina:~/Articles/quotes $ php test3.php
-
filename: /home/mathieu/Articles/quotes/test3.php
-
function name: (null)
-
number of ops: 5
-
line # op fetch ext operands
-
-------------------------------------------------------------------------------
-
2 0 ASSIGN !0, 'wereld%21'
-
4 1 CONCAT ~1, 'Hallo%2C+', !0
-
2 ASSIGN !0, ~1
-
6 3 RETURN 1
-
4 ZEND_HANDLE_EXCEPTION
Hier gebeuren een paar dingen:
- ASSIGN: Eerst wordt de waarde 'wereld!' toegekend aan de variabele $doel (regel 2)
- CONCAT: Vervolgens worden er 2 strings aan elkaar geplakt: 'Hallo, ' en de waarde uit $doel. Dit wordt opgeslagen in ~1, wat een tijdelijke interne variabele is. (regel 4)
- ASSIGN: Als laatste wordt de waarde uit de tijdelijke variabele (~1) toegekend aan onze variabele $singleQuoted.
Het daadwerkelijk tonen van de string mbv. de single quoted notatie bestaat dus binnen PHP uit 2 stappen. Laten we nu eens kijken wat er gebeurd als we de doubled quoted versie hiervan uitvoeren:
-
$doel = 'wereld!';
-
-
$doubleQuoted = "Hallo, $doel";
-
mathieu@loina:~/Articles/quotes $ php test4.php
-
filename: /home/mathieu/Articles/quotes/test4.php
-
function name: (null)
-
number of ops: 8
-
line # op fetch ext operands
-
-------------------------------------------------------------------------------
-
2 0 ASSIGN !0, 'wereld%21'
-
4 1 INIT_STRING ~1
-
2 ADD_STRING ~1, ~1, 'Hallo'
-
3 ADD_STRING ~1, ~1, '%2C+'
-
4 ADD_VAR ~1, ~1, !0
-
5 ASSIGN !0, ~1
-
6 6 RETURN 1
-
7 ZEND_HANDLE_EXCEPTION
Yipes! Daar gebeurt een hele hoop meer! In plaats van 2 stappen hebben we voor de precies dezelfde functionaliteit maar liefst 3 meer stappen uit te voeren:
- ASSIGN: Eerst wordt de waarde 'wereld!' toegekend aan de variabele $doel (regel 2)
- INIT_STRING: Een tijdelijke variabele wordt aangemaakt in het geheugen (regel 4)
- ADD_STRING: Het eerste deel van onze string, 'Hallo' wordt toegevoegd aan de tijdelijke variabele (regel 4)
- ADD_STRING: De rest van onze string, ', ', wordt ook toegevoegd aan de tijdelijke variabele (regel 4)
- ADD_VAR: De waarde uit onze $doel variabele wordt toegevoegd
- ASSIGN: de tijdelijke variabele wordt toegewezen aan onze $doubleQuoted variabele.
Kennelijk is het bij het parsen van de double quoted string nodig gebleken om de string op te delen in verschillende elementen. Vervolgens worden al die losse elementen toegevoegd aan de resulterende string. Als we een langere string proberen toe te wijzen, krijgen we:
-
$doel = 'wereld!';
-
-
$doubleQuoted = "Dag wrede en oneerlijke maar toch wel mooie, $doel";
-
mathieu@loina:~/Articles/quotes $ php test4.php
-
filename: /home/mathieu/Articles/quotes/test4.php
-
function name: (null)
-
number of ops: 22
-
line # op fetch ext operands
-
-------------------------------------------------------------------------------
-
2 0 ASSIGN !0, 'wereld%21'
-
4 1 INIT_STRING ~1
-
2 ADD_STRING ~1, ~1, 'Dag'
-
3 ADD_STRING ~1, ~1, '+'
-
4 ADD_STRING ~1, ~1, 'wrede'
-
5 ADD_STRING ~1, ~1, '+'
-
6 ADD_STRING ~1, ~1, 'en'
-
7 ADD_STRING ~1, ~1, '+'
-
8 ADD_STRING ~1, ~1, 'oneerlijke'
-
9 ADD_STRING ~1, ~1, '+'
-
10 ADD_STRING ~1, ~1, 'maar'
-
11 ADD_STRING ~1, ~1, '+'
-
12 ADD_STRING ~1, ~1, 'toch'
-
13 ADD_STRING ~1, ~1, '+'
-
14 ADD_STRING ~1, ~1, 'wel'
-
15 ADD_STRING ~1, ~1, '+'
-
16 ADD_STRING ~1, ~1, 'mooie'
-
17 ADD_STRING ~1, ~1, '%2C+'
-
18 ADD_VAR ~1, ~1, !0
-
19 ASSIGN !0, ~1
-
6 20 RETURN 1
-
21 ZEND_HANDLE_EXCEPTION
Het aantal stappen dat moet worden uitgevoerd is bij double quoted strings afhankelijk van de lengte van je string.
Maar wat betekent het allemaal?!
Je zult nu ongetwijfeld het gevoel hebben dat single quotes wel sneller moeten zijn: alleen al om het feit dat er ogenschijnlijk minder gedaan hoeft te worden. Hoewel het er alle schijn van heeft dat single quotes inderdaad sneller gaan zijn, kunnen we dat natuurlijk niet bepalen zonder een meting te doen.
We zullen daarom een belachelijk simpele benchmark doen. De kleine toewijs scripts die we net hebben ontleedt zullen we nu een paar duizend keer gaan uitvoeren om vervolgens te meten hoe lang ze er over doen. Ons benchmark scriptje zal er ongeveer zo uit zien:
Elk van de hier getoonde nummers is een gemiddelde van 4 metingen:
| Type | 1x | 100x | 1,000x | 100,000x | 1,000,000x |
| Single quoted: | 0.0001445 | 0.00025475 | 0.00092375 | 0.08494075 | 0.80271075 |
| Double quoted: | 0.000149 | 0.0003515 | 0.00231875 | 0.22375475 | 2.31610625 |
Een beetje perspectief
Aan de hand van deze resultaten kunnen we stellen dat het gebruik van single quoted strings sneller is. Natuurlijk moet je dit wel een beetje in perspectief zien. Als jij 100 simpele string operaties doet, wat wel een aardige schatting is van het aantal string operaties in 1 simpel script, scheelt het in ons geval nog geen 1-duizendste van een seconde. Ga je echter zeer veel string operaties doen, met verschillende variabelen en dergelijke, dan zie je dat het gebruik van single quotes en concatentation helemaal nog niet zo'n gek idee is.
Wat nog wel even interessant is, is om te kijken wat er gebeurd wanneer we Zend Optimizer los laten op ons testcode. Zend Optimizer is een gratis module voor PHP waarmee de gegenereerde opcodes geoptimaliseerd worden voordat het word uitgevoerd. We zullen eens kijken wat dat doen met onze meest duidelijke test: 1,000,000x een simpele string operatie:
| Type | Gewoon 1,000,000x |
ZendOptimizer 1,000,000x |
| Single quoted: | 0.80271075 | 0.775341 |
| Double quoted: | 2.31610625 | 1.6547 |
Je ziet dat het bij de single quotes niet bijzonder veel geholpen heeft, maar bij het uitvoeren van de double quotes benchmark zie je dat het gebruik van ZendOptimizer er bijna een seconde eraf geschraapt heeft. De ZendOptimizer doet dus kennelijk toch ook iets met die strings.
Conclusie
In de meeste gevallen is het gebruik van single quoted strings inderdaad sneller al is bij een geringe hoeveelheid operaties (zoals de meeste scripts hebben) het verschil in performance te verwaarlozen. Het ligt er dan maar aan welke notatie wat je fijner vindt werken.
Bedenk hoe dan ook dat het verwerken van strings in de meeste gevallen niet het "probleem" is qua performance. Veel scripts zullen langer bezig zijn met I/O, zoals het verwerken van gegevens uit MySQL, dan dat ze bezig zijn met dit soort taakjes. Richt je dus, in elk geval éérst, op dat soort problemen in plaats van dit soort micro-optimisaties uit te voeren.
Volg Scriptorama via RSS!
Reageer ook!
Goed onderzoek. Het antwoord was al bekend, maar nu is het ook goed onderbouwd!
Door Tri Pham
op 02.19.06 @ 10:35 pm | Permalink
Goed artikel!
Door bokko
op 02.20.06 @ 3:50 pm | Permalink
Leuk onderzoekje! Alleen kan ik nergens teruglezen hoe je ervoor gezorgd hebt, dat de benchmark wel verantwoord is uitgevoerd . Hoe zorg je ervoor dat andere processen niet de uitkomst van de benchmark beinvloeden etc ?
Door Sijmen Ruwhof
op 02.21.06 @ 7:19 pm | Permalink
Zoals gezegd is het getoonde getal een gemiddelde van 4 metingen, vlak achter elkaar uitgevoerd.
Door Mathieu
op 02.21.06 @ 7:35 pm | Permalink
Een variant die ik een beetje mis is de volgende:
"willekeurig aantal woorden".$some_var
m.a.w. een variant waarbij alle variabelen buiten (dubbele) quotes worden gehouden (wat mij voor de leesbaarheid van de code een goede zaak lijkt).
Ik ben heel erg benieuwd naar de opcodes die in dat geval worden geproduceerd en hoeveel tijd het dan allemaal kost, want op deze manier lijkt de conclusie een beetje gestuurd door de aanname dat het gebruik van single quotes sneller zou zijn (of is), en (enkel) voorbeelden die dit ondersteunen.
Als de double-quotes-met-vars-buiten-quotes-variant (nagenoeg) even snel is als de single-quote-variant, dan zou je bij zeer performance intensieve processen niet per sé over hoeven te schakelen naar enkel single quotes, terwijl de uitkomst van dit onderzoekje wellicht anders doet vermoeden.
Door Thomas van den Heuvel
op 05.17.06 @ 10:13 am | Permalink
Een double quoted string zonder variabelen wordt gezien als een single quoted string, zoals je bij het kopje "Show me that string, baby!" kunt zien..
Wordt er een variabele toegevoegd, dan veroorzaakt dat een CONCAT opcode.
Door Mathieu Kooiman
op 05.17.06 @ 10:42 am | Permalink
Het maakt dus geen drol uit of je ' of " gebruikt. Helemaal NIETS! Zeker in dit onderzoek gaat het daar HELEMAAL NIET om! Waar het wel om gaat is variabele in string, of met concat.
Door rudie
op 08.04.06 @ 3:02 pm | Permalink
Wat helaas niet te zien is met de OPcodes, is dat PHP blijkbaar een functie ingebouwd heeft, die controleerd of er een bepaalde combinatie van karakters voorkomt in een double-quoted string, en dus aangeeft of de string geparsed moet worden. Aangezien er meerdere combinaties van karakters mogelijk zijn die een speciale betekenis hebben (bv: \n, $foo, ${$foo}, etc), kost dit wel even tijd. Bij een single-quoted string wordt deze functie uiteraard niet aangeroepen, aangezien deze string bij voorbaat al niet geparsed hoeft te worden.
-RW
Door Remi Woler
op 04.22.07 @ 7:53 pm | Permalink
Ehm, dat staat toch bij het 4e voorbeeld? Die ene met de ADD_VAR opcode?
Door Mathieu Kooiman
op 04.22.07 @ 8:04 pm | Permalink
(Sorry voor de late reactie, ik zal ook maar eens gaan syndicaten, ipv steeds weer via google op deze site te komen)
Er is in de latere voorbeelden wel te zien dat er wat gedaan wordt met de variables, maar de check zelf is niet te zien. Immers, die zou dan al te zien moeten zijn in het voorbeeld waar je laat zien dat strings dezelfde opcodes genereren, onafhankelijk of ze nu omgeven zijn door single of double quotes. Wat ik bedoelde te zeggen is eigenlijk: als je zo precies gaat kijken naar wat er gebeurd, laat dat dan ook zien/weten. Je verhaal lijkt nu te suggereren alsof een single quoted string _exact_ hetzelfde wordt behandeld als een double quoted string. Natuurlijk is dit verschil met hedendaagse computers en optimalisaties natuurlijk niet te merken, maar het is er natuurlijk wel.
Overigens ben ik groot voorstander van single quotes, en ben ik heel blij met je artikel. Schijnbaar willen sommige mensen het van jou wel aannemen ;)
-RW
Door Remi_Woler
op 07.27.07 @ 4:54 pm | Permalink
> Je verhaal lijkt nu te suggereren alsof een
> single quoted string _exact_ hetzelfde wordt
> behandeld als een double quoted string.
Als je dat uit het artikel haalt, vraag ik me af welke stukken je dan overgeslagen hebt, lees de conclusie nog eens.
> Schijnbaar willen sommige mensen het van jou
> wel aannemen ;)
Mja, dat krijg je snel met een goede benchmark. Niettemin is het verschil, zoals je zelf al aangeeft, verwaarloosbaar. Het grootste voordeel van single quotes ten opzichte van dubbele is voor mij dan ook niet de snelheid, maar het feit dat je gedwongen wordt om variabelen buiten quotes te zetten.
Door berry__
op 08.07.07 @ 8:11 am | Permalink
Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>