Cómo creamos PHP 7 el doble de rápido que PHP 5. Parte 1: optimización de estructuras de datos

En diciembre de 2015 , se lanzó PHP 7.0. Las compañías que cambiaron al "siete" notaron que la productividad ha aumentado y la carga en el servidor ha disminuido. Los primeros en pasar a los siete fueron Vebia y Etsy, y tenemos a Badoo, Avito y OLX. Para Badoo, cambiar a los siete le costó $ 1 millón en ahorros de servidor. Gracias a PHP 7 en OLX, la carga promedio del servidor disminuyó 3 veces, aumentó la eficiencia y el ahorro de recursos.

Dmitry Stogov de Zend Technologies habló en HighLoad ++ , lo que aumentó la productividad. En la decodificación: sobre la estructura interna de PHP, sobre las ideas centrales de la versión 7.0, sobre los cambios en las estructuras de datos básicos y los algoritmos que determinaron el éxito.

Descargo de responsabilidad: a partir de marzo de 2019, el 80% de los sitios se ejecutan en PHP, y el 70% de ellos se ejecutan en PHP 5, aunque esta versión no se admite desde el 1 de enero de 2019 . El informe de 2016 de Dmitry sobre los principios por los cuales hubo un doble salto en la productividad entre PHP 5 y 7 también es relevante en marzo de 2019. Para la mitad de los sitios, seguro.

Sobre el orador: Dmitry Stogov comenzó a programar en los años 80: "Electronics B3-34", Basic, ensamblador. En 2002, Dmitry se familiarizó con PHP y pronto comenzó a trabajar para mejorarlo: desarrolló Turck MMCache para PHP, dirigió el proyecto PHPNG y desempeñó un papel importante en el trabajo en JIT para PHP. Los últimos 14 años de ingeniero principal en Zend Technologies.

Zend Technologies está desarrollando PHP y soluciones comerciales basadas en él. En 1999, fue fundada por los programadores israelíes Andy Gutmans y Zeev Suraski, que hace dos años crearon PHP 3. Estas personas estuvieron a la vanguardia del desarrollo de PHP y determinaron en gran medida el aspecto actual del lenguaje y el éxito de la tecnología.

Zend Technologies desarrolla el núcleo de PHP y las aplicaciones para él, y durante el trabajo tuve que escribir extensiones, entrar en todos los subsistemas e incluso participar en proyectos comerciales, a veces nada relacionados con PHP. Pero el tema más interesante para mí siempre ha sido el rendimiento .

Empecé a buscar formas de acelerar PHP incluso antes de unirme a Zend, trabajando en mi propio proyecto que competía con la compañía. Durante el trabajo en el proyecto, entendí completamente el lenguaje y me di cuenta de que trabajar no con el proyecto principal, solo puede influir en ciertos aspectos de la ejecución del script, y todo lo más interesante y efectivo solo se puede crear en el núcleo . Esta comprensión y coincidencia me llevaron a Zend.

Una pequeña digresión en la historia de PHP


PHP no es solo y no solo un lenguaje de programación . PHP significa Página de inicio personal: una herramienta para crear páginas web personales y sitios web dinámicos. El idioma es solo una de sus partes principales. PHP es una gran biblioteca de funciones, muchas extensiones para trabajar con otras bibliotecas de terceros, por ejemplo, para acceder a la base de datos o analizadores XML, así como un conjunto de módulos para comunicarse con varios servidores web.

El programador danés Rasmus Lerdorf presentó PHP en junio de 1995 . En ese momento, era solo una colección de scripts CGI escritos en Perl . En abril de 96, Rasmus introdujo PHP / FI, y en junio se lanzó PHP / FI 2.0. Posteriormente, Andy Gutmans y Zeev Surasky reelaboraron sustancialmente esta versión, y en el 98th lanzó PHP 3.0. Para el año 2000, el lenguaje llegó al tipo que estamos acostumbrados a ver hoy tanto en términos de lenguaje como de arquitectura interna: PHP 4, basado en Zend Engine.

Desde la versión 4, PHP ha evolucionado. El punto de inflexión fue el lanzamiento de PHP 5 en 2004, cuando el modelo de objetos se actualizó por completo . Fue ella quien abrió la era de los frameworks PHP y planteó la cuestión del rendimiento a un nuevo nivel. Anticipando esto, inmediatamente después del lanzamiento de 5.0, en Zend pensamos en acelerar PHP y comenzamos a trabajar para mejorar la productividad.

La versión 7.1, que se lanzó en noviembre de 2016 en pruebas sintéticas, es 25 veces más rápida que la versión 2002 . Según el gráfico de cambios de rendimiento en diferentes ramas, los principales avances son visibles en 5.1 y 7.0.



En la versión 5.1, comenzamos a trabajar en el rendimiento y obtuvimos todo lo que asumimos, pero después de 5.3 nos topamos con una pared, todos los intentos de mejorar el intérprete quedaron en nada.

Sin embargo, encontramos dónde cavar y obtuvimos aún más de lo esperado: aceleración de 2.5 veces en comparación con la versión 5.6 anterior en las pruebas. Pero lo más interesante es que obtuvimos la misma aceleración de 2.5 veces en aplicaciones reales sin cambios. Este es un fenómeno, porque desarrollamos el factor 2 anterior a lo largo de la vida de los cinco en 10 años.



El gran salto en 5.1 en pruebas sintéticas no se nota en aplicaciones reales. La razón es que con diferentes usos, el rendimiento de PHP se basa en los frenos asociados con diferentes subsistemas.

La historia de PHP 7 comienza con un estancamiento de tres años que comenzó en 2012 y terminó en 2015 con el lanzamiento de la séptima versión. Luego nos dimos cuenta de que ya no podíamos aumentar la productividad con pequeñas mejoras de nuestro intérprete y pasamos al lado de JIT.

Deambulando por JIT


Pasamos casi dos años en el prototipo JIT para PHP-5.5. Al principio, generamos un código muy simple: una secuencia de llamadas para controladores estándar, algo así como un código Fort cosido. Luego escribieron su propio Runtime Assembler , un código separado en línea para soluciones alternativas, pero se dieron cuenta de que tales optimizaciones de bajo nivel no daban efecto práctico incluso en las pruebas.

Luego pensamos en derivar tipos de variables utilizando métodos de análisis estático. Al darnos cuenta de la conclusión, de inmediato recibimos una aceleración de 2 veces en las pruebas. Animados, intentaron escribir asignadores de registros globales, pero fallaron. Usamos una representación de alto nivel y era casi imposible usarla para la asignación de registros.

Para evitar problemas de bajo nivel, decidimos probar LLVM, y un año después obtuvimos una aceleración 10x para bench.php , pero nada en aplicaciones reales. Además, compilar aplicaciones reales ahora tomó minutos, por ejemplo, la primera solicitud a Wordpress tomó 2 minutos y no dio aceleración. Por supuesto, esto era completamente inadecuado para la práctica real.

Un buen código es posible con una predicción de tipo adecuada, que funciona mal en aplicaciones reales, y el uso de estructuras de datos PHP hace que el código generado sea ineficiente.

¿Qué se ralentiza?


Repensamos las razones de las fallas y decidimos una vez más ver por qué PHP es lento. La imagen muestra el resultado del perfil de varias solicitudes a la página de inicio de Wordpress.



Menos del 30% se gasta en la interpretación de bytecode, el 20% es la sobrecarga del administrador de memoria, el 13% está trabajando con tablas hash y el 5% está trabajando con expresiones regulares.

Trabajando para JIT, nos libramos solo del primer 30%, y todo lo demás yacía en peso muerto. Casi en todas partes, nos vimos obligados a usar estructuras de datos PHP estándar, lo que implicaba una sobrecarga: asignación de memoria, conteo de referencias, etc. Esta comprensión llevó a la conclusión de que es necesario reemplazar las estructuras de datos clave en PHP. Con esta sustitución de la fundación , comenzó el proyecto PHPNG.

Phpng Nueva generacion


El proyecto se desarrolló después de intentos fallidos de crear JIT para PHP. El objetivo principal es lograr un nuevo nivel de productividad y sentar las bases para futuras mejoras .

Nos prometimos por un tiempo que ya no usaremos pruebas sintéticas para medir el rendimiento; estos son generalmente pequeños programas informáticos que usan una cantidad limitada de datos que se ajusta completamente en la memoria caché del procesador. Las aplicaciones reales, por el contrario, están sujetas a los frenos asociados con la memoria del subsistema, y ​​una sola lectura de la memoria puede costar 100 instrucciones computacionales. El proyecto PHPNG es una refactorización de estructuras clave de datos PHP para optimizar el acceso a la memoria . Sin innovación, 100% compatible con PHP 5.

Cómo cambiar estas estructuras estaba claro. Pero el volumen de cambios dependientes fue enorme, porque el núcleo de PHP en sí era de 150,000 líneas , y casi cada tercio necesitaba ser cambiado. Agregue cien extensiones más que se incluyen en la distribución base, una docena de módulos para diferentes servidores web y se dará cuenta de la grandeza del proyecto.

Ni siquiera estábamos seguros de que terminaríamos el proyecto. Por lo tanto, lanzaron el proyecto en secreto y lo abrieron solo cuando aparecieron los primeros resultados optimistas. Tomó dos semanas simplemente compilar el núcleo . Dos semanas después, bench.php ganó. Pasamos un mes y medio para apoyar Wordpress. Un mes después, abrimos el proyecto: era mayo de 2014. En ese momento, tuvimos una aceleración del 30% en Wordpress . Ya parecía un gran evento.

PHPNG despertó inmediatamente una ola de interés, y en agosto de 2014 se adoptó como base para el futuro de PHP 7 . Ya era otro proyecto, con un conjunto diferente de objetivos, donde la productividad era solo uno de ellos.

PHP 7.0


La versión número 7 en sí estaba en duda. La versión anterior era la quinta. Y el sexto se desarrolló hace varios años y estaba completamente dedicado al soporte nativo de Unicode , pero las decisiones fallidas tomadas en las primeras etapas de desarrollo llevaron a una complejidad excesiva del código del núcleo y cada extensión. Al final, se decidió congelar el proyecto.

En este momento, ya se había acumulado una gran cantidad de material dedicado a PHP 6: discursos en conferencias, libros publicados. Para no confundir a nadie, llamamos al proyecto PHP 7, omitiendo PHP 6. Esta versión fue mucho más afortunada: PHP 7 se lanzó en diciembre de 2015, casi de acuerdo con el plan.

Además del rendimiento, aparecieron algunas innovaciones muy buscadas en PHP 7:

  • Capacidad para definir tipos escalares de parámetros y valores de retorno.
  • Excepciones en lugar de errores: ahora podemos detectarlos y procesarlos.
  • Zero-cost assert() , clases anónimas, inconsistencias de limpieza, nuevos operadores y funciones (<=>, ??) aparecieron.

La innovación es buena, pero volviendo a los cambios internos. Hablemos sobre el camino que ha seguido PHP 7 y hacia dónde nos puede llevar este camino.

zval


Esta es la estructura básica de datos PHP. Se utiliza para representar cualquier valor en PHP . Como nuestro lenguaje se escribe dinámicamente y el tipo de variables puede cambiar durante la ejecución del programa, necesitamos almacenar un campo de tipo (tipo zend_uchar), que puede tomar los valores IS_NULL, IS_BOOL, IS_LONG, IS_DOUBLE, IS_ARRAY, IS_OBJECT, etc., y en realidad El valor representado por la unión (valor), donde se puede almacenar un número entero, número real, cadena, matriz u objeto.

zval en PHP 5


La memoria para cada estructura se asignó por separado en el montón. Además del tipo y el valor, también contenía un contador de referencias a la estructura. Entonces, la estructura tomó 24 bytes, sin contar la sobrecarga del administrador de memoria y el puntero a ella.

La imagen en la parte superior derecha muestra las estructuras de datos que se crearon en la memoria de PHP 5 para un script simple.



En la pila, se asignó memoria para 4 variables representadas por punteros. Los valores mismos (zval) están en el montón. En nuestro caso, estos son solo dos zval, cada uno de los cuales está referenciado por dos variables, y en consecuencia sus contadores de referencia se establecen en 2.

Para acceder a un tipo o un valor escalar, necesita al menos dos lecturas: primero lea el valor del puntero y luego el valor de la estructura. Si necesita leer no un valor escalar, sino, por ejemplo, parte de una cadena o matriz, necesitará al menos una lectura más.

zval en PHP 7


Donde usamos punteros antes, en los siete comenzamos a incrustar zval. Nos hemos alejado del recuento de referencias para tipos escalares. El tipo y el valor de los campos se mantuvieron sin cambios significativos, pero se agregaron algunas banderas más y un lugar reservado, del que hablaré un poco más adelante.



A la izquierda es lo que parecía en PHP 5, y a la derecha, en PHP 7.



Ahora los mismos zval están en la pila. Para leer tipos y valores escalares, solo una instrucción de máquina es suficiente. Todos los valores se agrupan en un área de memoria, lo que significa que cuando trabajemos con variables locales, prácticamente no tendremos pérdidas debido a errores en la memoria caché del procesador. Pero el verdadero poder del nuevo rendimiento se incluye cuando se necesita copiar.

Copiar registro


En la línea superior del guión, se agregó otra asignación.



En PHP5, asignamos memoria del montón para el nuevo zval, inicializamos su int (2), cambiamos el valor del puntero a la variable b y disminuimos el contador de referencia del valor al que b se había referido previamente.

En PHP 7, simplemente inicializamos la variable b directamente en su lugar con unas pocas instrucciones , mientras que en PHP 5 requería cientos de instrucciones. Entonces zval se ve ahora en la memoria.



Estas son dos palabras de 64 bits. La primera palabra es significado: entero, real o puntero. En la segunda palabra, el tipo (dice cómo interpretar el significado), las banderas y un lugar reservado que aún se agregaría al alinear. Pero no desaparece, sino que es utilizado por diferentes subsistemas para almacenar valores relacionados indirectamente.

Las banderas son un conjunto de bits donde cada bit indica si zval admite un protocolo. Por ejemplo, si es IS_TYPE_REFCOUNTED , cuando trabaje con este zval, el motor debe cuidar el valor del contador de referencia. Al asignar, aumente; al salir del alcance, disminuya; si el contador de referencia llega a cero, destruya la estructura dependiente.

De los tipos, en comparación con PHP 5, aparecieron varios nuevos.

  • IS_UNDEF : un marcador de una variable no inicializada.
  • El IS_BOOL único IS_BOOL reemplazó por IS_FALSE e IS_TRUE por separado.
  • Se agregó un tipo separado para enlaces y algunos tipos mágicos más.

Los tipos de IS_UNDEF a IS_DOUBLE son escalares y no requieren memoria adicional. Para copiarlos, es suficiente copiar la primera palabra de máquina de 64 bits con un valor y la mitad del segundo con un tipo y banderas.

Reembolsado


Con otros tipos más difíciles. Todos están representados por una estructura subordinada, y zval simplemente almacena una referencia a esta estructura. Para cada tipo, esta estructura es diferente, pero en términos de OOP, todos tienen un ancestro abstracto común o estructura zend_refcounted. Determina el formato de la primera palabra de 64 bits , donde se almacenan el recuento de referencia y otra información para el recolector de basura.



Esta palabra puede considerarse simplemente como información para el recolector de basura, y las estructuras para tipos específicos agregan sus campos después de esta primera palabra.

Líneas


En el siete para la cadena, almacenamos el valor calculado de la función hash, su longitud y los propios caracteres. El tamaño de dicha estructura es variable y depende de la longitud de la cadena. La función hash se calcula para la cadena una vez, cuando sea necesario. En PHP 5, se volvió a calcular en cada necesidad.



Ahora las cadenas se han convertido en contables de referencia, y si en PHP 5 copiamos los propios caracteres, ahora es suficiente para aumentar el recuento de referencias para esta estructura.

Como en PHP 5, todavía tenemos el concepto de cadenas inmutables o internados . Por lo general, existen en una instancia, viven hasta el final de la consulta y pueden comportarse como valores escalares. No necesitamos ocuparnos del contador de referencias a ellos, y para copiar es suficiente copiar solo zval con la ayuda de cuatro instrucciones de máquina.

Matrices


Las matrices están representadas por una tabla hash incorporada y no son muy diferentes de PHP 5. La tabla hash en sí misma ha cambiado, pero más sobre eso por separado.



Las matrices ahora son una estructura adaptativa que cambia ligeramente su estructura interna y su comportamiento según los datos almacenados. Si almacenamos solo elementos con teclas numéricas cercanas, entonces tenemos acceso a los elementos directamente por índice con una velocidad comparable a la velocidad de las matrices en C.Pero si agrega un elemento con una clave de cadena a la misma matriz, se convierte en un hash real con resolución de colisión.

Así es como se ve la tabla hash en PHP 5.



Esta es una implementación clásica de tabla hash con resolución de colisión utilizando listas lineales (se muestra en la esquina superior derecha). Cada artículo está representado por un cubo. Todos los cubos están vinculados por listas doblemente vinculadas para resolver colisiones, y vinculados por otra lista doblemente vinculada para iterar en orden. Los valores para cada zval se asignan por separado: en Bucket solo almacenamos un enlace a él. Además, las claves de cadena se pueden asignar por separado.

Por lo tanto, para cada tabla hash, debe asignar muchos bloques pequeños de memoria y, para encontrar algo más adelante, debe ejecutar los punteros. Cada transición de este tipo puede causar una pérdida de velocidad y un retraso de ~ 10-100 ciclos de procesador.

Esto es lo que sucedió en PHP 7.



La estructura lógica se mantuvo sin cambios, solo cambió la física. Ahora, bajo una tabla hash, la memoria se asigna con una operación.

En la imagen, en la parte inferior del puntero base, hay elementos, y en la parte superior hay una matriz hash a la que se dirige una función hash. Para las matrices planas o empaquetadas, cuando almacenamos solo elementos con índices numéricos, la parte superior no se asigna en absoluto, y abordamos el Bucket directamente por número.

Para omitir elementos, los ordenamos secuencialmente de arriba a abajo o de abajo a arriba, lo que los procesadores modernos hacen perfectamente. Los valores están incorporados en Buckets, pero el espacio reservado en ellos solo se usa para resolver colisiones. Almacena el índice de otro Bucket con el mismo valor de función hash o el final del marcador de lista.

La memoria para los valores de cadena de las claves se asigna por separado, pero sigue siendo la misma zend_string. Al pegar en una matriz, es suficiente aumentar el contador de referencia de la cadena, aunque antes teníamos que copiar los caracteres directamente, y al buscar, ahora no podemos comparar los caracteres, sino los punteros a las cadenas en sí.

Matrices inmutables


Anteriormente, teníamos cadenas inmutables, pero ahora también han aparecido matrices inmutables. Al igual que las cadenas, no utilizan el recuento de referencia y no se destruyen hasta el final de la solicitud. Este es un script simple que crea una matriz de un millón de elementos, y cada elemento es la misma matriz con un solo elemento "hola".



En PHP 5, en cada iteración de bucle, se creó una nueva matriz vacía, se escribió "hola" y se agregó todo esto a la matriz resultante. En PHP 7, en la etapa de compilación, creamos solo una matriz inmutable que se comporta como un escalar, y la agregamos a la resultante. En el ejemplo presentado, esto nos permite lograr una reducción de más de 10 veces en el consumo de memoria y una aceleración de casi 10 veces.

Las matrices constantes de millones de elementos en aplicaciones reales, por supuesto, no se encuentran a menudo, pero las pequeñas son bastante comunes. En cada uno de ellos obtendrá un pequeño, pero una victoria.

Los objetos


Los enlaces a todos los objetos en PHP 5 se encuentran en un repositorio separado, y en zval solo había un identificador de objeto único.



Para llegar al objeto, hicimos al menos 3 lecturas. Además, la memoria para el valor de cada propiedad del objeto se asignó por separado, y necesitábamos al menos 2 lecturas más para leerlo.

En PHP 7, pudimos pasar al direccionamiento directo.



La dirección zend_object es accesible con una sola instrucción de máquina. Y las propiedades están integradas y para leerlas solo necesita una lectura adicional. También se agrupan, lo que mejora la localidad de los datos y ayuda a los procesadores modernos a no tropezar.

Además de la propiedad predefinida, también se almacena aquí un enlace a la clase de este objeto, algunos controladores, un análogo de tablas de métodos virtuales y una tabla hash para propiedades que no se definieron. En PHP, puede agregar propiedades a cualquier objeto que no se definió originalmente, y si varias instrucciones de la máquina son suficientes para acceder a la propiedad predefinida, para las propiedades no predefinidas tendrá que usar una tabla hash, que requerirá docenas de instrucciones de la máquina. Por supuesto, esto es mucho más caro.

Referencia


Finalmente, tuvimos que introducir un tipo separado para representar enlaces PHP.



Este es un tipo completamente transparente. No es visible para los scripts PHP. Las secuencias de comandos ven otro zval integrado en la estructura zend_reference. Se entiende que nos referimos a una de esas estructuras desde al menos dos lugares, y el contador de referencia de esta estructura siempre es mayor que 1. Tan pronto como el contador cae a 1, el enlace se convierte en un valor escalar regular. El zval incrustado en el enlace se copia al último zval que hace referencia a él, y la estructura misma se elimina.

Parece que trabajar con referencia ahora es mucho más complicado que con otros tipos (y esto es cierto), pero de hecho en PHP 5 tuvimos que hacer un trabajo de complejidad comparable al acceder a cualquier valor (incluso un número entero primo). Ahora estamos aplicando protocolos más complejos a un solo tipo y acelerando así el trabajo con todos los demás, especialmente con valores escalares.

IS_FALSE y IS_TRUE


Ya he dicho que el tipo único IS_BOOL se dividió en IS_FALSE e IS_TRUE por separado. Esta idea se espió en la implementación de LuaJIT y se hizo para acelerar una de las operaciones más comunes: la transición condicional.



Si en PHP 5 se requería leer el tipo, verificar el valor booleano, leer el valor, averiguar si es verdadero o falso y hacer una transición basada en esto, ahora es suficiente simplemente verificar el tipo y compararlo con verdadero:

  • si es cierto, entonces vamos a lo largo de una rama;
  • si no es cierto, ve a otra rama;
  • si es más que cierto, vaya a la llamada ruta lenta (ruta lenta) y allí verificaremos de qué tipo proviene y qué hacer con ella: si es un número entero, debemos comparar su valor con 0, si es flotante, nuevamente con 0 ( pero real), etc.

Convención de convocatoria


Un cambio en la Convención de llamada o la convención de llamada de función es una optimización importante que afecta no solo a las estructuras de datos, sino también a los algoritmos subyacentes. En la imagen de la izquierda hay un pequeño script que consiste en la función foo () y su llamada. A continuación se muestra el código de bytes en el que PHP 5 compiló este script.



Primero, te diré cómo funcionó en PHP 5.

Convención de llamadas en PHP 5


La primera instrucción SEND_VAL fue enviar el valor "3" a la función foo. Para hacer esto, se vio obligada a asignar un nuevo zval en el montón, copiar el valor (3) allí y escribir el valor del puntero a esta estructura en la pila.



Del mismo modo con la segunda instrucción. Además, DO_FCALL inicializó CALL FRAME , reservó un lugar para las variables locales y temporales, y transfirió el control a la función llamada.



La primera RECV verificó el primer argumento e inicializó la ranura en la pila con la variable local correspondiente ($ a). Aquí lo hicimos sin copiar y simplemente aumentamos el contador de referencia del parámetro correspondiente (zval con un valor de 3). De manera similar, la segunda RECV estableció una conexión entre la variable $ by el parámetro 5.



Otras funciones corporales. Sucedió 3 + 5: resultó 8. Esta es una variable temporal y su valor se almacenó directamente en la pila.



REGRESAR y volvemos de la función.



Al regresar, liberamos todas las variables y argumentos que están fuera de alcance. Para hacer esto, revisamos todos los zval referenciados por las ranuras del marco liberado, y para cada uno de ellos disminuimos el recuento de referencia. Si llega a 0, destruya la estructura correspondiente.

Como puede ver, incluso una operación tan simple como enviar una constante a una función requiere asignar nueva memoria, copiar y aumentar el contador de referencia, y luego también duplicar la disminución y la eliminación.

Convención de llamadas en PHP 7


En PHP 7, estos problemas se han solucionado: ahora en la pila no almacenamos los punteros zval, sino los mismos zval.



También introdujimos una nueva instrucción, INIT_FCALL , que ahora es responsable de inicializar y asignar memoria en CALL FRAME , y reservar espacio para argumentos y variables temporales.



SEND_VAL 3 ahora solo copia el argumento en el primer espacio después de CALL FRAME . Siguiente SEND_VAL 5 a la segunda ranura.



Entonces lo más interesante. Parece que DO_FCALL debería pasar el control a la primera instrucción de la función llamada. Pero los argumentos ya han llegado a los espacios reservados para los parámetros variables $ a y $ b, y las instrucciones RECV simplemente no hacen nada. Por lo tanto, simplemente puede omitirlos. Enviamos dos parámetros, por lo que omitimos dos instrucciones. Si enviaron tres, se habrían perdido tres.



Entonces vamos directamente al cuerpo de la función, hacemos suma y regresamos.



Al regresar, borramos todas las variables locales, pero ahora solo para dos ranuras, y dado que tenemos escalares allí, nuevamente no necesitamos hacer nada.



Mi historia está un poco simplificada, no tiene en cuenta las funciones con un número variable de argumentos y la necesidad de verificación de tipo y algunos otros puntos.

La nueva Convención de llamadas ha roto un poco la compatibilidad . PHP tiene funciones como func_get_arg y func_get_args . Si antes devolvían el valor original del parámetro enviado, ahora devuelven el valor actual de la variable local correspondiente, porque simplemente no almacenamos los valores originales. Al igual que los depuradores C.



Además, la función ya no puede tener múltiples parámetros con el mismo nombre. No tenía sentido en esto antes, pero conocí ese código PHP foo($_, $_) . ¿Cómo se ve? (Reconocí a Prolog)

Nuevo administrador de memoria


Habiendo terminado con la optimización de estructuras de datos y algoritmos básicos, una vez más llamamos la atención sobre todos los subsistemas de frenado. El administrador de memoria en PHP 5 tomó casi el 20% del tiempo del procesador en Wordpress.

Después de deshacernos de muchas asignaciones, sus costos generales se redujeron, pero siguieron siendo significativos, y no porque estaba haciendo un trabajo significativo, sino porque tropezó con el caché. Esto sucedió debido al hecho de que utilizamos el clásico algoritmo malloc de Doug Lea, que implicaba encontrar ubicaciones adecuadas de memoria libre viajando a través de enlaces y árboles, y todos estos viajes inevitablemente causaron errores de caché.

Hoy en día, hay nuevos algoritmos de administración de memoria que tienen en cuenta las características de los procesadores modernos. Por ejemplo: jemalloc y ptmalloc de Google . Al principio, intentamos usarlos sin cambios, pero no obtuvimos una victoria, ya que la falta de la funcionalidad específica de PHP hacía que fuera más costoso liberar memoria por completo al final de la solicitud. Como resultado, abandonamos dlmalloc y escribimos algo propio, combinando ideas del antiguo administrador de memoria y jemalloc.

Hemos reducido la sobrecarga de Memory Manager al 5% , hemos reducido la sobrecarga de memoria para la información de servicio y hemos mejorado el uso de la memoria caché de la CPU. Los mapas de bits ahora buscan bloques de memoria adecuados, la memoria para bloques pequeños se asigna desde páginas individuales y se almacena en caché en el momento del lanzamiento, se agregan funciones especializadas para tamaños de bloque usados ​​frecuentemente.

Muchas pequeñas mejoras


Solo hablé sobre las mejoras más importantes, pero hubo muchas más pequeñas. Puedo mencionar algunos de ellos.

  • API rápida para analizar parámetros de funciones internas y una nueva API para iterar sobre HashTable.
  • Nuevas instrucciones de VM: concatenación de cadenas, especialización, superinstrucciones.
  • Algunas funciones internas se han convertido en instrucciones de VM: strlen, is_int.
  • Uso de registros de CPU para registros de VM: IP y FP.
  • Optimización de la duplicación y eliminación de matrices.
  • Usar enlaces cuenta en lugar de copiar donde sea que pueda.
  • PCRE JIT.
  • Optimización de funciones internas y serialización ().
  • Tamaño de código reducido y datos procesados.

Algunos eran muy simples, por ejemplo, solo se necesitaban tres líneas de código para habilitar JIT en expresiones Perl regulares, y esto inmediatamente trajo una aceleración visible (2-3%) a casi todas las aplicaciones. Otras optimizaciones tocaron algunos aspectos estrechos de ciertas funciones de PHP, y no son particularmente interesantes, aunque la contribución total de todas estas mejoras menores es bastante significativa.

¿A qué has venido?


Esta es la contribución de varios subsistemas en WordPress / PHP 7.0.



La contribución de la máquina virtual aumentó al 50%.El Administrador de memoria ya consume menos del 5%, y principalmente no debido a las optimizaciones del Administrador de memoria en sí, sino al reducir el número de llamadas. Si anteriormente en la misma prueba, la memoria se asignó 130 millones de veces, ahora son solo 10 millones. Puede parecer que toda la aceleración principal se logró al reducir la sobrecarga del Administrador de memoria y la cantidad de llamadas debido a la mejora de las estructuras de datos, pero en realidad Todos los subsistemas se han mejorado significativamente.


Las principales fuentes de aceleración:

  • El intérprete comenzó a trabajar 2 veces mejor.
  • Los gastos generales de MM disminuyeron 17 veces.
  • Las tablas hash comenzaron a funcionar 4 veces más rápido.
  • La productividad total de WordPress ha crecido 3.5 veces.

2,5- , . ? , , CPU time, — , . PHP , .

PHP 7


WordPress 3.6 — . - , PHP 7 mysql, , .



, PHPNG. 2/3 . , .

, WordPress, , — 1,5 2- .

PHP 7 HHVM


HHVM.



— . . Facebook . HHVM . , , , , .



PHP 7 — . Vebia, Etsy Badoo. Highload- , .

PHP 7.0 Etsy Badoo -. Badoo .



, 2 , — 7 .

PHP 7.0. , PHP 7.1, .

PHP Russia PHP 8 . PHP, , , — 1 . , , — , , , .

Source: https://habr.com/ru/post/444508/


All Articles