【问题标题】:Storage for numerical data in binary files & data structures二进制文件和数据结构中数字数据的存储
【发布时间】:2018-05-22 12:40:31
【问题描述】:

这里的话题很大,我是新手,我正在寻找方向,因为这个话题的可能性似乎无穷无尽。

我正在运行一个数值模拟,它会创建大量数据,我希望不再以纯文本形式存储它(一旦我尝试保存所有创建的数据并最终得到一个 4TB 的 txt 文件)。

我的模拟涉及一个区间内的 4 个字段(它们每个都由通常包含 4000 到 16000 个元素的双精度数组表示)并且它们每次演化大约 100 万个周期,所以我们谈论的是生成的数十亿双精度。

当然,我不会每次都保存所有内容,而是使用 3 种类型的文件 (出于简短的原因,这些文件是一个模型,我的实际文件都是以 %g 格式编写的,因此它们采用这 7 个字符 + 制表符):

  1. 将字段内容保存在所有时间步的特定点的文件,例如:

    t     Phi    Pi    Delta    A
    0     1.3    0.4   0.3      0.99
    ...
    
  2. 在特定时间步长的所有区间内保存所有字段的文件

    x     Phi   Pi    Delta    A
    0     0.0   0.4   0.0      1.0
    ...
    
  3. 在时间和空间上每 n 步保存一次的文件

    t    x    Phi    Pi    Delta    A
    0.0  0.0  0.0    1.3   0.0      1.0
    0.0  0.1  0.01   1.2   0.02     0.98
    ...
    0.2  0.0  0.0    1.3   0.0      1.0
    0.2  0.1  0.03   1.5   0.01     0.95
    

然后我将这些文件用于各种目的,例如绘制图形、对其进行傅立叶变换并使用它们来恢复模拟。

我最终需要在集群上运行它,所以我仅限于 C,目前我不知道他们是否有任何数据库/大数据系统。

我的问题是:

  1. 什么是存储这些数据的最佳格式,我认为它只是将双打保存为原始二进制文件,然后编写一个程序稍后检索它们,但我愿意接受建议
  2. 组织这些数据的最佳方式是什么?我环顾四周,也许我可以写一棵树叶是数组的树
  3. 压缩怎么样?

【问题讨论】:

  • doubles 存储为二进制文件并不是你的“灵丹妙药”。考虑0.0 仅消耗 3 个字节(另外,可能是一个字节用于分隔制表符或空格)。 double 无论其值如何,都不断消耗 8 个字节。压缩可能会有更好的帮助。此外,它可以应用于二进制内容以及文本。 (人类可读文本的压缩通常可以实现令人印象深刻的缩减,尤其是对于大文件。)
  • 这只是一个例子,通常我会充分利用双精度,并保存为 %g 格式,我会用一些更真实的值来更新问题
  • 这些是我在盯着您的样本时想到的可能选项: 1. 存储 delta-s 而不是绝对值。 (这可能会使指数保持在较低水平。例如,视频压缩使用 deltas 与其他技术相结合。) 2. 这假设您可以将值存储为二进制 floats 而不是 doubles 而不会损失太多精度(即 float - > 无论值如何,始终为 4 个字节)。您甚至可以考虑存储固定点值(又名 int 具有一定比例),而不是每个值甚至少于 32 位。
  • 可能是,不是很认真但很容易实现:如果您将数字存储为文本(如问题所示),您只需要0 ... 9.-E、` `(或制表符)和 \n。这些是 15 个字符(可以用 4 位存储)。因此,您可以将两个字节压缩为一个 - 分别映射这些字符。 (我假设样本的标题不是必需的。)这会将文件大小至少减少一半,并且压缩/解压缩应该非常快。 :-)
  • @Scheff 你最后的建议很有趣,我想知道我是否可以简单地使用纯文本,然后使用一些 hoofman 树魔法

标签: c data-structures binaryfiles


【解决方案1】:

存储这些数据的最佳格式是什么

这取决于精度和值的结构。

如果 7 位有效十进制数字就足够了,并且值在 2-126 到 2127 (1.17549×10-38到 1.70141×1038),那么您可以使用 IEEE-754 binary32 格式。在用于高性能计算的所有机器和集群上,float 类型对应于此。

如果您需要 15 位有效小数位的精度,和/或范围从 2-1023 到 21023 (1.11254×10-308 到 8.98847×10308),使用 IEEE-754 binary64 格式。同样,在用于高性能计算的所有机器和集群上,double 类型对应于此。

剩下的问题是字节顺序和字段识别。

假设您不希望在计算期间将任何 HPC 资源用于数据转换,最好以原生字节顺序存储数据,但在文件中包含一个包含每个值类型的已知“原型”值的标头,以便读者可以检查它们以验证是否需要字节顺序补偿来正确解释字段;加上每个字段的描述符。

(例如,我以一种方式实现了这一点,允许在 C 和本机 Fortran 95 中轻松读取文件,并使用最少的编译器扩展,还允许每个计算节点将结果保存在本地文件中,使用阅读器自动从多个文件并行获取数据。我通常只支持u8s8u16s16u32s32u64s64(对于未签名和各种位大小的有符号整数),r32r64 分别用于单精度和双精度实数,或 Binary32 和 Binary64)。我还不需要复数格式。)

大多数人更喜欢使用例如NetCDF 为此,但我的方法不同之处在于作者以本机格式而不是规范化格式生成数据;我的意图是尽量减少数据创建/模拟时的开销,并将所有开销推给读者。

如果您发现文件生成时(模拟期间)的小开销是可以接受的,并且没有编写二进制文件格式例程的经验,我建议您使用 NetCDF。

请注意,如果 HPC 集群操作员发现您的模拟/计算浪费资源(例如,平均核心 CPU 负载较低,或者不能很好地扩展到多个核心),则可能不允许您在簇。显然,这也取决于当地的政治和政策。

组织这些数据的最佳方式是什么?

由于数据量非常大,并行文件可能是您的最佳选择。 (某些集群具有快速的本地存储,在这种情况下,将数据直接从每个节点存储到本地文件,并在运行后将这些本地文件收集成一堆,可能会更好。因为它会有所不同,请咨询您的集群管理员。)

换句话说,一个文件对应一个相关的数据数组。

编写一个可以并行读取多个文件的库并不难,但正确解析和管理结构化文件要困难得多。

此外,将数据拆分为单独的文件通常会使数据传输更容易。如果您有一个 16 TiB 大小的数据文件,那么您基本上受限于网络传输,甚至可能受限于您可以使用哪些文件系统。但是,如果您有 128 个文件,每个文件大小约为 128 GiB,那么您有更多选择,并且可能可以将其中一些保留在离线存储中,同时处理其他文件。特别是很多 HPC 集群运营商会让你将文件直接传输到本地媒体存储设备(USB3 磁盘或记忆棒),以减少网络传输拥塞。

压缩怎么样?

如果需要,您可以压缩数据,但我个人会在您自己的工作站上收集/合并/处理数据时进行压缩,而不是在生成数据时进行。 HPC 计算成本高昂;在您第一次处理数据时将数据整理起来要便宜得多。

二进制数据的压缩效果不如文本,但在相同的数据分辨率下,文本文件要大得多。这意味着无论如何选择用于存储每个参数的正确值类型非常重要。并且您希望在整个集合中保留该类型,而不是从记录更改为另一个,以保持处理简单。

至于压缩/解压缩算法,我会在zlibxz 之间进行选择。参见例如here 快速查看速度/压缩比曲线。简而言之,zlib 速度快但压缩率适中,而 xz 速度较慢但压缩率要好得多。

【讨论】:

  • 感谢您的完整回答和解释,我正在阅读有关 NetCDF 的内容,因为它似乎很有用,有什么好的教程/指南可以帮助我入门吗?
  • @AndreaNardecchia:NetCDF 主页包含文档链接,包括 tutorial;尤其是本教程应该可以帮助您入门。
  • 致任何未来的读者:.nc 文件是第 3 方软件支持的格式,例如只需几个命令行,您就可以将内容导入 Matlab
  • @AndreaNardecchia:你可以自己写一个答案,概述你的发现,以及你做出选择的依据,然后接受它作为答案。 (我不关心这里的观点;我只是希望人们能够构建更好的东西。但请注意,如果你这样做了,解释你的选择及其基础对于考虑类似问题的未来开发人员非常重要;他们可能有不同的情况,因此您的答案可能不适合每个人:上下文很重要。例如,由于我的答案中概述的原因,我自己不使用 NetCDF。许多其他人都这样做了,而且成功了。)
【解决方案2】:

假设值在 0.00 和 9.99 之间,您可以使用 4 位编码,包括数字分隔符和行尾指示符:

/*
 0    1    2    3    4    5    6    7    8    9    \t   \n
 0000 0001 0010 0100 0100 0101 0110 0111 1000 1001 1010 1011

 1.99\t --> 0001 1001 1001 1010
*/

uint16_t cvtnum(double d, int endl)
{
    char snum[6], *cp=snum;
    uint16_t out= 0;
    sprintf(snum,"%.2f",d);
    while (*cp) {
        if (*cp!='.') out = (out<<4) | (*cp - '0');
        cp++;
    }
    if (endl)
        out = (out<<4) | (11);
    else
        out = (out<<4) | (10);
    return out;
}

请注意,小数点未编码并假定在第一位数字之后。如果endl 为真,则附加行尾指示符,否则附加数字分隔符。

【讨论】:

  • 非常有趣,遗憾的是我认为这行不通,因为我的数据变化至少在 8 个数量级之内,我一回到家就会修改表格
猜你喜欢
  • 1970-01-01
  • 2017-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多