Relatos tragicómicos de un moderador de foro de programación – Capítulo 0×01
¿Para qué escribir algo de cero si se puede copiar y pegar algo ya escrito? Una de las máximas de la “programación” aplicada a esto del “blaaahg”.
Moderar un foro de programación no es fácil, puedo decirlo por experiencia, pero tampoco es agotador. Los programadores somos gente difícil y poco tolerante hacia los “users”, o sea, esa gente que usa la tecnología sin molestarse lo suficiente en comprenderla o por lo menos respetar su complejidad (y dejar a los que se dedican a ello preocuparse por los problemas mayores).
En fin, aquí va un pequeño relato de lo que acontece en los oscuros pasillos de internet, donde se trama la conquista del ciberespacio, un bit a la vez.
Mitos de la programación… PHP: require vs require_once
Se que más adelante voy a lamentar andar mirando el twitter de phpsenior en lugar de dedicarme a adelantar trabajo atrasado, pero bueh… ya está hecho el daño. Ví este artículo sobre varias curiosidades de PHP y me detuve un segundo sobre uno en particular que me llamó la atención. En uno de los puntos asegura que require es más rápido que require_once… esto es mentira, un mito del mundo PHP… incluso, por alguna razón que desconozco, en las últimas versiones de PHP, require_once funciona de manera más óptima que require. Deben haber estado tweakeandolo, ya que se recomienda el uso de require_once sobre require (para cargar includes que solo deberían cargarse una vez).
De todas maneras, nunca hubo una ventaja en velocidad de procesamiento por usar un keyword u el otro, (salvo recientemente que parece que se optimizo uno y el otro no). Explico porque. Tuve la oportunidad de andar investigando como funciona internamente el zend engine hace un año cuando había mucho revuelo por el tema de los namespaces en php, e inocentemente pense que andar tocando el engine no podía ser tan difícil, solo hacía falta sacarle el polvo a mis habilidades en C… En fín, esa es otra historia.
En teoría no debería haber cambiado mucho la lógica que distingue a require de require_once desde la última vez que la ví. Básicamente, se utiliza una hash table (de las mismas que se usan para los arrays) para recordar que archivos fueron cargados y cargarlos de vuelta cuando se usa require_once. Lo que sucede es que ambos hacen la busqueda del bucket en la hash table, porque tanto require como require_once tienen que anotar que el archivo fue incluido. La busqueda en la hash table es el trabajo pesado ahí, después fallar si se encontro el archivo, no hace diferencia a la performance. En definitiva la diferencia entre require y require_once es minima, no hay ninguna “llamada extra al sistema”, eso es un mito del mundo php.
Ahora, hay muchas “resultados estadisticos” generados por entusiastas del tema que miden el tiempo de ejecución y comparan resultados. Hay algunos que le dan la razón a la versión mito y otros que muestran lo contrario. El problema con estos resultados, como cualquier medición de este estilo, es que hay que tener cuidado en observar bien que es lo que realmente se está midiendo.
Con require y require_once hay un problema en los resultados de la medición: el acceso a disco. Estas operaciones estan sujetas a la disponibilidad del sistema operativo (recordemos que el tiempo de procesador es compartido en sistemas multitarea), y en comparación con los tiempos reales que tarda lo que realmente queremos medir, el tiempo ocupado por el IO es mucho mayor. O sea, los resultados obtenidos son poco estables y poco confiables… pero podríamos salvarlo si evitaramos el acceso a disco.
Para esto algunos a recurrido a los mismos sistemas que se usan en producción para evitar el acceso a disco (APC, memcache, etc), pero estos también son metodos que están sujetos a los caprichos del sistema operativo. Lo mejor sería evitar completamente el acceso a disco… Y hay una manera.
Otro de los features poco usados de PHP son los stream wrappers. Esto te permite crear tipos de “streams”, objetos utilizados en operaciones de IO. En el manual de php hay un ejemplo de stream wrapper que implementa streams para variables globales, y lo interesante es que podemos usarlos con require y require_once. Use este ejemplo del manual de php para hacer un profile de require y require_once que no considere el acceso a disco:
function mean($data) {
sort($data);
$error = ceil(count($data) * 0.03);
$data = array_slice($data, $error, -$error);
return array_sum($data) / count($data);
}
$n = 1000;
for ($i = 0; $i < $n; $i++) {
$GLOBALS["var$i"] = '';
}
$times = array();
for ($i = 0; $i < $n; $i++) {
$file = "var://var$i";
$start = microtime(true);
require $file;
$end = microtime(true);
$times[] = $end - $start;
}
printf("%f\n", mean($times));
Al correr el test con estos valores los resultados se muestran muy estables. Estos son (en mi computadora):
require: 0.000037 segundos
require_once: 0.000023 segundos
Hacer la prueba para include e include_once me reporta los mismos resultados, como era de esperarse (ya que tampoco hay una diferencia de importancia entre ambos… salvo cuando ocurre un error). Lo que me resulto extraño es encontrar un diferencia a favor de require_once… seguramente deben haber estado realizando mejoras que no se aplicaron por igual… vaya uno a saber… el zend engine es spaghetti code.