nerou/large-array-buffer
Composer 安装命令:
composer require nerou/large-array-buffer
包简介
Handle arrays with huge cardinality but small items with ease
README 文档
README
This is for you, if...
...you are working with pretty large arrays that conflict with close to every memory limit you can set. The array elements on the other hand should not be that big, such that you can keep a single element in memory quite easily. If the array gets too big for memory, it will be moved to disk temporarily and transparently. You can still iterate over it as if it was a normal array.
One typical use case would be to load a lot of datasets from a database at once. (There are reasons to prefer this over running multiple queries.) See Usage below for an example for this use case using this library.
In general, you might want to try the following PHP-builtin solutions first:
- SplFixedArray: By being of fixed length, this saves some memory compared to traditional
arrays. It behaves pretty much likearrays do. Although the savings are not that huge compared to other solutions, it is probably the easiest to adopt. - Generator: Instead of returning an
arrayall at once, this allows to return one item after the other. Thereby a "consumer" is able to process one item after the other accordingly. One can also terminate the "generation" of further items. One of the downsides is, that a Generator can only be iterated over once. The memory consumption can be as good as constant, but this approach is quite different and therefore rather hard to adopt and not appropriate in every scenario.
It should be noted, that a LargeArrayBuffer can be converted to both of these using toFixedArray() and toGenerator() respectively.
Install
Note: This library requires PHP 8.0+!
Use composer to install this library:
composer require nerou/large-array-buffer
There are pretty much no dependencies with some exceptions:
- If you want to use the
toJSONFile()method, you need to installext-json(PHP's PECL JSON extension) as well. - If you want to use the igbinary serializer,
ext-igbinaryis required. See php-ext-igbinary. - If you want to use the msgpack serializer,
ext-msgpackis required. See php-ext-msgpack. - If you want to use LZ4 compression,
ext-lz4is required. See php-ext-lz4.
Usage
$pdo = new PDO(/* your database credentials */); $stmt = $pdo->query('SELECT * FROM SomeDatabaseTable', PDO::FETCH_ASSOC); $buffer = new LargeArrayBuffer(); // explicit use of LargeArrayBuffer $buffer = new ArrayBuffer(1000); // wrapper using `array` until given threshold (item count) is reached, // then switching to LargeArrayBuffer while(($dataset = $stmt->fetch()) !== false){ // load one dataset at a time $buffer->push($dataset); // push this dataset to the buffer } // ... foreach($buffer as $item){ // work with your data here }
Options
The constructor of LargeArrayBuffer provides some options:
- You can set the threshold when to move the data to disk. When pushing data to the buffer, it is stored in memory until it gets too large.
E.g.:
new LargeArrayBuffer(512);to set a 512 MiB threshold. - You can choose either the PHP serializer, the igbinary serializer or the msgpack serializer (PHP serializer is default).
E.g.:
new LargeArrayBuffer(serializer: LargeArrayBuffer::SERIALIZER_IGBINARY); - You can enable GZIP or LZ4 compression for the serialized items. Although this is recommended only if your items are pretty big like > 1 KiB each. E.g.:
new LargeArrayBuffer(compression: LargeArrayBuffer::COMPRESSION_GZIP);. Note, that LZ4 compression requires ext-lz4 to be loaded.
Read from the buffer
There are several options to read the data:
- Iterate: As you might have seen in the example above, you can iterate over the buffer just like over an array with
foreach.foreach($buffer as $item){ /* work with your data here */ } toArray(): If you have a set of buffers which do not fit in memory all together but one at a time, you can use$buffer->toArray()to get an array to work with.toJSONFile(): If you want to write the array in JSON format to some file orresource, use this method. It supports all thejson_encode()options and flags.
Buffer stats
There are some stats you can obtain:
count(): The number of items in the buffer.getSize(): The number of bytes of the serialized (!) data.
How it works
To put it in one sentence: This library uses php://temp as well as PHP's serialize/unserialize functions to store an array on disk if it gets too large.
Limitations and concerns
- associative arrays are not supported
- the item type needs to be compatible with PHP's serialize/unserialize functions
- since storage drives (even PCIe SSDs) are a lot slower than memory and de-/serialization needs to be done, you trade hard memory overflows for performance losses
Benchmark
A benchmark with 1 million measurements (consisting of DateTimeImmutable, int and float) using PHP 8.1 with 10 iterations comparing a normal array with the LargeArrayBuffer gave the following results (LargeArrayBuffer was configured with a memory limit of 128 MiB):
| Action | Serializer | Compression | Consumed time | Consumed memory | Buffer size |
|---|---|---|---|---|---|
| Fill array | none | none | 1.57 s | 490.0 MiB | NA |
| Iterate over array | none | none | 0.27 s | 492.0 MiB | NA |
| Fill buffer | PHP | none | 4.27 s | 0 B | 378.7 MiB |
| Iterate over buffer | PHP | none | 2.85 s | 0 B | 378.7 MiB |
| Fill buffer | PHP | GZIP | 24.76 s | 0 B | 192.5 MiB |
| Iterate over buffer | PHP | GZIP | 6.79 s | 0 B | 192.5 MiB |
| Fill buffer | PHP | LZ4 | 4.89 s | 0 B | 241.0 MiB |
| Iterate over buffer | PHP | LZ4 | 2.93 s | 0 B | 241.0 MiB |
| Fill buffer | igbinary | none | 4.26 s | 0 B | 319.1 MiB |
| Iterate over buffer | igbinary | none | 3.41 s | 0 B | 319.1 MiB |
| Fill buffer | igbinary | GZIP | 21.50 s | 0 B | 173.2 MiB |
| Iterate over buffer | igbinary | GZIP | 4.80 s | 0 B | 173.2 MiB |
| Fill buffer | igbinary | LZ4 | 4.38 s | 0 B | 195.1 MiB |
| Iterate over buffer | igbinary | LZ4 | 3.17 s | 0 B | 195.1 MiB |
Note:
- The peak memory usage using the buffer is about its memory limit. The table shows the memory usage after the specified action.
- PHP seems to cache the array once it is created for the first time, although
unsetis used. That is why I have not put the average value in the table for this specific value but the maximum (first run). - The serialized data is smaller than the binary data in memory. I have absolutly no idea why.
To reproduce call bench/benchmark.php.
License
This library is licensed under the MIT License. Please see LICENSE for more information.
nerou/large-array-buffer 适用场景与选型建议
nerou/large-array-buffer 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 2.26k 次下载、GitHub Stars 达 1, 最近一次更新时间为 2023 年 11 月 20 日, 在 PHP 生态内属于活跃度较高的组件。
我们在过去多个企业项目中使用过 nerou/large-array-buffer 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 nerou/large-array-buffer 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
统计信息
- 总下载量: 2.26k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 1
- 点击次数: 19
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2023-11-20