Which import mechanism is faster?

拈花ヽ惹草 提交于 2021-02-07 20:51:31

问题


1:

%%timeit -n 1000000
import math
math.sin(math.pi/2)

1000000 loops, best of 3: 284 ns per loop

2:

%%timeit -n 1000000
from math import sin, pi
sin(pi/2)

1000000 loops, best of 3: 1.01 µs per loop

回答1:


There you are timing two different statements - and indeed, "common sense" has it that attribute lookup as in the first snippet should have a higher overhead than using local (in this case, global) scoped names.

What is happening though, is that when one does from math import sin, pi, those two names have to be binded in the global scope where the import statement is, and in current Python it turns out the overhead for this binding far surpasses the overhead of binding just the name math in the other example.

This difference is more likely due to different code-paths inside the import machinery itself, when using from <module> import <name> form instead of import <module> - keep in mind the times measured are really tiny in both cases. And if you were calling sin as little as 3 times in the measured code, the difference would be supplanted already.




回答2:


The question is a useful reminder that a naive approach to benchmarking is unlikely to yield useful results. There are enough mistaken assumptions (as well as some answers) in the comments that the answer should perhaps be summarised.

First and foremost, importing individual names from a module does not mean the interpreter only loads a part of the library. The difference between the different forms of import is solely in how the imported entities appear in the importing namespace.

  • import module results in the named module being imported (and added to sys.modules). It is then bound to the name module in the local namespace

  • import module as name is exactly the same except it binds the newly-imported module as name rather than that of the module

  • from module import name again imports the module and adds it to sys.modules, but does not bind it in the local namespace. Instead name in the local namespace is bound to the same object that name is bound to in the module.

  • from module import name as name2 works in the same way, but binds to name2 in the local namespace the object that is bound to name in the module.

Secondly, the first (and only the first) import of a module requires the interpreter to load the module's code (and possibly compile the .py file if no corresponding .pyc can be found). Thereafter its presence is the sys.modules dictionary inhibits further imports - there's no point reloading code already in memory.

A "better" benchmark might therefore be (this code is untested: caveat emptor)

import math
from math import sin, pi

followed by

%%timeit -n 1000000
__main__.math.sin(__main__.math.pi/2)

and

%%timeit -n 1000000
__main__.sin(__main__.pi/2)

Casting the code in this way removes the first import from the timing, and leaves the only difference in timing down to the additional length of time it takes to implement the extra namespace lookups in the first test. While that is a useful thing to learn, it did not directly answer your question, since the differences in timing were not related to the import mechanism.

Finally, it's wise not to obsess about such timings (I understand this was an investigation, and there's nothing wrong with that). The time to look at optimisation is when you already have a working solution, and only when it needs to run faster!

A sound understanding of Python's import mechanisms will stand you in good stead as your Python skills develop.




回答3:


Taking the sin call out of the equation, it would seem that the IMPORT_FROM op is just more expensive.

>>> %%timeit -n 1000000
... import math; sin = math.sin; del math
...
144 ns ± 10.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
>>> %%timeit -n 1000000
... from math import sin
...
788 ns ± 0.824 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Disassembly of the two different kinds of imports shows the extra CPython opcodes for IMPORT_FROM:

>>> import dis
>>> dis.dis("import modname")
  1           0 LOAD_CONST               0 (0)
              2 LOAD_CONST               1 (None)
              4 IMPORT_NAME              0 (modname)
              6 STORE_NAME               0 (modname)
              8 LOAD_CONST               1 (None)
             10 RETURN_VALUE
>>> dis.dis("from modname import funcname")
  1           0 LOAD_CONST               0 (0)
              2 LOAD_CONST               1 (('funcname',))
              4 IMPORT_NAME              0 (modname)
              6 IMPORT_FROM              1 (funcname)
              8 STORE_NAME               1 (funcname)
             10 POP_TOP
             12 LOAD_CONST               2 (None)
             14 RETURN_VALUE

Please do not take this as a recommendation to rewrite all your from imports in order to shave off nanoseconds.



来源:https://stackoverflow.com/questions/57483624/which-import-mechanism-is-faster

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!