Python多处理scipy:过多的文件系统’stat’和’open’尝试

我在
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的版本也不是那么容易,尽管我可以让集群管理员在需要时构建更新的版本.

这可能是问题吗?

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

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

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

但关键在于从系统调用的角度来看,上面的import语句应该是一个no-op,因为如果一个模块名已经作为sys.modules字典中的一个键存在,那么import只返回它在没有尝试从文件系统加载任何内容的情况下找到的值:

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

因此,上面引用的if语句特别试图防止在随机模块甚至没有被加载的情况下额外导入的费用.当您在没有加载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()系统调用不会尝试随机导入模块,而是命名不存在的模块multiprocessing.random!

这是尝试额外导入的关键原因,即使随机已经在sys.modules中列出 – 因为在允许Python 2.6回到导入语句真正旨在随机导入的假设之前,它必须消除它是尝试相对导入multiprocessing.random的可能性,因为import语句出现在multiprocessing.forking子模块的代码中.

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

点赞