web-dev-qa-db-fra.com

Besoin d'une structure de type tableau dans PHP avec une utilisation minimale de la mémoire

Dans mon script PHP, je dois créer un tableau de> 600k entiers. Malheureusement, mon serveur Web memory_limit a la valeur 32M. Par conséquent, lors de l’initialisation du tableau, le script abandonne avec message. 

Erreur fatale: Taille de mémoire autorisée de 33554432 octets épuisés (tentative d'allocation de 71 octets) dans /home/www/myaccount/html/mem_test.php à la ligne 8

Je suis conscient du fait que PHP ne stocke pas les valeurs du tableau sous forme d'entiers simples, mais plutôt sous la forme de valeurs z qui sont beaucoup plus grandes que la valeur entière (8 octets sur mon système 64 bits). J'ai écrit un petit script pour estimer la quantité de mémoire utilisée par chaque entrée de tableau. Il s'avère que c'est exactement 128 octets. 128 !!! J'aurais besoin de> 73M juste pour stocker le tableau. Malheureusement, le serveur Web n'est pas sous mon contrôle, je ne peux donc pas augmenter le memory_limit.

Ma question est la suivante: existe-t-il une possibilité dans PHP de créer une structure semblable à un tableau qui utilise moins de mémoire. Je n'ai pas besoin que cette structure soit associative (un accès d'index complet est suffisant). Il n’a pas non plus besoin d’un redimensionnement dynamique - je sais exactement quelle sera la taille du tableau. En outre, tous les éléments seraient du même type. Juste comme un bon vieux C-array.


Edit: La solution de deceze fonctionne donc avec des entiers 32 bits. Mais même si vous utilisez un système 64 bits, pack () ne semble pas prendre en charge les entiers 64 bits. Afin d'utiliser des entiers 64 bits dans mon tableau, j'ai appliqué une manipulation de bits. Peut-être que les extraits ci-dessous seront utiles à quelqu'un:

function Push_back(&$storage, $value)
{
    // split the 64-bit value into two 32-bit chunks, then pass these to pack().
    $storage .= pack('ll', ($value>>32), $value);
}

function get(&$storage, $idx)
{
    // read two 32-bit chunks from $storage and glue them back together.
    return (current(unpack('l', substr($storage, $idx * 8, 4)))<<32 |
            current(unpack('l', substr($storage, $idx * 8+4, 4))));
}

La solution la plus efficace en termes de mémoire consiste probablement à stocker tout dans une chaîne, compressée au format binaire, et à l'indexer manuellement.

$storage = '';

$storage .= pack('l', 42);

// ...

// get 10th entry
$int = current(unpack('l', substr($storage, 9 * 4, 4)));

Cela peut être faisable si l'initialisation "array" peut être faite d'un seul coup et que vous ne lisez que la structure. Si vous avez besoin de beaucoup d’ajout à la chaîne, cela devient extrêmement inefficace. Même cela peut être fait en utilisant un descripteur de ressource si:

$storage = fopen('php://memory', 'r+');
fwrite($storage, pack('l', 42));
...

C'est très efficace. Vous pouvez ensuite lire ce tampon dans une variable et l'utiliser en tant que chaîne, ou continuer à travailler avec la ressource et fseek.

59
deceze

Un PHP Judy Array utilisera beaucoup moins de mémoire qu'un tableau PHP standard et un SplFixedArray.

Je cite "Un tableau avec 1 million d'entrées utilisant une structure de données de tableau PHP standard prend 200 Mo. SplFixedArray utilise environ 90 mégaoctets. Judy utilise 8 Mo. Le compromis est en cours d'exécution, Judy prend environ le double du temps d'implémentation régulière d'un tableau PHP. "

28
Ryan

Vous pouvez essayer d’utiliser un SplFixedArray , c’est plus rapide et prend moins de mémoire (le commentaire doc dit environ 30% moins) Testez ici et ici .

11
Dysosmus

Vous pouvez utiliser un objet si possible. Ceux-ci utilisent souvent moins de mémoire que le tableau . De plus, SplFixedArray est une bonne option.

Mais cela dépend vraiment de l'implémentation que vous devez faire. Si vous avez besoin d’une fonction pour retourner un tableau et utilisez PHP 5.5. Vous pouvez utiliser le generator yield pour rediffuser le tableau.

11
RJD22

Utilisez une chaîne - c'est ce que je ferais. Stockez-le dans une chaîne sur des décalages fixes (16 ou 20 chiffres devraient le faire, je suppose?) Et utilisez substr pour obtenir celui qui vous convient. Très rapide en écriture/lecture, super facile, et 600.000 entiers ne prendront que ~ 12M à stocker.

base_convert () - si vous avez besoin de quelque chose de plus compact mais avec un minimum d’effort, convertissez vos entiers en base-36 au lieu de base-10; dans ce cas, un numéro à 14 chiffres serait stocké dans 9 caractères alphanumériques. Vous aurez besoin de faire 2 morceaux de 64 bits, mais je suis sûr que ce n'est pas un problème. (Je les scindais en morceaux de 9 chiffres, la conversion vous donnant une version à 6 caractères.)

pack ()/unpack () - La compression binaire est la même chose avec un peu plus d'efficacité. Utilisez-le si rien d'autre ne fonctionne; divisez vos nombres pour les adapter à deux morceaux de 32 bits.

5
dkellner

600K, c'est beaucoup d'éléments. Si vous êtes ouvert aux méthodes alternatives, j'utiliserais personnellement une base de données pour cela. Utilisez ensuite la syntaxe standard sql/nosql select pour extraire des éléments. Peut-être memcache ou redis si vous avez un hôte facile à utiliser, tel que garantiadata.com. Peut-être APC. 

4
Gavin

Selon la manière dont vous générez les entiers, vous pouvez éventuellement utiliser les générateurs de PHP , en supposant que vous parcourez le tableau et que vous fassiez quelque chose avec des valeurs individuelles.

1
Oscar M.

J'ai pris la réponse par @deceze et l'ai enveloppée dans une classe qui peut gérer des entiers 32 bits. Il est uniquement ajouté, mais vous pouvez toujours l'utiliser comme simple, optimisé pour la mémoire PHP Array, Queue ou Heap. AppendItem et ItemAt sont tous deux O (1) et ne surchargent pas de mémoire. J'ai ajouté currentPosition/currentSize pour éviter les appels de fonction fseek inutiles. Si vous devez limiter l'utilisation de la mémoire et basculer automatiquement vers un fichier temporaire, utilisez php: // temp à la place.

class MemoryOptimizedArray
{
    private $_storage;
    private $_currentPosition;
    private $_currentSize;
    const BYTES_PER_ENTRY = 4;
    function __construct()
    {
        $this->_storage = fopen('php://memory', 'rw+');
        $this->_currentPosition = 0;
        $this->_currentSize = 0;
    }
    function __destruct()
    {
        fclose($this->_storage);
    }
    function AppendItem($value)
    {
        if($this->_currentPosition != $this->_currentSize)
        {
            fseek($this->_storage, SEEK_END);
        }
        fwrite($this->_storage, pack('l', $value));
        $this->_currentSize += self::BYTES_PER_ENTRY;
        $this->_currentPosition = $this->_currentSize;
    }
    function ItemAt($index)
    {
        $itemPosition = $index * self::BYTES_PER_ENTRY;
        if($this->_currentPosition != $itemPosition)
        {
            fseek($this->_storage, $itemPosition);
        }
        $binaryData = fread($this->_storage, self::BYTES_PER_ENTRY);
        $this->_currentPosition = $itemPosition + self::BYTES_PER_ENTRY;
        $unpackedElements = unpack('l', $binaryData);
        return $unpackedElements[1];
    }
}

$arr = new MemoryOptimizedArray();
for($i = 0; $i < 3; $i++)
{
    $v = Rand(-2000000000,2000000000);
    $arr->AddToEnd($v);
    print("added $v\n");
}
for($i = 0; $i < 3; $i++)
{
    print($arr->ItemAt($i)."\n");
}
for($i = 2; $i >=0; $i--)
{
    print($arr->ItemAt($i)."\n");
}
0
humbads