kdb q - 有效地计算平面文件中的表

kdb q - efficiently count tables in flatfiles

我有很多 table 存储在平面文件中(在名为 basepath 的目录中),我想检查它们的行数。我现在能做的最好的是:

c:([] filename:system "ls ",basepath; 
      tablesize:count each get each hsym `$basepath,/:system "ls ",basepath)

将每个 table 完全加载到内存中,然后执行计数(这非常慢)。保存为展开 tables 是使它更快的唯一方法(因为我只会加载 1 列并计算它)还是我可以使用 q 中的技巧?

感谢帮助

如果您的表未压缩存储,那么您可以对文件中的 headers 执行 read1 操作,直到找到第一列 header.

但是 v hacky :-(

你负责保存这些吗?你能保持这样的运行状态吗?

您可以使用以下方法使您当前拥有的东西更有效率

counttables:{count each get each hsym `$basepath}

这将通过不包括额外读入的数据以及您当前正在执行的连接来提高计数速度。你是对的,但如果保存的表格展开,你只需要阅读一列就可以提高效率。

如果您将 basepath 定义为存储所有平面 table 的目录路径的字符串,那么您可以创建一个行数字典,如下所示:

q)cnt:{count get hsym x}
q)filename:key hsym `$basepath
q)filename!cnt each filename
t| 2
g| 3

这是我在基本路径目录中保存平面 tables t 和 g 的位置。这使您不必使用通常效率较低的 system 命令。 函数 cnt 获取每个平面的路径 table (作为符号)和 returns 行数而不将它们保存到内存中。

如果您可以控制保存此类文件的过程,那么最好的解决方案是添加一个额外的步骤,在保存原始数据的同时将行数的元信息保存在单独的某个地方。这将允许您从此文件快速访问 table 大小,而不是每次都读取完整的表。

但是,请注意,要完全避免将它们拉入内存,您必须改为使用 read1 并查看二进制数据上的 header。正如您所说,最好保存为展开的 table 并在一栏中阅读。

更新:我不建议这样做,并强烈建议这样做,但在研究使用 read1 后出于好奇,这里有一个 hacky 解决方案可能是什么样的示例:

f:{
  b:read1(y;0;x);
  if[not 0x62630b~b[2 4 5];'`$"not a table"];
  cc:first first((),"i";(),4)1:b 7+til 4;
  if[null ce:first where cc=sums 0x0=11 _ b;:.z.s[x*2;y]];
  c:`$"[=11=]0" vs "c"$b[11+til ce];
  n:first first((),"i";(),4)1:b[(20+ce)+til 4];
  :`columns`rows!(c;n);
  }[2000]

q 二进制文件格式在任何地方都没有记录,弄清楚它的唯一方法是保存不同的东西并查看字节如何变化。它也受版本之间变化的影响 - 以上是为 3.5 编写的,可能仅对 3.0-3.5 有效,不适用于最新的 3.6 版本或任何 2.X.

给定的代码按以下方式工作:

  1. 从文件前面读取一个块

  2. 验证它看起来像一个没有键控的平面table(用符号[11]键翻转字典[99]的[98])

  3. 将列列表中的符号计数读取为 little endian 4 字节 int
  4. 扫描以 null 结尾的字符串以获得那么多零字节
  5. 如果列太多或冗长以至于我们没有它们 all in this chunk 它将使块的大小加倍并重试
  6. 将字符串转换为符号
  7. 使用我们从列列表末尾获得的偏移量,跳过一点 更多 header 列的混合列表
  8. 然后从第一列的header读取计数

希望这能回答您的问题!

从对二进制文件的实验来看,似乎 table 计数在您保存平面文件时作为二进制文件的一部分保存,在初始 object 之后占用 4 个字节类型和列标题将从 table 到 table.

`:test set ([]a:1 2 3;b:4 5 6;c:7 8 9;aa:10 11 12;bb:13 14 15)
q)read1 `:test
0xff016200630b000500000061006200630061610062620000000500000009000300000
  0             7       11                                      31          

bytes             | example                  | meaning
---------------------------------------------------------------------------------------
0 - 5             | 0xff016200630b0          | object is a flat table
7 - 11            | 0x05000000               | number of columns (5)
12- 22            | 0x6100620063006161006262 | one byte for the ascii values of column "a" and "b" in hex followed by the one byte separator
23 - 30           | 0x0000050000000900       | 8 bytes that can be skipped
31 - 34           | 0x0300000                | 4 bytes for row count of first column (3)

这应该可以帮助您理解 Fiona 发布的函数。

二进制文件保存下来 little-endian 意味着 most-significant 字节是 right-hand 最多的数字 - 对数字 100 以十进制执行此操作将得到 001,而 100 的(最多significant) 在右边,然后是 10s,最后是 1s 在左边。在二进制文件中,每组2位为一个字节。

您可以使用 1: 读取二进制文件的内容,列表中的附加参数指定偏移量 - 从哪里开始读取,以及要读取多少字节。在我们的例子中,我们希望从第 31 个字节开始读取 4 个字节,指定输出应该是一个整数并将输入分成单独的 4 个字节块。

q)first first (enlist "i";enlist 4)1:(`:test;31;4)
3i

将 little-endian 字节转换为 long 给我们行数。由于这只需要读取 4 个字节而不是整个文件,因此速度要快得多。

对于具有 10000 行和 2 列的 table,差别不大:

q)\t 0x0 sv reverse first (enlist "x";enlist 1)1:(`:test10000;31;4)
0
q)\t count get `:test10000
0

对于具有 100m 行和 2 列的 table:

q)\t 0x0 sv reverse first (enlist "x";enlist 1)1:(`:test10m;31;4)
0
q)\t count get `:test10m
2023

如果您有一个展开的 table,您可以像这样从字节 9-13 读取其中一列中的元素数量,假设该列是一个简单的列表:

q)first first (enlist "i";enlist 4)1:(`:a;8;4)
3i

您可以在此处阅读有关从二进制文件读取的更多信息https://code.kx.com/q/ref/filenumbers/#1-binary-files