比方说,您有一个项目,该项目在各个地方都有多个级别的文件夹,为了使导入调用更整洁,人们已经为整个项目修改了PYTHONPATH。
这意味着不用说:
from folder1.folder2.folder3 import foo
他们现在可以说
from folder3 import foo
并将folder1 / folder2添加到PYTHONPATH。这里的问题是,如果您继续这样做,并在PYTHONPATH中添加了大量路径,是否会对性能产生明显影响?
要在性能方面增加一些规模感,我要问的是最小毫秒(即:100 ms?500 ms?)
答案 0 :(得分:3)
因此,在系统调用中将看到在PYTHONPATH
中具有许多不同目录与具有深层嵌套的包结构之间的性能折衷。因此,假设我们具有以下目录结构:
bash-3.2$ tree a
a
└── b
└── c
└── d
└── __init__.py
bash-3.2$ tree e
e
├── __init__.py
├── __init__.pyc
└── f
├── __init__.py
├── __init__.pyc
└── g
├── __init__.py
├── __init__.pyc
└── h
├── __init__.py
└── __init__.pyc
我们可以使用这些结构和strace
程序来比较和对比为以下命令生成的系统调用:
strace python -c 'from e.f.g import h'
PYTHONPATH="./a/b/c:$PYTHONPATH" strace python -c 'import d'
因此,这里的权衡实际上是启动时的系统调用,而不是导入时的系统调用。对于PYTHONPATH
中的每个条目,python
首先检查目录是否存在:
stat("./a/b/c", {st_mode=S_IFDIR|0776, st_size=4096, ...}) = 0
stat("./a/b/c", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
如果目录存在(它由右0表示...),则解释器启动时Python将搜索许多模块。对于每个模块,它都会检查:
stat("./a/b/c/site", 0x7ffd900baaf0) = -1 ENOENT (No such file or directory)
open("./a/b/c/site.x86_64-linux-gnu.so", O_RDONLY) = -1 ENOENT (No such file or directory)
open("./a/b/c/site.so", O_RDONLY) = -1 ENOENT (No such file or directory)
open("./a/b/c/sitemodule.so", O_RDONLY) = -1 ENOENT (No such file or directory)
open("./a/b/c/site.py", O_RDONLY) = -1 ENOENT (No such file or directory)
open("./a/b/c/site.pyc", O_RDONLY) = -1 ENOENT (No such file or directory)
每个失败,它继续前进到路径中的下一个条目,以搜索要订购的模块。我的3.5解释器以这种方式查找了25个模块,从而在启动时针对每个新的152
条目产生了一个递增的PYTHONPATH
系统调用。
深度包结构在解释器启动时不会带来任何损失,但是当我们从深度嵌套的包结构中导入时,我们确实会看到差异。作为基准,这是从d/__init__.py
中a/b/c
目录中的PYTHONPATH
的简单导入:
stat("/home/matt/a/b/c/d", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
stat("/home/matt/a/b/c/d/__init__.py", {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
stat("/home/matt/a/b/c/d/__init__", 0x7ffd900ba990) = -1 ENOENT (No such file or directory)
open("/home/matt/a/b/c/d/__init__.x86_64-linux-gnu.so", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/home/matt/a/b/c/d/__init__.so", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/home/matt/a/b/c/d/__init__module.so", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/home/matt/a/b/c/d/__init__.py", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
open("/home/matt/a/b/c/d/__init__.pyc", O_RDONLY) = 4
fstat(4, {st_mode=S_IFREG|0664, st_size=117, ...}) = 0
read(4, "\3\363\r\n\17\3105[c\0\0\0\0\0\0\0\0\1\0\0\0@\0\0\0s\4\0\0\0d\0"..., 4096) = 117
fstat(4, {st_mode=S_IFREG|0664, st_size=117, ...}) = 0
read(4, "", 4096) = 0
close(4) = 0
close(3) = 0
基本上,这是在寻找d
软件包或模块。找到d/__init__.py
后,将其打开,然后打开d/__init__.pyc
并将内容读入内存,然后关闭两个文件。
使用深度嵌套的包结构,我们必须重复此操作3次,这对于每个目录15
个系统调用来说是一件好事,总共可以再进行45个系统调用。虽然这少于通过向我们的PYTHONPATH
添加路径所添加的调用次数的一半,但read
调用可能比其他系统调用更耗时(或需要更多系统调用)取决于__init__.py
文件的大小。
考虑到所有这些因素,这些差异几乎肯定不足以抵消所需解决方案的设计优势。
如果您的进程是长期运行的(例如网络应用),而不是短暂的,则尤其如此。
我们可以通过以下方式减少系统调用:
PYTHONPATH
条目.pyc
文件,以免将其写入我们可以通过删除py
文件来大大提高性能,这样就不会与PYC文件一起读取这些文件用于调试目的……但这对我来说似乎太过分了。
希望这很有用,可能比必要的潜水要深得多。
答案 1 :(得分:2)
除非您在慢速驱动器位置上附加路径,否则不会对性能产生影响。但这可能产生的影响可以忽略不计。
通过在PYTHONPATH
上添加太多位置,您最有可能遇到的问题是模块冲突,其中不同的位置具有相同的模块但版本不同。
答案 2 :(得分:2)
这是有史以来最可怕的想法。
首先,因为它使代码更难阅读和推理。等待,“ folder3”,这是从哪里来的?另外,因为如果两个包用相同的名称定义了一个子模块,那么导入时将获得的子模块取决于PYTHONPATH中的顺序。并且一旦重新排列了PYTHONPATH,以便从“ packageX”而不是“ packageY”获得“ moduleX”,则有人在“ packageX”下添加了一个“ moduleY”,这使“ moduleY”与“ packageY”不符。然后你就被搞砸了...
但是那只是不那么令人讨厌的部分...
如果您有一个使用from folder1.folder2.folder3 import foo
的模块,而另一个使用from folder3 import foo
,则在sys.modules
中最终得到两个不同的模块对象(模块的两个实例)-以及所有定义的对象在这些模块中,它们也是重复的(两个实例,ID不同),现在您有了一个程序,只要涉及到身份测试,它就会以最不稳定的方式开始运行。而且由于异常处理依赖于身份,因此如果foo
是一个异常,则取决于模块的哪个实例引发了该实例,以及哪个实例试图捕获该异常,因此该测试将成功或失败而没有可识别的模式。
祝您调试顺利...