Python multiprocessing + scipy:过多的文件系统'stat'和'open'尝试

时间:2012-06-11 02:05:03

标签: python random scipy multiprocessing strace

我在Python中观察到一些极端奇怪的行为。请考虑以下代码:

from multiprocessing import Process  
import scipy

def test():
    pass

for i in range(1000):
    p1 = Process(target=test)
    p1.start()
    p1.join()
    print i

当我对此运行strace -f时,我从循环中得到以下段:

clone(Process 19706 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x2b23afde1970) = 19706
[pid 19706] set_robust_list(0x2b23afde1980, 0x18) = 0
[pid 18673] wait4(19706, Process 18673 suspended
 <unfinished ...>
[pid 19706] stat("/apps/python/2.7.1/lib/python2.7/multiprocessing/random", 0x7fff041fc150) = -1 ENOENT (No such file or directory)
[pid 19706] open("/apps/python/2.7.1/lib/python2.7/multiprocessing/random.so", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid 19706] open("/apps/python/2.7.1/lib/python2.7/multiprocessing/randommodule.so", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid 19706] open("/apps/python/2.7.1/lib/python2.7/multiprocessing/random.py", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid 19706] open("/apps/python/2.7.1/lib/python2.7/multiprocessing/random.pyc", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid 19706] open("/dev/urandom", O_RDONLY) = 3
[pid 19706] read(3, "\3\204g\362\260\324:]\337F0n\n\377\317\343", 16) = 16
[pid 19706] close(3)                    = 0
[pid 19706] open("/dev/null", O_RDONLY) = 3
[pid 19706] fstat(3, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0
[pid 19706] exit_group(0)               = ?
Process 18673 resumed
Process 19706 detached

关于在“文件系统”中搜索“随机”的所有垃圾是怎么回事?我真的想避免这种情况,因为我在群集上并行运行了很多具有此结构的进程,并且循环速度非常快,而且这种文件系统活动会阻塞文件系统元数据服务器,或者集群管理员告诉我

如果我删除“import scipy”命令,那么这个问题就会消失:

clone(Process 23081 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x2b42ec15e970) = 23081
[pid 23081] set_robust_list(0x2b42ec15e980, 0x18) = 0
[pid 22052] wait4(23081, Process 22052 suspended
 <unfinished ...>
[pid 23081] open("/dev/null", O_RDONLY) = 3
[pid 23081] fstat(3, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0
[pid 23081] exit_group(0)               = ?
Process 22052 resumed
Process 23081 detached

但我需要scipy在我的真实代码中,所以我不能摆脱它。或许我可以,但那会很痛苦。

有谁知道为什么我会看到这种行为?如果它是某种版本的东西的怪癖,我正在运行以下内容:

python:2.7.1, 多处理:0.70a1, scipy:0.9.0,

实际上,因为我刚刚意识到它可能与系统有关,所以我在笔记本电脑上运行了相同的代码并且没有问题(即输出相当于第二种情况)。在我正在运行的笔记本电脑上

python:2.6.5, 多处理:0.70a1, scipy:0.10.0,

可能是早期版本的scipy中的问题或错误已修复?我对这类事情的搜索没有任何结果。即使这是问题,在集群上更改scipy的版本也不是那么容易,尽管我可以让集群管理员在需要时构建更新的版本。

这可能是问题吗?

4 个答案:

答案 0 :(得分:5)

这不是因为Windows或__main__模块。这也不是Python喜欢做生意的方式。而且,如果您要重新检查,我认为您会发现它是Python 2.6的行为而不是2.7,除非您运行的是修改后的2.7。

问题完全正确,问题源于multiprocessing.forking模块中的随机模块初始化步骤 - 该模块旨在阻止您的流程,当它分叉生成 n 工作者时,从创建工作人员到完全相同的一系列伪随机数(例如,如果他们都使用这些数字协商SSL连接可能会危及安全性):

        if 'random' in sys.modules:
            import random
            random.seed()

但这里的关键是要从系统调用的角度认识到上面的import语句应该是无操作的,因为如果模块名已经存在作为sys.modules字典中的键,import只返回它在那里找到的值,而不试图从文件系统加载任何东西:

>>> import sys
>>> sys.modules['fake'] = 'Not even a module'
>>> import fake
>>> fake
'Not even a module'

因此,上面引用的if语句专门试图在import模块没有的情况下阻止额外random的费用甚至装了。当您在未加载scipy的情况下进行实验时,if语句正文甚至不会触发。

那么问题是什么?

问题是2.7之前的旧版本的Python让你意味着两个不同的东西在一个包含在一个包中的模块中说import foo:你可能正在尝试相对导入是the_package.foo,或者您可能正在尝试导入顶级包foo。有关为何在最近的Python版本中更改这种模糊且昂贵的行为的详细信息,请参阅PEP 328:

http://legacy.python.org/dev/peps/pep-0328/

在此背景下,您可以查看strace输出,并在此处注意答案中尚未提及的内容:列出的stat()open()系统调用为不尝试导入模块random,而是导入名为multiprocessing.random 的不存在的模块!

即使random中已列出sys.modules,这也是尝试附加导入的关键原因 - 因为在允许Python 2.6回归之前假设import语句实际上旨在导入random,它必须消除它尝试相对导入multiprocessing.random的可能性,因为import语句出现在multiprocessing.forking子模块的代码。

程序员应该说sys.modules['random'].seed()而不是尝试新的导入来为你节省额外的系统调用。但是,一旦你有机会升级到更新版本的Python,希望你不会因为这种行为而感到困扰。

答案 1 :(得分:1)

这是python导入模块时的作用。没有什么问题。在第一次访问之后,事情将在文件系统缓存中,因此这不太可能导致任何问题。

Python检查PYTHONPATH中的所有文件夹,查找具有给定名称的模块可能具有的所有有效名称。当您运行使用动态库的编译程序时会发生类似的事情 - 动态链接器也将搜索库的各个位置,直到找到它为止。

答案 2 :(得分:1)

好吧所以看起来ThiefMaster是完全正确的,没有任何问题,虽然我仍然不喜欢它,我会避免它。但首先,这就是正在发生的事情。在multiprocessing.forking中会发生以下情况:

class Popen(object):

    def __init__(self, process_obj):
        sys.stdout.flush()
        sys.stderr.flush()
        self.returncode = None

        self.pid = os.fork()
        if self.pid == 0:
            if 'random' in sys.modules:
                import random
                random.seed()
            code = process_obj._bootstrap()
            sys.stdout.flush()
            sys.stderr.flush()
            os._exit(code)

因此,如果'random'在sys.modules中,那么它确实会随机导入并使用它来生成新的随机种子。我想对某些应用程序自动完成这项工作可能会很好,但我当然不会期望它。也许有充分的理由这样做,但我不需要它。

由于我的多处理需求非常简单,我现在只是自己做分叉:

    childpid = os.fork()
    if childpid == 0:
        ...run code...
        os._exit(0)
    else:
        os.waitpid(childpid, 0)

当然这没有导入,所以我没有搜索任何东西。通过继承多处理的适当位并仅删除'import',也可以使搜索消失。我不知道为什么我的笔记本电脑上没有发生搜索,因为我运行的是同一版本的多处理。

答案 3 :(得分:0)

您的操作系统是什么?我猜它是Windows。正如ThiefMaster所说,行为是正常的,但是你在每次循环迭代中得到它的原因可能是因为Windows上的multiprocessing imports the __main__ module。尝试保护if __name__=="__main__"块内的循环。