Recientemente escuché nuevamente que la gente de PHP todavía habla de comillas simples versus comillas dobles y que usar comillas simples es solo una micro optimización, pero si te acostumbras a usar comillas simples todo el tiempo ahorrarás una gran cantidad de CPU. ¡ciclos!
"Todo ya está dicho, pero todavía no todos" – Karl Valentin
Es con este espíritu que estoy escribiendo un artículo sobre el mismo tema que Nikita Popov ya hizo hace 12 años (si estás leyendo su artículo, puedes dejar de leer aquí).
PHP realiza interpolación de cadenas, en la que busca el uso de variables en una cadena y las reemplaza con el valor de la variable utilizada:
$juice = "apple"; echo "They drank some $juice juice."; // will output: They drank some apple juice.
Esta función está limitada a cadenas entre comillas dobles y heredoc. El uso de comillas simples (o nowdoc) producirá un resultado diferente:
$juice = "apple"; echo 'They drank some $juice juice.'; // will output: They drank some $juice juice.
Mira eso: PHP no buscará variables en esa cadena entre comillas simples. Entonces podríamos comenzar a usar comillas simples en todas partes. Entonces la gente empezó a sugerir cambios como este...
- $juice = "apple"; $juice = 'apple';
.. porque será más rápido y ahorrará un montón de ciclos de CPU con cada ejecución de ese código porque PHP no busca variables entre comillas simples (que de todos modos no existen en el ejemplo) y todos contentos, caso cerrado.
Obviamente existe una diferencia entre el uso de comillas simples y dobles, pero para comprender lo que está sucediendo debemos profundizar un poco más.
Aunque PHP es un lenguaje interpretado, utiliza un paso de compilación en el que ciertas partes se combinan para obtener algo que la máquina virtual realmente puede ejecutar, que son códigos de operación. Entonces, ¿cómo pasamos del código fuente PHP a los códigos de operación?
El lexer escanea el archivo de código fuente y lo descompone en tokens. Se puede encontrar un ejemplo simple de lo que esto significa en la documentación de la función token_get_all(). Un código fuente PHP de sólo
T_OPEN_TAG (Podemos ver esto en acción y jugar con él en este fragmento de 3v4l.org.
el analizador
El analizador toma estos tokens y genera un árbol de sintaxis abstracta a partir de ellos. Una representación AST del ejemplo anterior se ve así cuando se representa como JSON:
{ "data": [ { "nodeType": "Stmt_Echo", "attributes": { "startLine": 1, "startTokenPos": 1, "startFilePos": 6, "endLine": 1, "endTokenPos": 4, "endFilePos": 13 }, "exprs": [ { "nodeType": "Scalar_String", "attributes": { "startLine": 1, "startTokenPos": 3, "startFilePos": 11, "endLine": 1, "endTokenPos": 3, "endFilePos": 12, "kind": 2, "rawValue": "\"\"" }, "value": "" } ] } ] }En caso de que quieras jugar con esto también y ver cómo se ve el AST para otro código, encontré https://phpast.com/ de Ryan Chandler y https://php-ast-viewer.com/, que ambos le muestran el AST de un fragmento determinado de código PHP.
el compilador
El compilador toma el AST y crea códigos de operación. Los códigos de operación son las cosas que ejecuta la máquina virtual, también es lo que se almacenará en OPcache si tiene esa configuración y está habilitada (lo cual recomiendo encarecidamente).
Para ver los códigos de operación tenemos múltiples opciones (tal vez más, pero conozco estas tres):
- use la extensión volcadora lógica vulcan. También está integrado en 3v4l.org.
- use phpdbg -p script.php para volcar los códigos de operación
- o use la configuración INI opcache.opt_debug_level para OPcache para que imprima los códigos de operación
- un valor de 0x10000 genera códigos de operación antes de la optimización
- un valor de 0x20000 genera códigos de operación después de la optimización
$ echo ' foo.php $ php -dopcache.opt_debug_level=0x10000 foo.php $_main: ... 0000 ECHO string("") 0001 RETURN int(1)Hipótesis
Volviendo a la idea inicial de ahorrar ciclos de CPU al usar comillas simples versus comillas dobles, creo que todos estamos de acuerdo en que esto solo sería cierto si PHP evaluara estas cadenas en tiempo de ejecución para cada solicitud.
¿Qué sucede en tiempo de ejecución?
Entonces, veamos qué códigos de operación crea PHP para las dos versiones diferentes.
Comillas dobles:
0000 ECHO string("apple") 0001 RETURN int(1)vs. comillas simples:
0000 ECHO string("apple") 0001 RETURN int(1)Oye, espera, sucedió algo extraño. ¡Esto parece idéntico! ¿A dónde fue mi micro optimización?
Bueno, tal vez, solo tal vez la implementación del controlador de código de operación ECHO analiza la cadena dada, aunque no hay ningún marcador o algo más que le indique que lo haga... ¿hmm?
Probemos un enfoque diferente y veamos qué hace el lexer en esos dos casos:
Comillas dobles:
T_OPEN_TAG (vs. comillas simples:
Line 1: T_OPEN_TAG (Los tokens todavía distinguen entre comillas dobles y simples, pero verificar el AST nos dará un resultado idéntico para ambos casos; la única diferencia es el rawValue en los atributos del nodo Scalar_String, que todavía tiene comillas simples/dobles, pero el valor utiliza comillas dobles en ambos casos.
Nueva hipótesis
¿Podría ser que la interpolación de cadenas se realice realmente en el momento de la compilación?
Comprobemos con un ejemplo un poco más "sofisticado":
Los tokens para este archivo son:
T_OPEN_TAG (¡Mira las dos últimas fichas! La interpolación de cadenas se maneja en el lexer y, como tal, es una cuestión de tiempo de compilación y no tiene nada que ver con el tiempo de ejecución.
Para completar, echemos un vistazo a los códigos de operación generados por esto (después de la optimización, usando 0x20000):
0000 ASSIGN CV0($juice) string("apple") 0001 T2 = FAST_CONCAT string("juice: ") CV0($juice) 0002 ECHO T2 0003 RETURN int(1)Este es un código de operación diferente al que teníamos en nuestro simple
Vayamos al grano: ¿debería concatizar o interpolar?
Echemos un vistazo a estas tres versiones diferentes:
El primer código de operación asigna la cadena "manzana" a la variable $jugo:
0000 ASSIGN CV0($juice) string("apple")
La primera versión (interpolación de cadenas) utiliza una cuerda como estructura de datos subyacente, que está optimizada para hacer la menor cantidad de copias de cadenas posible.
0001 T2 = ROPE_INIT 4 string("juice: ") 0002 T2 = ROPE_ADD 1 T2 CV0($juice) 0003 T2 = ROPE_ADD 2 T2 string(" ") 0004 T1 = ROPE_END 3 T2 CV0($juice) 0005 ECHO T1
La segunda versión es la más eficaz en cuanto a memoria ya que no crea una representación de cadena intermedia. En lugar de eso, realiza múltiples llamadas a ECHO, que es una llamada de bloqueo desde una perspectiva de E/S, por lo que dependiendo de su caso de uso, esto podría ser una desventaja.
0006 ECHO string("juice: ") 0007 ECHO CV0($juice) 0008 ECHO string(" ") 0009 ECHO CV0($juice)
La tercera versión usa CONCAT/FAST_CONCAT para crear una representación de cadena intermedia y, como tal, podría usar más memoria que la versión de cuerda.
0010 T1 = CONCAT string("juice: ") CV0($juice) 0011 T2 = FAST_CONCAT T1 string(" ") 0012 T1 = CONCAT T2 CV0($juice) 0013 ECHO T1
Entonces... ¿qué es lo correcto a hacer aquí y por qué es la interpolación de cadenas?
La interpolación de cadenas utiliza FAST_CONCAT en el caso de echo "juice: $juice"; o códigos de operación ROPE_* altamente optimizados en el caso de echo "juice: $juice $juice";, pero lo más importante es que comunica la intención claramente y nada de esto ha sido un cuello de botella en ninguna de las aplicaciones PHP con las que he trabajado hasta ahora. así que nada de esto realmente importa.
La interpolación de cadenas es una cuestión de tiempo de compilación. Por supuesto, sin OPcache, el lexer tendrá que verificar las variables utilizadas en cadenas entre comillas dobles en cada solicitud, incluso si no hay ninguna, reduciendo los ciclos de CPU, pero honestamente: ¡el problema no son las cadenas entre comillas dobles, sino no usar OPcache!
Sin embargo, hay una advertencia: PHP hasta 4 (y creo que incluso incluyendo 5.0 y tal vez incluso 5.1, no lo sé) hacía interpolación de cadenas en tiempo de ejecución, por lo que usar estas versiones... hmm, supongo que si alguien realmente todavía usa PHP 5, se aplica lo mismo que arriba: el problema no son las cadenas entre comillas dobles, sino el uso de una versión de PHP desactualizada.
¡Actualiza a la última versión de PHP, habilita OPcache y vive feliz para siempre!
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3