2个版本
0.1.1 | 2020年1月5日 |
---|---|
0.1.0 | 2020年1月5日 |
在数学类中排名991
27KB
264 行
欢迎使用RawArray库!
简介
RawArray是一个用于存储n维数组的简单文件格式。扩展名.ra
可以发音为arr-ay或rah(就像“raw”,或古埃及太阳神)。
RawArray被设计成可移植、快速且存储效率高。特别是对于科学应用,它可以在不使用单独的头文件来存储维度和类型元数据的情况下,简单地存储大型数组。
我认为世界不需要另一个分层数据容器。我们已经有一个了——它被称为文件系统。所需的是一种简单的数据结构到磁盘文件的映射,该映射保留元数据,并且读写速度快且简单。
除了任意大小的int、uint和float外,RawArray还支持
(1) 复数浮点数:这是其他常见格式,如HDF5所不具备的。
(2) 复合类型:RawArray可以处理这些类型的读写,但编码和解码留给用户,因为只有他们才知道自己的struct
结构。对于固定大小的类型,解码可以像类型转换一样简单。在Rust中,它们被读取为Vec<T>
,所以你可以自由地按需处理。
顺便说一下,RawArray格式在技术上来说是递归的(或者说是分形?!)。如果你想通过定义文件为复合类型,可以在一个RawArray文件中存储RawArray文件的数组。
格式
文件格式是由一个头数组和一个数据数组简单拼接而成的。头数组由至少七个64位无符号整数组成。数组数据可以是任何你想要的内容。可选地,可以在文件末尾附加文本或二进制元数据,这对文件没有害处,但库不会保存或写回这些数据。跟踪这些数据是你的责任。
文件结构
偏移量(字节) | 对象 | 类型 | 含义 |
---|---|---|---|
HEADER | |||
0 | 魔数 | UInt64 | 魔数 |
8 | 标志 | UInt64 | 字节序,未来选项 |
16 | eltype | UInt64 | 元素类型代码 |
24 | elbyte | UInt64 | 元素大小(字节) |
32 | size | UInt64 | 数据段长度(字节) |
40 | ndims | UInt64 | 数组维度数量 |
48 | dims | Vector{UInt64} | 数组维度 |
48 + 8 x ndims | data | Vector{UInt8} | 数组数据 |
48 + 8 x ndims + size | - | - | 易失性元数据 |
元素类型规范
代码 | 类型 |
---|---|
0 | 用户定义 |
1 | 有符号整数 |
2 | 无符号整数 |
3 | 浮点数(IEEE-754标准) |
4 | 复浮点数(IEEE浮点数的对) |
5 | 脑浮点数 |
这些类型的宽度在elbyte
字段中单独定义。例如,
- 一个32位无符号整数将是
eltype = 2
,elbyte = 4
; - 一个单精度复浮点数(32位浮点数的对)将是
eltype = 4
,elbyte = 8
; - 一个字符串将是
eltype = 2
,elbyte = 1
,而size
将包含字符串的长度。
用户定义的结构
struct foo {
char info[12];
uint32_t index;
double v[8];
}
包含一个12字节的字符串,一个4字节的int和8个8字节的浮点数,所以总大小是80字节。它将被编码为eltype = 0
,elbyte = 80
。
数据以你在其上的硬件的二进制表示形式写入和读取。目前假设为小端字节序
,但如果有兴趣,可以添加大端字节序的支持。
内存序
RawArray格式是列主序
,因此第一个维度将是内存中变化最快的维度。这个决定是因为大多数科学语言传统上是列主序的,尽管C在技术上是以行为主序的,但实际上在通过计算线性索引访问多维数组的应用中是无关的(例如CUDA)。在提供的示例中,所有示例都是列主序的,除了Python。在Python的情况下,我们不是将数组读入Python并重新排序到非最优步长,而是在写入和读取之前简单地转置维度。这意味着在Python中数组看起来是转置的,但所有语言中的维度步长相同。换句话说,Python中数组的最后一个维度将是Julia和Matlab中的第一个维度。
文件内省
为了更好地了解RawArray文件格式,让我们看看一个文件内部的情况。如果你在Unix系统上或在Windows上安装了Cygwin,你可以使用命令行工具检查RawArray文件的内容。在本节中,我们将使用test.ra
文件,该文件位于julia/
子目录中。
首先,让我们假设你不知道数组的维度。然后
> od -t uL -N 48 test.ra
0000000 8746397786917265778 0
0000020 4 8
0000040 96 2
0000060
显示第三行第二个数字为维度(2)。该命令提取前48个字节并将它们格式化为UInt64。第一个列出的是荒谬的数字,表示这是一个RawArray文件。一个略微不同的命令揭示了这一点
> od -a -N 16 test.ra
0000000 r a w a r r a y nul nul nul nul nul nul nul nul
0000020
凭借数组是2D的知识,我们知道头文件长度为48 + 2*8 = 64
字节。跳过头文件只查看数据的命令将是
> od -j 64 -f test.ra
0000100 0.000000e+00 -inf 1.000000e+00 -1.000000e+00
0000120 2.000000e+00 -5.000000e-01 3.000000e+00 -3.333333e-01
0000140 4.000000e+00 -2.500000e-01 5.000000e+00 -2.000000e-01
0000160 6.000000e+00 -1.666667e-01 7.000000e+00 -1.428571e-01
0000200 8.000000e+00 -1.250000e-01 9.000000e+00 -1.111111e-01
0000220 1.000000e+01 -1.000000e-01 1.100000e+01 -9.090909e-02
0000240
在这里,我们使用-j
跳过前64个字节,使用-f
将字节数据格式化为单精度浮点数。注意od
不理解复数,但复数数据以实部和虚部浮点对的形式存储在磁盘上,它们是连续的。这意味着输出中的每一行都显示了两个复数,列1和3是实部,列2和4是虚部。注意它正确地渲染了负无穷大。
获取
... TODO:解释cargo和添加一些二进制工具...
用法
在你的Rust源代码中的一个示例用法是
use rawarray::RawArray;
use std::io;
fn main() -> io::Result<()> {
let vec1: Vec<f32> = vec![1.0, 2.0, 3.0, 4.0];
let ra: RawArray<f32> = vec1.clone().into();
ra.write("myarray.ra")?;
let vec2: Vec<f32> = RawArray::<f32>::read("myarray.ra")?.into();
assert_eq!(vec1, vec2);
Ok(())
}
校验和和时间戳
数据校验和或时间戳被故意不包括在格式中,因为无法对包含其校验和的文件进行校验和。**现有的方法(例如tar)通常将校验和字段清零,然后校验文件其余部分,但这需要理解格式的特殊软件,因此标准命令行校验和工具将不起作用。校验和验证最好留给外部手段,即使它需要单独的文件。
时间戳也不必要,因为文件系统已经提供了这个功能。添加在重写或访问时更改的时间戳也会阻止校验和尝试。由于这个原因,HDF5文件校验和非常困难。我们相信,校验和应该只依赖于数据属性,而不是任何时间顺序。如果两个文件包含相同的数据,它们就是相同的,不管它们是在什么时候创建或最后访问的。
要校验和RawArray文件,只需运行你本地的校验和命令。例如,在Linux上
> md5sum examples/test.ra
1dd9f98a0d57ec3c4d8ad50343bd20cd examples/test.ra
** 从技术上讲并非不可能,但计算上非常困难。
获取帮助
如有帮助,请在问题跟踪器上提交问题或发送电子邮件给作者之一。欢迎第三方帮助并通过拉取请求进行贡献。
作者
David S. Smith [email protected]
免责声明
此代码没有任何保证。自行承担风险使用。如果它损坏,请让我们知道,我们将尽力帮助您修复它。
依赖关系
~1.5MB
~31K SLoC