私のPHPスクリプトでは、> 600k整数の配列を作成する必要があります。残念ながら、私のWebサーバーmemory_limit
は32Mに設定されているため、配列を初期化すると、スクリプトはメッセージで中止されます
致命的エラー:/ home/www /で33554432バイトの許容メモリサイズを使い果たしました(71バイトを割り当てようとしました) myaccount/html/mem_test.phpの行8
PHPは配列の値をプレーンな整数として保存するのではなく、プレーンな整数値(64ビットシステムでは8バイト)よりもはるかに大きいzvalueとして保存するという事実を私は知っています。 )各配列エントリが使用するメモリの量を見積もる小さなスクリプトを書いたところ、128バイトであることが判明しました。128!!!配列を格納するだけで73Mを超える必要があります。残念ながら、Webサーバーは私のコントロールなので、memory_limit
。
私の質問は、PHPでメモリ使用量が少ない配列のような構造を作成する可能性があるかどうかです。この構造を連想させる必要はありません(単純なインデックスアクセスで十分です)。また、動的なサイズ変更も必要ありません-配列の大きさは正確にわかっています。また、すべての要素は同じ型になります。古き良きC配列と同じです。
編集:したがって、 deceze のソリューションは、32ビット整数を使用すると、すぐに機能します。しかし、64ビットシステムを使用している場合でも、 pack() は64ビット整数をサポートしていないようです。私の配列で64ビット整数を使用するために、ビット操作を適用しました。おそらく、以下のスニペットは誰かのために役立つでしょう:
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))));
}
あなたが得る最もメモリ効率の良いものは、おそらくすべてをバイナリにパックされた文字列に格納し、それに手動でインデックスを付けることです。
$storage = '';
$storage .= pack('l', 42);
// ...
// get 10th entry
$int = current(unpack('l', substr($storage, 9 * 4, 4)));
これは、 "配列"の初期化を一気に行うことができ、構造から読み取っている場合にのみ可能です。文字列に多くの追加が必要な場合、これは非常に非効率になります。ただし、これはリソースハンドルを使用して行うこともできます。
$storage = fopen('php://memory', 'r+');
fwrite($storage, pack('l', 42));
...
これは非常に効率的です。その後、このバッファを変数に読み込んで文字列として使用するか、リソースとfseek
を引き続き使用できます。
PHP Judy Array は、標準のPHP配列、およびSplFixedArray配列よりも大幅に少ないメモリを使用します。
「通常のPHP配列データ構造を使用した100万エントリの配列は200MBかかります。SplFixedArrayは約90メガバイトを使用します。Judyは8MBを使用します。トレードオフはパフォーマンスにあり、Judyは通常のphp配列の実装。」
SplFixedArray を使用することを試みることができます、それはより速く、より少ないメモリを必要とします(ドキュメントのコメントは〜30%少ないと言います)。テスト ここ および ここ 。
可能であればオブジェクトを使用できます。これらは多くの場合、アレイよりも少ないメモリを使用します。また、 SplFixedArray は適切なオプションです。
しかし、それは本当にあなたがする必要がある実装に依存します。配列を返す関数が必要で、PHP 5.5を使用している場合。 generator yield を使用して、配列をストリーミングして戻すことができます。
文字列を使用-それが私がすることです。それを固定オフセットの文字列に格納し(16桁または20桁でそれを行う必要があると思いますか?)、substrを使用して必要なものを取得します。非常に高速な書き込み/読み取り、超簡単、および600.000の整数の格納には、約12Mしかかかりません。
base_convert()-よりコンパクトなものが必要な場合、最小限の労力で、整数をbase-10ではなくbase-36に変換します。この場合、14桁の数字は9文字の英数字で格納されます。 64ビットの整数を2つ作成する必要がありますが、問題はないと思います。 (私はそれらを9桁のチャンクに分割して、変換によって6文字バージョンを提供します。)
pack()/ unpack()-バイナリパッキングは同じことで、もう少し効率的です。他に何も機能しない場合に使用します。数値を分割して、2つの32ビット部分に収まるようにします。
600Kは多くの要素です。別の方法を受け入れるのであれば、私は個人的にはデータベースを使用します。次に、標準のsql/nosql select構文を使用して、物事を引き出します。 garantiadata.comなどの簡単なホストがある場合は、おそらくmemcacheまたはredisです。多分APC。
整数の生成方法によっては、配列をトラバースし、個々の値で何かを行うと想定して、 PHPのジェネレーター を使用できます。
@decezeで答えを受け取り、32ビット整数を処理できるクラスでラップしました。追加のみですが、メモリ最適化された単純なPHP配列、キュー、またはヒープとして使用できます。AppendItemとItemAtはどちらもO(1)であり、メモリのオーバーヘッド。不必要なfseek関数呼び出しを回避するためにcurrentPosition/currentSizeを追加しました。メモリ使用量を制限して一時ファイルに自動的に切り替える必要がある場合は、代わりに 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");
}