Accediendo a Big Arrays en PHP

He estado haciendo algunos perfiles en diferentes métodos para acceder a grandes conjuntos de datos en PHP. El caso de uso es bastante simple: algunas de nuestras herramientas generan datos en archivos PHP como matrices asociativas y la aplicación considera estos archivos como datos estáticos. Creamos juegos, por lo que algunos ejemplos de archivos de datos incluirían elementos en un catálogo, tareas que un usuario debe completar o definiciones para mapas:

<?php
$some_data = array(
    ...lots and lots of stuff in here...
);
?>

Dado que estas matrices son grandes (400K) y gran parte de nuestro código está interesado en estos datos, se hace necesario acceder a estos datos de la manera más eficiente posible. Me decidí por cronometrar 3 patrones diferentes para hacer esto. Después de presentar los métodos, compartiré mis resultados a continuación.

Lo que estoy buscando es una validación basada en la experiencia de estos métodos y su tiempo, así como cualquier otro método para probar.

Método #1: función getter

En el método, el exportador en realidad crea un archivo que se parece a:

<?php
function getSomeData()
{
    $some_data = array(
        ...lots and lots of stuff here...
    );
    return $some_data;
}
?>

El código del cliente puede obtener los datos simplemente llamando a getSomeData() cuando lo desee.

Método #2: global + incluir

En este método, el archivo de datos se ve idéntico al bloque de código original anterior, sin embargo, el código del cliente debe pasar por algunos obstáculos para obtener los datos en un ámbito local. Esto supone que la matriz está en un archivo llamado 'some_data.php';

global $some_data; //must be the same name as the variable in the data file...
include 'some_data.php';

Esto traerá la matriz $some_data al alcance, aunque es un poco engorroso para el código del cliente (mi opinión).

Método #3: getter por referencia

Este método es casi idéntico al Método #1, sin embargo, la función getter no devuelve un valor sino que establece una referencia a los datos.

<?php
function getSomeDataByRef($some_data)
{
    $some_data = array(
        ...lots and lots of stuff here...
    );
    return $some_data;
}
?>

Luego, el código del cliente recupera los datos declarando una variable local (llamada cualquier cosa) y pasándola por referencia al captador:

$some_data_anyname = array();
getSomeDataByRef(&$some_data_anyname);

Resultados

Así que ejecuté un pequeño script que ejecuta cada uno de estos métodos de recuperación de datos 1000 veces y promedia el tiempo de ejecución (calculado por microtiempo (verdadero) al principio y al final). Los siguientes son mis resultados (en ms, ejecutándose en un MacBookPro de 2 GHz, 8 GB de RAM, PHP versión 5.3.4):

MÉTODO 1:

PROMEDIO: 0.0031637034416199 MÁX: 0.0043289661407471 MÍN: 0.0025908946990967

MÉTODO 2:

PROMEDIO: 0.01434082698822 MÁX: 0.018275022506714 MÍN: 0.012722969055176

MÉTODO 3:

PROMEDIO: 0.00335768699646 MÁX: 0.0043489933013916 MÍN: 0.0029017925262451

Parece bastante claro, a partir de estos datos de todos modos, que el método global+include es inferior a los otros dos, que son una diferencia "insignificante".

¿Pensamientos? ¿Me estoy perdiendo algo por completo? (probablemente...)

Gracias de antemano!

preguntado el 03 de mayo de 12 a las 16:05

En primer lugar, muy buena pregunta para ser la primera. Me alegra ver que alguien lee esas preguntas frecuentes. En segundo lugar, creo que sería mejor para usted usar una estructura de datos y objetos. es decir, un Task clase para describir tareas, Level para describir niveles (y tal vez sostener Task ¿objetos?), Player para describir jugadores, etc. Es mejor que una matriz asociativa en mi humilde opinión. -

¿Podría explicar por qué está utilizando archivos PHP como almacén de datos? ¿Por qué no almacenar los datos en un RDBMS (o equivalente) y acceder a ellos desde allí? Tal vez no entendí bien, pero parece que está generando archivos PHP para almacenar datos. -

Su referencia es al revés. El & debe estar en la declaración de la función, el paso por referencia de tiempo de llamada está en desuso, es decir, debe ser function getSomeDataByRef(&$some_data) y getSomeDataByRef($some_data_anyname);. También tenga en cuenta que declarar previamente la variable pasada como una matriz no es necesario. include siempre será menos eficiente, debido al hecho de que tiene que iniciar el analizador de nuevo. Sospecho que la razón por la que los demás son una diferencia insignificante es porque la primera versión aún se implementará como copia en escritura y no requiere una copia de memoria para el valor de retorno. -

Devuelve la matriz en lugar de asignarla a $some_data y solo usa $some_data = include('some_data') -

2 Respuestas

No estoy seguro de si esto es exactamente lo que estás buscando, pero debería ayudarte con los problemas de velocidad y memoria. Puede usar la matriz spl fija:

$startMemory = memory_get_usage();
$array = new SplFixedArray(100000);
for ($i = 0; $i < 100000; ++$i) {
    $array[$i] = $i;
}
echo memory_get_usage() - $startMemory, ' bytes';

Lea más sobre grandes arreglos php aquí: http://nikic.github.com/2011/12/12/How-big-are-PHP-arrays-really-Hint-BIG.html

¿También has pensado en almacenar los datos en un caché/memoria? Por ejemplo, podría usar mysqlite con el motor inmemory en la primera ejecución y luego acceder a los datos desde allí:

$pdo = new PDO('sqlite::memory:');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// .. Use PDO as normal

contestado el 03 de mayo de 12 a las 17:05

Hemos tomado la decisión de utilizar matrices asociativas en lugar de objetos por motivos de rendimiento. Entiendo que esas razones pueden o no ser desacreditadas con PHP5, pero es poco probable que esa decisión cambie. RDBMS no se usa por razones de rendimiento similares: establecer una conexión y ejecutar una consulta es simplemente lento para nosotros en la práctica. - se queda

Guardamos en caché los datos hasta cierto punto y supongo que no estaba claro al respecto... es decir, solo 'obtendremos' los datos una vez para una clase/servicio web determinado en su constructor. Lo que me interesa es la forma más rápida de obtener esos datos en el alcance. Gracias. - se queda

Si bien normalmente tiendo a favorecer las matrices para cosas más pequeñas, como la configuración, la importación por lotes y demás, debe envolver el acceso a los datos en algún objeto LazyArray o StreamArray que nunca tenga más de un tamaño de mandril fijo en la memoria a la vez. Si implementa un iterador, aún podría realizar búsquedas globales o tener acceso con clave a todo, es posible que primero tenga que iterar varias veces, o simplemente implementar como un CallbackFilterIterator - Christoffer Bubach

Para uno de mis proyectos en los que una base de datos no era una opción, me enfrenté al mismo problema de cargar archivos php grandes (por grandes me refiero a series de archivos de 3 MB) que contenían matrices en la memoria y estaba buscando opciones para maximizar el rendimiento. Encontré uno muy fácil que estaba almacenando en caché estos archivos en el disco como json en el primer uso. Dividí el tiempo de carga por 3 y también el consumo máximo de memoria por 30%. Cargar un archivo json local con json_decode() es mucho más rápido que incluir un archivo php grande que contiene una matriz. también tiene la ventaja de ser un formato que la mayoría de los lenguajes pueden manipular directamente. Espero que ayude.

Respondido el 27 de junio de 14 a las 16:06

Buena solución. También en mis pruebas, analizar el archivo json y obtenerlo como matriz es aproximadamente un 10% más rápido que obtenerlo como objeto. - Hokusai

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.