Python可以配置为缓存sys.path目录查找吗?

时间:2014-08-12 02:53:15

标签: python python-import

我们一直在对通过远程连接运行的Python进行大量基准测试。该程序在异地运行但在现场访问磁盘。我们在RHEL6下运行。我们用strace观看了一个简单的程序。它似乎花了很多时间执行统计和打开文件,看看他们是否在那里。在远程连接上,这是昂贵的。有没有办法配置Python一次读取目录内容并缓存它的列表,以便它不必再次检查?

示例程序test_import.py:

import random
import itertools

我运行了以下命令:

$ strace -Tf python test_import.py >& strace.out
$ grep '/usr/lib64/python2.6/' strace.out | wc
331    3160   35350

所以它在该目录中大约看了331次。其中很多都有如下结果:

stat ( "/usr/lib64/python2.6/posixpath", 0x7fff1b447340 ) = -1 ENOENT ( No such file or directory ) < 0.000009 >

如果它缓存了目录,则不必对文件进行统计以查看它是否存在。

3 个答案:

答案 0 :(得分:9)

您可以通过迁移到Python 3.3或使用替代方案替换标准导入系统来避免这种情况。在我两周前在PyOhio发表的strace演讲中,我讨论了不幸的 O(nm)性能(对于 n 目录和 m < / em>可能的后缀)旧的导入机制;从this slide开始。

我演示了easy_install加上一个Zope驱动的Web框架如何生成73,477个系统调用,只是为了启动和运行足够的导入。

例如,在我的笔记本电脑上的virtualenv中快速安装bottle后,我发现Python需要正好进行1,000次调用才能导入该模块并启动并运行:

$ strace -c -e stat64,open python -c 'import bottle'
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00    0.000179           0      1519      1355 open
  0.00    0.000000           0       475       363 stat64
------ ----------- ----------- --------- --------- ----------------
100.00    0.000179                  1994      1718 total

但是,如果我跳进os.py,我可以添加一个缓存导入器,即使是一个非常天真的实现,也可以将未命中数减少近千个:

$ strace -c -e stat64,open python -c 'import bottle'
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00    0.000041           0       699       581 open
  0.00    0.000000           0       301       189 stat64
------ ----------- ----------- --------- --------- ----------------
100.00    0.000041                  1000       770 total

我选择os.py进行实验,因为strace显示它是Python导入的第一个模块,我们越早安装导入器,Python将拥有的标准库模块就越少在旧的可怕的缓慢政权下进口!

# Put this right below "del _names" in os.py

class CachingImporter(object):

    def __init__(self):
        self.directory_listings = {}

    def find_module(self, fullname, other_path=None):
        filename = fullname + '.py'
        for syspath in sys.path:
            listing = self.directory_listings.get(syspath, None)
            if listing is None:
                try:
                    listing = listdir(syspath)
                except OSError:
                    listing = []
                self.directory_listings[syspath] = listing
            if filename in listing:
                modpath = path.join(syspath, filename)
                return CachingLoader(modpath)

class CachingLoader(object):

    def __init__(self, modpath):
        self.modpath = modpath

    def load_module(self, fullname):
        if fullname in sys.modules:
            return sys.modules[fullname]
        import imp
        mod = imp.new_module(fullname)
        mod.__loader__ = self
        sys.modules[fullname] = mod
        mod.__file__ = self.modpath
        with file(self.modpath) as f:
            code = f.read()
        exec code in mod.__dict__
        return mod

sys.meta_path.append(CachingImporter())

这有粗糙的边缘 - 当然 - 它不会尝试检测.pyc文件或.so文件或Python可能寻找的任何其他扩展。它也不知道__init__.py文件或包内的模块(这需要在lsdir()条目的子目录中运行sys.path。但它至少说明了可以通过类似的方式消除数以千计的额外调用,并展示了您可能尝试的方向。当它找不到一个模块时,正常的导入机制就是开始了。

我想知道PyPI或某个地方是否有一个好的缓存导入器?看起来好像已经在各个商店写过数百次了。我以为Noah Gift已经写过一篇并把它放在博客文章中,但我找不到确认我记忆的链接。

在评论中提及

编辑 @ncoglan ,在PyPI上提供了新的Python 3.3+导入系统的alpha-release backport到Python 2.7:{ {3}} - 不幸的是,看起来提问者仍然坚持2.6。

答案 1 :(得分:1)

我知道这不是你想要的,但无论如何我都会回答:D

sys.path目录没有缓存系统,但zipimport创建.zip文件内模块的索引。该索引用于更快地进行模块查找。

这个解决方案的缺点是你不能将它与二进制模块(例如.so)一起使用,因为在dlopen()中缺乏对Python加载这种模块的支持。

其他问题是CPython解释器在其引导过程中会加载某些模块(例如您的示例中使用的posixpath)。

PS。我希望你能在PythonBrasil上记住我,当时我用迪士尼/皮克斯的纪念品帮你装了一些袋子:D

答案 2 :(得分:1)

除了使用导入程序或zipimport之外,您还应该考虑冻结代码。冻结将大大减少统计调用。

Python的一部分:https://wiki.python.org/moin/Freeze 第三方:http://cx-freeze.readthedocs.org/en/latest/

冻结一个基本的脚本将统计数据从232减少到88.

$ strace -c -e stat64,open python2 hello.py
hello
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00    0.000011           0       232       161 open
------ ----------- ----------- --------- --------- ----------------
100.00    0.000011                   232       161 total
$ strace -c -e stat64,open ./hello
hello
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
  -nan    0.000000           0        88        73 open
------ ----------- ----------- --------- --------- ----------------
100.00    0.000000                    88        73 total

您仍然容易受到sys.path中条目数的影响(但这就是importlib2及其缓存可以帮助您的地方)。