问题是n个进程同时读取大小约为20GB的文件。文件在每行包含一个字符串,字符串的长度可能相同也可能不同。字符串长度最多可以为10个字节。
我有一个拥有16个节点的集群。每个节点都是单处理器,有6GB RAM。我使用MPI编写并行代码。
对这个大文件进行分区的有效方法是什么,以便可以利用所有资源?
注意:对分区的约束是将文件读取为固定行数的块。 假设文件包含1600行(例如1600个字符串)。然后第一个过程应该从第1行读到第100行,第二个过程应该从第101行到第200行等等....
因为我认为一次不能通过多个进程读取文件,因为我们只有一个文件处理程序指向某处只有一个字符串。那么其他进程如何从不同的块中并行阅读呢?
答案 0 :(得分:4)
因此,正如您所发现的那样,处理大量数据的文本文件格式很差;它们不仅比二进制格式大,而且还会遇到类似这里的格式化问题(换行换行),而且一切都多慢(数据必须转换为字符串)。对于数字数据,基于文本的格式和二进制格式之间的IO速度很容易有10倍的差异。但我们现在假设您已经停留在文本文件格式中。
据推测,你正在为速度做这个分区。但是,除非你有一个并行文件系统 - 即多个服务器从多个磁盘服务,并且一个FS可以保持这些协调 - 你不太可能从同一个文件读取多个MPI任务获得显着的加速,最终这些请求都将在服务器/控制器/磁盘级别进行序列化。
此外,读取大块数据将比fseek()和读取新行的小读取快得多。
所以我的建议是让一个进程(也许是最后一个)以尽可能少的块读取所有数据,并将相关的行发送到每个任务(包括最终本身)。如果你知道文件在开始时有多少行,这很简单;读入2 GB数据,在内存中搜索N / Pth行的末尾,并将其发送到任务0,向任务0发送“已完成数据”消息,然后继续。
答案 1 :(得分:0)
您没有指定分区是否有任何约束,所以我假设没有。我还假设您希望分区的大小尽可能接近。
天真的方法是将文件拆分为大小为20GB/n
的块。 i
的块i*20GB/n
的起始位置为i=0..n-1
。
问题当然是,无法保证块边界会落在输入文件的行之间。一般来说,他们不会。
幸运的是,有一种简单的方法可以解决这个问题。建立了如上所述的边界后,稍微移动它们,使它们(i=0
除外)放在下面的换行符之后。
这将涉及读取文件的15个小片段,但会导致非常均匀的分区。
事实上,每个节点可以单独进行校正,但可能不值得用这个解释复杂化。
答案 2 :(得分:0)
我认为最好编写一段代码来获取行长度并将行分配给流程。那个分配函数本身不会使用字符串,而只能处理它们的长度。
找到一个算法来均匀分布固定大小的来源不是问题。
之后,分发函数将告诉其他进程他们必须从事什么工作。进程0(分发者)将读取一行。它已经知道,行数。 1应该由进程1工作。... P.0读取行号。 N并且知道什么过程必须使用它。
哦!我们不需要从一开始就优化分配。只需分销商流程从输入中读取新行并将其提供给自由流程。就是这样。
所以,你甚至有两个解决方案:大大优化和简单的解决方案。
如果分销商流程会不时地重新优化未读的字符串,我们可以达到更优化。
答案 3 :(得分:0)
这是python中的一个函数,使用mpi和pypar扩展来读取大文件中的行数,使用mpi在多个主机之间分配职责。
def getFileLineCount( file1 ):
import pypar, mmap, os
"""
uses pypar and mpi to speed up counting lines
parameters:
file1 - the file name to count lines
returns:
(line count)
"""
p1 = open( file1, "r" )
f1 = mmap.mmap( p1.fileno(), 0, None, mmap.ACCESS_READ )
#work out file size
fSize = os.stat( file1 ).st_size
#divide up to farm out line counting
chunk = ( fSize / pypar.size() ) + 1
lines = 0
#set start and end locations
seekStart = chunk * ( pypar.rank() )
seekEnd = chunk * ( pypar.rank() + 1 )
if seekEnd > fSize:
seekEnd = fSize
#find start of next line after chunk
if pypar.rank() > 0:
f1.seek( seekStart )
l1 = f1.readline()
seekStart = f1.tell()
#tell previous rank my seek start to make their seek end
if pypar.rank() > 0:
# logging.info( 'Sending to %d, seek start %d' % ( pypar.rank() - 1, seekStart ) )
pypar.send( seekStart, pypar.rank() - 1 )
if pypar.rank() < pypar.size() - 1:
seekEnd = pypar.receive( pypar.rank() + 1 )
# logging.info( 'Receiving from %d, seek end %d' % ( pypar.rank() + 1, seekEnd ) )
f1.seek( seekStart )
logging.info( 'Calculating line lengths and positions from file byte %d to %d' % ( seekStart, seekEnd ) )
l1 = f1.readline()
prevLine = l1
while len( l1 ) > 0:
lines += 1
l1 = f1.readline()
if f1.tell() > seekEnd or len( l1 ) == 0:
break
prevLine = l1
#while
f1.close()
p1.close()
if pypar.rank() == 0:
logging.info( 'Receiving line info' )
for p in range( 1, pypar.size() ):
lines += pypar.receive( p )
else:
logging.info( 'Sending my line info' )
pypar.send( lines, 0 )
lines = pypar.broadcast( lines )
return ( lines )