带小尾巴的NPY文件格式
- 与
Numpy的NPY文件格式兼容, 可以用原生的np.load函数加载 - 支持
memmap模式, 可以跨进程一写多读。append - 支持调整文件大小
resize,但不移动数据 - 支持
tell、seek、rewind、read等操作 - 支持无限写入
NPY8
pip install npytimport numpy as np
from npyt import NPYT
arr = np.array([1, 2, 3, 4, 5, 6])
file = "tmp.npy"
# 创建文件
nt1 = NPYT(file).save(arr, capacity=10, end=0).load(mmap_mode="r+")
# 只读加载文件
nt2 = NPYT(file).load(mmap_mode="r")
nt3 = NPYT(file).load(mmap_mode="r")
nt1.append(arr)
print(nt2.data())
nt1.append(arr[0:1])
print(nt3.data())本人需要一种准实时的行情存储方式,考虑arrow或np.memmap()
arrow,列式存储,并不适合行情数据。因为要将新数据写入到不同的位置np.memmap(),内存映射,行式存储,适合行情数据。但还是有不足- 需另行维护
dtype,shape等信息 - 文件大小随数据量而增大,不能动态调整大小
- 需另行维护
所以如果初始时创建一个大文件,然后维护一个标记,用来记录数据的位置,就能实现数据增长了。
标记是放在同一文件,还是放在不同文件呢?
最开始是放在不同文件,这样代码实现方便,但是要维护两个文件。
后来发现np.load函数支持mmap_mode参数,可以直接加载内存映射文件,并且还是直接带了dtype和shape信息。为何不直接用呢? 只要把额外信息放在NPY文件的尾部就可以了。
额外信息放在NPY文件的尾部,多加个5个uint64数字。
- start: 开始位置
- end: 结束位置
- update_timestamp: 更新时间。可处理end不变,但数据变化的情况
- offset: 数据区开始位置,方便其他语言快速定位并写入
- magic: 魔术数,用来判断是否
NPYT格式文件
NPY文件头有shape信息的字符串,例如;
(20, 3)
(200, 3)
很明显,这两个字符串的长度是不一样的。而NPYT项目修改了此部分,让字符串长度一致,例如;
( 20, 3)
( 200, 3)
这样就可以直接修改数据大小而不用移动数据区。
np.load可以打开NPYT.save保存的文件。NPYT.load可以打开np.save保存的文件, 取原始数据NPYT._raw()np.save保存的文件大小不可修改。NPYT.save保存的文件,可用NPYT.resize修改np.load后dtype缺失align属性。NPYT.load后dtype还原了align属性。(numpy 2.2.5)- 当
array要传给numba.jit函数,函数中需要对array进行修改,由于align属性缺失,可能导致修改时出现数据复制,复制出来的对象是只读 - numpy/numpy#28973
- 当
最开始提供了RingBuffer模式。但存在不足
- 编写过于复杂
c++版读写都是单行数据读取,可以用%算法来实现环形numpy版。都是多行整体写入和读取,无法用%算法来实现,导致需要写大量判断语句,逻辑复杂
- 无法零拷贝
- start<end,零拷贝
- start>end,数据分成了两段,需要拷贝
所以,NPY8模式诞生了,可以7*24写入数据
它本质是创建一个文件夹和一个.lock文件,通过.lock来维护文件夹中最新的几个NPYT文件。
- 写入数据时,从
.lock文件尾,读取最新的NPYT文件,如果文件已满,创建新的NPYT文件,并写入.lock文件 .lock文件会限定文件数量,比如8个文件,多于8个文件时,文件移出队列不再维护read时,从.lock文件头开始,读取单个文件,已读完,切换下一个文件,直到.lock文件尾tail时,从.lock文件尾开始,读取单个文件,已读完,但数量不够,继续读取上一个文件,直到.lock文件头
-
NPY8.tail是跨文件的,如果能取的是单个文件,就能减少拷贝- 返回的是
np.ndarray列表,一个个按需使用比concat后使用更好 capacity_per_file设置得大一些,越大越能减少跨文件的概率(tail-1)/capacity。例如:- capacity=100,tail=100,则99%的概率跨文件
- capacity=100,tail=50,则49%的概率跨文件
- capacity=50,tail=1,则0%的概率跨文件
tail由自己的策略所决定,根据用户能接受的概率,文件大小,记录时长来设置capacity_per_file - 返回的是
-
NPY8.read一次只读取一个文件,适合遍历场景。capacity很大时。可以认为与tail功能接近read(n=1000, prefetch=100)最多返回1000+100条数据
-
NPY8是跨文件的,适合外汇、数字货币场景。股票、期货这类,一个大NPYT文件更好用