Pau*_*ndt 12 python python-import
我们一直在对通过远程连接运行的Python进行大量基准测试.该程序在异地运行但在现场访问磁盘.我们在RHEL6下运行.我们用strace观看了一个简单的程序.它似乎花了很多时间执行stat和open文件以查看它们是否在那里.在远程连接上,这是昂贵的.有没有办法配置Python一次读取目录内容并缓存它的列表,所以它不必再检查它?
示例程序test_import.py:
import random
import itertools
Run Code Online (Sandbox Code Playgroud)
我运行了以下命令:
$ strace -Tf python test_import.py >& strace.out
$ grep '/usr/lib64/python2.6/' strace.out | wc
331 3160 35350
Run Code Online (Sandbox Code Playgroud)
所以它在该目录中大约看了331次.其中很多都有如下结果:
stat ( "/usr/lib64/python2.6/posixpath", 0x7fff1b447340 ) = -1 ENOENT ( No such file or directory ) < 0.000009 >
Run Code Online (Sandbox Code Playgroud)
如果它缓存了目录,则不必统计文件以查看它是否存在.
您可以通过迁移到Python 3.3或使用替代方案替换标准导入系统来避免这种情况.在strace我两周前在PyOhio发表的演讲中,我讨论了旧的导入机制的不幸的O(nm)性能(对于n个目录和m个可能的后缀); 从这张幻灯片开始.
我演示了如何easy_install加上一个Zope驱动的Web框架生成73,477个系统调用,只需要进行足够的导入即可启动和运行.
bottle例如,在我的笔记本电脑上快速安装virtualenv之后,我发现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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
我选择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())
Run Code Online (Sandbox Code Playgroud)
当然,这有粗糙的边缘 - 它不会尝试检测.pyc文件或.so文件或Python可能寻找的任何其他扩展.它也不知道__init__.py文件或包内的模块(这需要lsdir()在sys.path条目的子目录中运行).但它至少说明了可以通过类似的方式消除数以千计的额外调用,并展示了您可能尝试的方向.当它找不到一个模块时,正常的导入机制就是开始了.
我想知道PyPI或某个地方是否有一个好的缓存导入器?看起来好像已经在各个商店写过数百次了.我以为Noah Gift已经写了一篇文章,并把它放在博客文章或其他东西,但我找不到一个确认我的记忆的链接.
编辑:正如@ncoglan在评论中提到的那样,PyPI上有一个新的Python 3.3+导入系统的alpha版本backport到Python 2.7:http://pypi.python.org/pypi/importlib2 - 不幸的是它看起来像提问者仍然坚持2.6.
我知道这不完全是您要找的,但无论如何我都会回答:D
目录没有缓存系统sys.path,但会在文件zipimport内创建模块的索引.zip。该索引用于使模块查找更快。
该解决方案的缺点是您不能将其与二进制模块(例如.so)一起使用,因为Python 缺乏支持dlopen()来加载此类模块。
另一个问题是某些模块(例如posixpath示例中使用的模块)是由 CPython 解释器在引导过程中加载的。
附言。我希望你记得我在 PythonBrasil 时帮你装满迪士尼/皮克斯纪念品的袋子:D
| 归档时间: |
|
| 查看次数: |
742 次 |
| 最近记录: |