关联数组:Python vs Perl vs Awk 性能

gka*_*doi -3 python perl performance awk

我正在处理具有约 4 亿行和 3 列的文件。前两列是字母数字字符串,而最后一列是数字。像这样的东西:

NM_001011874.1,NM_011441.4,-0.131672299779153
Run Code Online (Sandbox Code Playgroud)

我有多个这样的文件,它们的行数和 3 列数大致相同。这些不一定基于 3 列中的任何一列进行排序。我正在尝试根据前两列的组合来组合这些文件。例如:

File 1

NM_001011874.1,XR_104537.1,-0.929524370261122
NM_001011874.1,XM_003084433.1,-0.603098764428879

File 2

NM_001011874.1,XR_104537.1,-0.11254525414
NM_001011874.1,NM_005040.1,-0.20509876488

File 3

NM_001011874.1,XR_104537.1,-0.41254525414
NM_001011874.1,NM_005040.1,-0.60509876488
Run Code Online (Sandbox Code Playgroud)

我想要做的是通过使用前两列中的值组合来创建一个键,然后从该对的第三列中检索相应的值。我得到的最终输出是:

Output2 

NM_001011874.1,XR_104537.1,-0.11254525414,-0.929524370261122,-0.41254525414
NM_001011874.1,NM_005040.1,-0.20509876488,,-0.60509876488
Run Code Online (Sandbox Code Playgroud)

我正在使用 awk 执行上述操作:

awk -F',' 'NR==FNR{a[$1,$2]=$3;next}{$4=a[$1,$2];print}' OFS=',' file1.txt file2.txt
Run Code Online (Sandbox Code Playgroud)

我为任务分配了 256GB。使用上述命令通过组合两个文件来生成输出大约需要 90 分钟,其中每个文件有大约 4 亿行和 3 列。输出文件再次有大约 4 亿行但有 4 列。每添加一列,生成输出文件所花费的时间就会增加。

我是按顺序进行的,即合并 file1 和 file2 以生成具有 4 列的 output1。然后合并 file3 和 output1 以生成具有 5 列的 output2,然后将 file4 和 output2 合并以生成具有 6 列的 output3,依此类推,直到我得到具有 22 列的最终输出。

我想知道在 Python 或 Perl 中执行此操作在速度和自动化方面是否更有效?我有大约 20 个这样的文件,每个文件有 3 列,尽管行从约 1 亿到约 4 亿不等。如果您认为我最好在 Python 或 Perl 中执行此操作,请分享一个示例来说明 awk 脚本如何转换为 Python 或 Perl。

编辑:根据评论添加了文件 3 和之后的最终输出。

Sch*_*ern 5

当您拥有庞大的数据文件并希望有效地使用它们时,您可能最好将它们放入 SQLite 数据库中,对它们进行索引,然后对其进行查询。有关更多信息,请参阅我关于 CSV 与 SQLite 性能的回答

为数据创建一个表(stuff 是一个可怕的名字,但我不知道这个数据是什么,所以它是“stuff”)。

create table stuff (
    key1 text,
    key2 text,
    value real
);
Run Code Online (Sandbox Code Playgroud)

使用 SQLite shell 将您的 CSV 导入表中。

sqlite> .mode csv
sqlite> .import file1 stuff
sqlite> .import file2 stuff
sqlite> .import file3 stuff
Run Code Online (Sandbox Code Playgroud)

为键创建索引。

create index stuff_key on stuff (key1, key2);
Run Code Online (Sandbox Code Playgroud)

查询您的内心。

select value
from stuff
where key1 = "NM_001011874.1" and
      key2 = "XR_104537.1"

-0.929524370261122
-0.11254525414
-0.41254525414
Run Code Online (Sandbox Code Playgroud)

导入和索引完成后,数据有多大就无关紧要了。无需更新 CSV 并重新导入所有 CSV,您可以导入仅包含新字段的小型 CSV 文件。或者您可以跳过 CSV 并直接插入它们。

insert into stuff (key1, key2, value)
values ("NM_204958293.2", "XR_29238498.3", -239.2),
       ("NM_2904892.3", "XR_3093.0", 9482.39);
Run Code Online (Sandbox Code Playgroud)

我测试了这个的性能,因为我已经提倡过很多次了,但没有测试过。

首先我清理了一堆磁盘空间,因为这些文件会变大。我在 2011 年最先进的 Macbook Pro i7 上执行此操作。幸运的是,它具有售后市场 SSD,因此 I/O 性能非常出色。它毫不懈怠,但也不是顶级服务器。重点是,您不需要花哨的硬件来获得良好的性能。

然后我编写了一个Perl 程序来生成 4 亿行数据,然后在运行时我编写了一个 C 程序来更快地完成它。在一个罕见的例子中,对于一次性脚本来说,程序时间程序员时间更重要,C 程序首先完成了两个大致相同的 14G 文件。它们有点不同,但对我们的目的来说无关紧要。

然后我创建了表并开始导入。最初的导入时间并不是很重要,因为我不必坐在这里盯着它看或照顾它。我知道它会起作用,我知道我只需要做一次,所以我可以并行处理任意数量的事情(比如编辑这篇文章)。不幸的是,SQLite 不是并行工作的,它似乎只使用一个内核。OTOH 它使用的内存不超过大约 3 兆。

导入一个 4 亿行的文件需要 20 分钟。生成的 SQLite 数据库大约有 17 个演出,因此数据没有很大的扩展。我不会做剩下的,因为它很快就变得多余了。

现在我正在创建索引。同样,这是一次我不必坐下来观看的事情......除非我这样做,因为它使用了 1 gig 的虚拟内存,而 SQLite 文件现在是 30 gigs。所以...更多的文件删除。建立索引大约需要 30 分钟。

使用 30 gigs 的磁盘进行 50 分钟的导入和索引,大约是原始数据的两倍。无需编程。