解析用Tab分隔的大文件(~2Gb)的最快方法



请指导我:有没有更有效的方法来执行这样的任务:

  1. 从第一个制表符分隔的文件中读取键值对到映射容器中
  2. 读取第二个制表符分隔的文件(键值对)时,写入第三个文件

对于具有7000000行的fname1和fname2,执行这两个步骤花费了3700秒。

#include <string>
#include <map>
#include <fstream>
string col1;
int x;
map<string, int> m;
ifstream is1(fname1, ios::in | ios::binary);
ifstream is2(fname2, ios::in | ios::binary);
ofstream os3(fname3, ios::out | ios::binary);
if (is1.is_open())
{
while (is1 >> col1 >> x)
{
m[col1] = x;
}
is.close();
}
if (is2.is_open() && os3.is_open())
{
while (is2 >> col1 >> x)
{
if (m.count(col1) > 0)
x += m[col1];
os3 << col1 << "t" << x << endl;
}
is2.close();
os3.close();
}

我做错了什么?有没有更有效的方法来执行这些任务?还是文件I/O在大多数情况下是一个瓶颈?

更新:这里我放了两个相同算法的实现。主要问题是:为什么Python版本的运行速度更快?我决定转向C++,因为我听说它提供了更快的代码。我错了吗?

fname1、fname2—输入。fname3-所需输出。

fname1:

col1 col2 col3

1 1 1

2 2

fname2:

col1 col2 col3

1 1 2

3 3 3

fname3:

col1 col2 col3

1 1 3

2 2

3 3 3

def merge_two_files(fname1, fname2, fname3):
fout=open(fname3,'w')
fin1=open(fname1)
d1=dict()
for line in fin1:
l=line.strip().split('t')
key='t'.join(l[0:2])
d1[key] = float(l[2])
fin1.close()
d2=dict()
fin2=open(fname2)
for line in fin2:
l=line.strip().split('t')
key='t'.join(l[0:2])
d2[key] = float(l[2])
fin2.close()
for e in d1.viewkeys() & d2.viewkeys():
line_out='t'.join([e,'{:.2f}'.format(d1[e]+d2[e])])
fout.write(line_out+'n')
for e in d1.viewkeys() - (d1.viewkeys() & d2.viewkeys())
line_out='t'.join([e,'{:.2f}'.format(d1[e])])
fout.write(line_out+'n')
for e in d2.viewkeys() - (d1.viewkeys() & d2.viewkeys())
line_out='t'.join([e,'{:.2f}'.format(d2[e])])
fout.write(line_out+'n')
#include <fstream>
#include <string>
#include <unordered_map>
#include <set>
using namespace std;
int main() {
unordered_map < string, float > map1, map2 ;
set < string > s1, s2, both ;
string col1, col2, key, fname1, fname2, fname3 ;
float col3 ;
ifstream f1 ( fname1, ios::in | ios::binary) ;
ifstream f2 ( fname2, ios::in | ios::binary) ;
ofstream f3 ( fname3, ios::out | ios::binary) ;
if ( f1.is_open() ) {
while ( f1 >> col1 >> col2 >> col3 )
key= col1 + "t" + col2 ;
map1.insert(make_pair(key,col3)) ;
s1.insert(key) ;
}
f1.close()
if ( f2.is_open() ) {
while ( f2 >> col1 >> col2 >> col3 ) {
key= col1 + "t" + col2 ;
map2.insert(make_pair(key,col3)) ;
s2.insert(key) ;
}
}
f2.close() ;
set_intersection(s1.begin(), s1.end(),
s2.begin(), s2.end(),
inserter(both, both.begin())) ;
if ( f3.is_open() ) {
for ( const auto& e : both ) {
f3 << e << "t" << map1.at(e) + map2.at(e) << "n" ;
}
for ( const auto& kv : map1 ) {
if ( both.count(kv.first) ) continue ;
f3 << kv.first << "t" << kv.second << "n" ;
}
for ( const auto& kv : map2 ) {
if ( both.count(kv.first) ) continue ;
f3 << kv.first << "t" << kv.second << "n" ;
}
}
f3.close() ;
return 0;
}

至少在这些文件中有大量不同密钥的限制下(我假设你在这里已经足够接近了),限制因素将是map密钥查找,而不是IO。在std::map中查找密钥具有时间复杂性O(ln n),其中n是容器中不同密钥的数量。

使用平均分摊了O(1)密钥查找时间的std::unordered_map

在您开始根据错误的假设进行优化之前,请使用探查器告诉您大部分时间花在了哪里。在这种情况下也要做同样的事情来验证我是否正确。

瓶颈

您的瓶颈是1)文件I/O和2)格式转换。文件可能需要一些开销,如启动(硬盘驱动器)、查找时间和实际读取时间。

数据流

无论文件位于闪存驱动器还是硬盘驱动器上,流式传输数据都是最有效的(即保持数据流动)。例如,在1个事务中读取1k数据比在1k个事务中阅读1k数据更有效。这里最有效的方法是将大量数据读入内存。您可能需要检查您的操作系统,看看它是否支持内存映射。

转换数据

下一个瓶颈是解析数据并将其从文本格式转换为内部数字格式。这需要时间。要加快速度,请将数据写入二进制(内部)表示。由于Endianess或浮点问题,这可能是不可能的。

存储数据

我想下一个瓶颈是将内部格式化的数据存储到数据结构中。对于键、值、对,您可能需要使用std::mapstd::unordered_map。如果您不关心搜索时间,您可能希望创建一个具有值的struct并存储到数组中(如果在编译时数量未知,则为std::vector)。

分析

找出瓶颈所在的最佳方法是配置。重复应用程序1E6次并取平均值(您可能需要运行1E9才能获得更好的准确性)。在互联网上搜索"基准C++程序"。基准测试将向您展示如何获得更好的结果。

相关内容

最新更新