c中的并行读/写文件

时间:2012-01-19 08:44:17

标签: algorithm parallel-processing mpi

问题是n个进程同时读取大小约为20GB的文件。文件在每行包含一个字符串,字符串的长度可能相同也可能不同。字符串长度最多可以为10个字节。

我有一个拥有16个节点的集群。每个节点都是单处理器,有6GB RAM。我使用MPI编写并行代码。

对这个大文件进行分区的有效方法是什么,以便可以利用所有资源?

注意:对分区的约束是将文件读取为固定行数的块。 假设文件包含1600行(例如1600个字符串)。然后第一个过程应该从第1行读到第100行,第二个过程应该从第101行到第200行等等....

因为我认为一次不能通过多个进程读取文件,因为我们只有一个文件处理程序指向某处只有一个字符串。那么其他进程如何从不同的块中并行阅读呢?

4 个答案:

答案 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 )