2008-12-04 15 views
9

He estado evaluando el rendimiento de un marco que estoy escribiendo en Perl y estoy recibiendo un 50% de disminución en las solicitudes por segundo sobre nuestra base de código existente (algunos aciertos son comprensibles, porque vamos desde el código de espagueti de procedimientos a un marco OOP MVC).¿Cómo puedo ejecutar código ineficiente solo en tiempo de compilación cuando uso mod_perl?

La aplicación se está ejecutando en mod_perl, y he agregado Moose y todo mi código de marco en el startup.pl script, que duplicó mis solicitudes por segundo. Estoy buscando mejorar aún más este número para acercarlo lo más posible a la cantidad existente. El argumento es que esta es una optimización prematura, pero hay un par de ineficiencias flagrantes que me gustaría solucionar y ver cómo afecta el rendimiento.

Al igual que la mayoría de los marcos, tengo un archivo de configuración y un despachador. La parte de configuración es manejada por Config::General, por lo que se requiere un poco de IO y análisis para obtener mi archivo de configuración cargado en la aplicación. ¡El problema más grande que veo aquí es que estoy haciendo esto por CADA PETICIÓN que entra!

Ejecutando Devel :: Dprof en mi aplicación apunta a Config :: General :: BEGIN y un grupo de módulos de IO relacionados como uno de los principales puntos lentos que no es Moose. Entonces, lo que me gustaría hacer, y lo que tiene más sentido en retrospectiva es aprovechar la persistencia de mod_perl y las cosas de compilación de startup.pl para hacer solo el trabajo de cargar en el archivo de configuración una vez, cuando el servidor se inicie.

El problema es que no estoy muy familiarizado con la forma en que esto funcionaría.

Actualmente cada proyecto tiene una clase de programa previo PerlHandler que es bastante delgado y se ve así:

use MyApp; 
MyApp->new(config_file => '/path/to/site.config')->run(); 

MyApp.pm hereda del módulo de Proyecto de marco, que tiene este código:

my $config = Config::General->new(
       -ConfigFile => $self->config_file, 
       -InterPolateVars => 1, 
      );  

$self->config({$config->getall}); 

Para hacer esto solo en tiempo de compilación, tanto mi bootstrap como los módulos base del Proyecto tendrán que cambiar (creo), pero no estoy seguro de qué cambios realizar y aún así mantener el código agradable y sin problemas. Puede alguien señalarme la dirección correcta?

ACTUALIZACIÓN

He probado el bloque BEGIN en cada enfoque módulo de proyecto según lo descrito por ysth en su respuesta. Así que ahora tengo:

package MyApp::bootstrap; 
use MyApp; 

my $config; 
BEGIN 
{ 
    $config = {Config::General->new(...)->getall};   
} 

sub handler { ..etc. 
    MyApp->new(config => $config)->run(); 

Este cambio rápido solo me dio un aumento50% en solicitudes por segundo, lo que confirma mi pensamiento de que el archivo de configuración fue un importante cuello de botella pena de fijación. La cifra de referencia en nuestra vieja máquina dev de crotchety es 60rps, y mi marco ha pasado de 30rps a 45rps con este cambio solo. Para aquellos que dicen que Moose es lento y tiene un tiempo de compilación alcanzado ... Obtuve el mismo (50%) aumento cuando compilé todo mi código Moose en la puesta en marcha como lo hice al precompilar mi archivo de configuración.

El único problema que tengo ahora es que esto viola el principio DRY ya que el mismo código Config :: General-> nuevo está en cada bloque BEGIN con solo la ruta al archivo de configuración que difiere. Tengo algunas estrategias diferentes para limitar esto, pero solo quería publicar los resultados de este cambio.

Respuesta

10

Suponiendo que sus aplicaciones no cambian la configuración en absoluto, se mueven en un bloque begin:

# this code goes at file scope 
my $config; 
BEGIN { 
    $config = { Config::General->new(...)->getall } 
} 

# when creating a new instance 
$self->config($config); 

Y asegúrese de que todos los módulos se compilan en startup.pl.

Puede obtener más elegante, y tener una clase singleton proporcionar el config hash, pero no es necesario.

+1

El problema con esta solución es que debe crear este bloque BEGIN con ese código para cada módulo de proyecto (cada proyecto tiene su propio archivo de configuración). Lo puse rápidamente y obtuve un 50% de aumento en las solicitudes por segundo, así que estoy votando la respuesta de todos modos, porque responde mi pregunta –

+1

Para evitar el "cada proyecto necesita su propia configuración", podrías 1) Combine todos los archivos en 1 con diferentes secciones (podría usar un archivo INI o Config :: ApacheFormat). 2) tienen una clase de configuración que mantiene cada archivo de configuración en un hash y extrae el correcto basado en algunas var. $ ENV. – mpeters

+1

Nunca consideré tener un gran archivo de configuración por lo difícil que sería mantenerlo cuando tienes cientos de proyectos ... pero en realidad hay muchos beneficios al hacer algo así, ya que compartimos conexiones de bases de datos en todos los proyectos, excepto en un puñado de casos. Gracias. –

1

que tenían los mismos problemas en un marco HTML :: Mason instalar, y se encontró que esto funcione bastante bien: En httpd.conf:

PerlRequire handler.pl 
<FilesMatch "\.mhtml$"> 
    SetHandler perl-script 
    PerlHandler YourModule::Mason 
</FilesMatch> 

En el archivo handler.pl, se definen todos sus elementos estáticos como su configuración, manejadores de base de datos, etc. Esto los define en el ámbito de YourModule :: Mason que se compila cuando se inicia el hilo de apache (los nuevos hilos obviamente tendrán una sobrecarga inherente). YourModule :: Mason tiene un método handler que maneja la solicitud.

Admitiré que puede haber algo de magia que esté sucediendo en HTML :: Mason que me está ayudando con esto, pero me funciona, ¿tal vez para ti?

+0

Ver mi respuesta a continuación. Es más que solo tener sus objetos masones instanciados, porque para entonces ya ha cargado una gran cantidad de Mason, que no tiene que ser recargada, y puede ser compartida de forma transparente entre los trabajadores. –

4

Si puede hacer sus clases de Moose immutable, eso podría darle otro aumento de velocidad.

+1

Por supuesto. Esta es una de las primeras cosas que aprendió a hacer al usar Moose. –

+1

Sí, pero es útil para que la gente sepa quién no sabe. (De hecho, cuando probé Moose por primera vez esto no estaba ni siquiera disponible o al menos documentado). – draegtun

-2

JackM tiene la idea correcta.

Al cargar todas sus clases y crear instancias de los objetos de nivel de aplicación (en su caso, la configuración) en el proceso de Apache " madre", usted no tiene que ellas compilar cada vez que un nuevo trabajador desova, ya que ya están disponibles y en la memoria. Los muy meticulosos entre nosotros agregan una línea de "uso" para cada módulo que su aplicación usa regularmente. Si no carga sus paquetes y módulos en el buque nodriza, cada trabajador no solo aprovechará la carga de rendimiento de cargar los módulos, sino que no obtendrá el beneficio de compartir la memoria que ofrecen los sistemas operativos modernos.

Es realmente la otra mitad de la diferencia entre mod_perl y CGI. La primera mitad es el perl-engine persistente de mod_perl frente al perl de reaparición de CGI para cada invocación.

+0

Lea la pregunta? Mencioné startup.pl varias veces. http://perl.apache.org/docs/2.0/user/config/config.html#C_PerlPostConfigRequire_ –

0

Una forma común de acelerar tales cosas con pocos cambios es simplemente usar variables globales y el estado de caché en ellos entre invocaciones de un mismo proceso de Apache:

use vars qw ($config); 
# ... 
$config = Config::General->new(...)->getall 
    unless blessed($config); # add more suitable test here 

No es muy limpio y puede conducir a oscuras errores (aunque "my $ var" lleva a más en mi experiencia) y a veces se come mucha memoria, pero muchas declaraciones de inicialización costosas (repetidas) se pueden evitar de esta manera. La ventaja sobre el uso de BEGIN {}; solo el código es que puede volver a inicializarse en función de otros eventos sin necesidad de reiniciar Apache o matar su proceso (por ejemplo, incluyendo la marca de tiempo de un archivo en el disco en la prueba anterior).

Cuidado con las trampas sin embargo: an easy way to break in

3

de un módulo de import sub se ejecuta en tiempo de compilación, por lo que podría utilizar eso para reducir/eliminar la SECO de ysth's answer.

En el siguiente ejemplo usamos un método de importación para leer el archivo de configuración con los argumentos que se nos dan y luego insertamos esa configuración en el paquete de llamadas.

La advertencia de cualquier variable $config en el paquete de llamada va a quedar anulada por esto.

package Foo_Config; 
use English qw(-no_match_vars); 
sub import { 
    my ($self, @cfg) = @ARG; 
    my $call_pkg  = caller; 
    my $config  = {Config::General->new(@cfg)->getall}; 
    do{ # this will create the $config variable in the calling package. 
     no strict 'refs'; 
     ${$call_pkg . '::config'} = $config; 
    }; 
    return; 
} 

package MyApp; 
# will execute Foo_Config->import('/path/to/site.config') at compile time. 
use Foo_Config '/path/to/site.config'; 
+0

+1, su solución aborda la queja del OP después de la actualización. –

Cuestiones relacionadas