Need an array-like structure in PHP with minimal memory usage

后端 未结 8 2170
星月不相逢
星月不相逢 2020-12-23 02:02

In my PHP script I need to create an array of >600k integers. Unfortunately my webservers memory_limit is set to 32M so when initializing the array the script a

相关标签:
8条回答
  • 2020-12-23 02:43

    600K is a lot of elements. If you are open to alternative methods, I personally would use a database for that. Then use standard sql/nosql select syntax to pull things out. Perhaps memcache or redis if you have an easy host for that, such as garantiadata.com. Maybe APC.

    0 讨论(0)
  • 2020-12-23 02:50

    Depending on how you are generate the integers, you could potentially use PHP's generators, assuming you are traversing the array and doing something with individual values.

    0 讨论(0)
  • 2020-12-23 02:55

    I took the answer by @deceze and wrapped it in a class that can handle 32-bit integers. It is append-only, but you can still use it as a simple, memory-optimized PHP Array, Queue, or Heap. AppendItem and ItemAt are both O(1), and it has no memory overhead. I added currentPosition/currentSize to avoid unnecessary fseek function calls. If you need to cap memory usage and switch to a temporary file automatically, use php://temp instead.

    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 讨论(0)
  • 2020-12-23 02:56

    The most memory efficient you'll get is probably by storing everything in a string, packed in binary, and use manual indexing to it.

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

    This can be feasible if the "array" initialisation can be done in one fell swoop and you're just reading from the structure. If you need a lot of appending to the string, this becomes extremely inefficient. Even this can be done using a resource handle though:

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

    This is very efficient. You can then read this buffer back into a variable and use it as string, or you can continue to work with the resource and fseek.

    0 讨论(0)
  • 2020-12-23 02:56

    Use a string - that's what I'd do. Store it in a string on fixed offsets (16 or 20 digits should do it I guess?) and use substr to get the one needed. Blazing fast write / read, super easy, and 600.000 integers will only take ~12M to store.

    base_convert() - if you need something more compact but with minimum effort, convert your integers to base-36 instead of base-10; in this case, a 14-digit number would be stored in 9 alphanumeric characters. You'll need to make 2 pieces of 64-bit ints, but I'm sure that's not a problem. (I'd split them to 9-digit chunks where conversion gives you a 6-char version.)

    pack()/unpack() - binary packing is the same thing with a bit more efficiency. Use it if nothing else works; split your numbers to make them fit to two 32-bit pieces.

    0 讨论(0)
  • 2020-12-23 02:57

    You could use an object if possible. These often use less memory than array's. Also SplFixedArray is an good option.

    But it really depends on the implementation that you need to do. If you need an function to return an array and are using PHP 5.5. You could use the generator yield to stream the array back.

    0 讨论(0)
提交回复
热议问题