webentwicklung-frage-antwort-db.com.de

Benötigen Sie eine Array-ähnliche Struktur in PHP mit minimalem Speicherbedarf

In meinem PHP Skript muss ich ein Array von> 600k-Ganzzahlen erstellen. Leider ist mein Webserver memory_limit auf 32M gesetzt. Beim Initialisieren des Arrays bricht das Skript mit der Nachricht ab 

Schwerwiegender Fehler: Zulässige Speichergröße von 33554432 Byte erschöpft (versucht, 71 Bytes zuzuordnen) in /home/www/myaccount/html/mem_test.php in Zeile 8

Ich bin mir der Tatsache bewusst, dass PHP die Array-Werte nicht als reine Ganzzahlen speichert, sondern als Z-Werte, die viel größer sind als der normale Ganzzahlwert (8 Byte auf meinem 64-Bit-System). Ich habe ein kleines Skript geschrieben, um zu schätzen, wie viel Speicher jeder Array-Eintrag belegt, und es stellt sich heraus, dass es ziemlich genau 128 Bytes sind. 128 !!! Ich brauche> 73M, um das Array zu speichern. Leider ist der Webserver nicht unter meiner Kontrolle, daher kann ich den memory_limit nicht erhöhen.

Meine Frage ist, ob es in PHP eine Möglichkeit gibt, eine Array-ähnliche Struktur zu erstellen, die weniger Speicher benötigt. Diese Struktur muss nicht assoziativ sein (einfacher Indexzugriff ist ausreichend). Es braucht auch keine dynamische Größenänderung - ich weiß genau, wie groß das Array sein wird. Außerdem sind alle Elemente vom selben Typ. Genau wie ein gutes altes C-Array.


Edit: Die Lösung von deceze arbeitet standardmäßig mit 32-Bit-Ganzzahlen. Selbst wenn Sie sich auf einem 64-Bit-System befinden, scheint pack () keine 64-Bit-Ganzzahlen zu unterstützen. Um 64-Bit-Ganzzahlen in meinem Array zu verwenden, habe ich einige Bit-Manipulationen vorgenommen. Vielleicht helfen die folgenden Ausschnitte für jemanden:

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))));
}

Am meisten Speichereffizienz erhalten Sie wahrscheinlich, indem Sie alles in einem String speichern, in Binärdateien verpacken und manuell indexieren.

$storage = '';

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

// ...

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

Dies ist möglich, wenn die "Array" -Initialisierung auf einen Schlag ausgeführt werden kann und Sie nur aus der Struktur lesen. Wenn Sie sehr viel an die Zeichenkette anhängen müssen, wird dies äußerst ineffizient. Auch dies kann mithilfe eines Ressourcen-Handles durchgeführt werden:

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

Das ist sehr effizient. Sie können diesen Puffer dann in eine Variable zurücklesen und als Zeichenfolge verwenden, oder Sie können mit der Ressource und fseek weiterarbeiten.

59
deceze

Ein PHP Judy Array benötigt wesentlich weniger Speicher als ein Standard PHP - Array und ein SplFixedArray.

Ich zitiere: "Ein Array mit 1 Million Einträgen unter Verwendung einer regulären PHP - Array-Datenstruktur benötigt 200 MB. SplFixedArray verwendet etwa 90 Megabyte. Judy verwendet 8 Megabyte. Kompromisse bei der Leistung, Judy benötigt etwa die doppelte Zeit der regulären PHP-Array-Implementierung. "

28
Ryan

Sie können versuchen, ein SplFixedArray zu verwenden, es ist schneller und benötigt weniger Speicher (der Dokumentenkommentar sagt ~ 30% weniger aus). Testen Sie hier und hier .

11
Dysosmus

Sie können ein Objekt verwenden, wenn möglich. Diese benötigen häufig weniger Speicher als Array . Auch SplFixedArray ist eine gute Option.

Aber es hängt wirklich von der Implementierung ab, die Sie durchführen müssen. Wenn Sie eine Funktion benötigen, um ein Array zurückzugeben, verwenden Sie PHP 5.5. Sie können den Generator Yield verwenden, um das Array wieder zu streamen.

11
RJD22

Verwenden Sie einen String - das würde ich tun. Speichern Sie es in einer Zeichenfolge auf festen Offsets (16 oder 20 Ziffern sollten ich tun?) Und verwenden Sie substr, um das benötigte zu erhalten. Blitzschnelles Schreiben/Lesen, supereinfach und 600.000 ganze Zahlen benötigen nur ~ 12M zum Speichern.

base_convert () - wenn Sie etwas Kompakteres benötigen, aber mit minimalem Aufwand, konvertieren Sie Ihre Ganzzahlen in Basis-36 anstelle von Basis-10; In diesem Fall würde eine 14-stellige Nummer in 9 alphanumerischen Zeichen gespeichert. Sie müssen zwei 64-Bit-Teile erstellen, aber ich bin mir sicher, dass dies kein Problem ist. (Ich würde sie in 9-stellige Chunks aufteilen, bei denen die Konvertierung eine 6-stellige Version ergibt.)

pack ()/unpack () - binäres Packen ist dasselbe mit etwas mehr Effizienz. Verwenden Sie es, wenn nichts anderes funktioniert. Teilen Sie Ihre Zahlen auf, um sie an zwei 32-Bit-Teile anzupassen.

5
dkellner

600K sind viele Elemente. Wenn Sie offen für alternative Methoden sind, würde ich persönlich eine Datenbank dafür verwenden. Verwenden Sie dann die standardmäßige SQL/Nosql-Auswahlsyntax, um die Dinge herauszuholen. Vielleicht Memcache oder Redis, wenn Sie einen einfachen Host dafür haben, wie garantiadata.com. Vielleicht APC. 

4
Gavin

Je nachdem, wie Sie die Ganzzahlen generieren, können Sie möglicherweise PHP-Generatoren verwenden, vorausgesetzt, Sie durchlaufen das Array und machen etwas mit individuellen Werten.

1
Oscar M.

Ich nahm die Antwort von @deceze und wickelte sie in eine Klasse ein, die 32-Bit-Ganzzahlen verarbeiten kann. Es ist nur Anhängen, aber Sie können es immer noch als einfaches, speicheroptimiertes PHP Array, Queue oder Heap verwenden. AppendItem und ItemAt sind beide O (1) und haben keinen Speicheraufwand. Ich habe currentPosition/currentSize hinzugefügt, um unnötige Fseek-Funktionsaufrufe zu vermeiden. Wenn Sie die Speichernutzung begrenzen und automatisch zu einer temporären Datei wechseln möchten, verwenden Sie stattdessen php: // temp .

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