MPI 屏障不阻塞文件写入、刷新和 os.fsync

Posted

技术标签:

【中文标题】MPI 屏障不阻塞文件写入、刷新和 os.fsync【英文标题】:MPI barrier not blocking file write, flush and os.fsync 【发布时间】:2017-12-17 04:06:16 【问题描述】:

我有这个测试代码,它执行以下操作:

将测试消息写入文件 > 屏障 > 读取测试消息 > 断言等于 > 重复。

from __future__ import print_function
import os
from mpi4py import MPI


comm = MPI.COMM_WORLD
rank = comm.Get_rank()
loop = True


def main():
    global loop
    txt_write = 'buhahaha'

    with open('test', 'w') as f1:
        if rank == 0:
            f1.write(txt_write)

        f1.flush()
        os.fsync(f1.fileno())

    comm.barrier()

    with open('test') as f2:
        txt_read = f2.read()

    try:
        assert txt_read == txt_write
    except:
        print("Assertion error", txt_read, "!=", txt_write, 'rank=', rank)
        loop = False
    finally:
        comm.barrier()
        if rank == 0:
            os.remove('test')


if __name__ == '__main__':
    i = 0
    while loop:
        main()
        if i % 1000 == 0 and rank == 0:
            print("Iterations:", i)

        i += 1

它适用于 100 或 1000 次迭代,但随后它会读取一个空文件并且断言失败。其他答案建议使用flushos.fsync,但这似乎无济于事——它只会使执行速度变慢。知道如何解决这个问题吗?

【问题讨论】:

你使用什么文件系统?这是单个节点还是集群? 不以可写方式打开文件通常会将其截断为空吗?那么,您的线程不是在它们中的大多数将其截断为空而一个正在截断它然后向其写入字符串之间竞争吗? @zulan ext4 文件系统在 Linux 上。我在工作站上使用 2 个进程运行此代码。 @jschultz410 MPI 屏障一直等到writeflushos.fsync,最后是__exit__ 函数调用,这会关闭文件。问题是文本保留在 I/O 缓冲区中等待写入。大多数情况下,此代码有效。如果没有,所有线程读取一个空文件,而不仅仅是rank > 1线程。 @jadelord 也许我在这里遗漏了一些非常基本的东西。我的理解是,您有 N 个进程(或线程)在循环内执行 main(),它们在共享磁盘上每次迭代的写入和读取部分之间(以及每次迭代之间)的屏障上同步。我的评论很简单,打开像 open(fname, 'w') 这样的文件通常会将文件截断为空(即写入它),并且不能保证所有修改同一个文件的竞争进程之间的写入顺序。我离这里很远吗? 【参考方案1】:

也许你可以尝试这样的事情,而不是:

if rank == 0:
  with open('test', 'w') as f1:
    f1.write(txt_write)
    # as @jschultz410 correctly pointed out, 
    # we remove f1.flush() and f1.close()

comm.barrier()

with open('test') as f2:
  txt_read = f2.read()

【讨论】:

你有缩进问题。部分代码仅适用于 0 级,部分代码适用于每个级别。而且,您正在打开所有级别的同一个文件进行写作。 我不认为这是缩进问题。这是一个逻辑问题。看起来您希望 open(fname, 'w') 对文件没有影响。另外,为什么要让读者打开文件进行写作?为什么不让 0 级线程成为唯一打开它进行写入的线程?而非 0 级线程直接跳到屏障上等待然后读取? 这正是@mko 在他的代码中所做的,现在我仔细观察了它。 (最初,我认为他让所有人都打开并向其写入相同的字符串,这也可以,但毫无意义的冗余/低效)。 您可以放弃对flush()close() 的显式调用,因为with 子句也会隐式调用它们。干得好@mko,注意到不需要显式的fsync(),因为您已将所有打开+读取命令在单次写入+关闭之后进行,操作系统应该尊重并强制执行。 我同意@jschultz410 的观点。为了进一步解释我为什么在那里这样做,我试图模仿unittest case which was causing problems。测试用例是一个函数,默认情况下按等级 0 打印,stdout 被重定向到一个文件来测试这个特性。我可能需要考虑一个更好的测试。【参考方案2】:

代码生成race condition,所有进程同时打开同一个文件。感谢 @jschultz410 和 @mko 发现这个逻辑错误。

我的代码解决方案是使用内存流而不是真实文件。现在,代码的 open、writeread 部分变为:

from io import StringIO

f1 = StringIO()
if rank == 0:
    f1.write(txt_write)

f1.flush()
comm.barrier()

txt_read = f1.getvalue()

【讨论】:

以上是关于MPI 屏障不阻塞文件写入、刷新和 os.fsync的主要内容,如果未能解决你的问题,请参考以下文章

测试 MPI_Barrier C++

为啥我会收到使用 MPI 屏障 [c++] 的致命错误

MPI 屏障 C++

MPI 中的屏障:如何实现屏障以使进程相互等待

MPI_Barrier - 只有一些进程通过屏障

MPI_Type_commit 是不是隐式调用 MPI_COMM_WORLD 中所有进程的屏障?