On Mon, 9 Nov 2015 09:35 am, BartC wrote: > Suppose this is the python program: > > import m > a=10 > b=20 > c=30 > m.f() > > The set of global names the compiler knows will be ("m","a","b","c").
Wrong. Up to the line "c=30", the set of names the compiler can infer are m, a, b and c. Once the line "m.f()" executes, *all bets are off*. The compiler can no longer infer *anything* about those names. In principle, m.f may have reached into the globals and deleted *any* of the names, including itself. > I don't believe code can remove these names (that would cause problems). Of course names can be removed. There's even a built-in statement to do so: "del". Deleting names from namespaces is a moderately common thing to do. > You say the set of global names can be added to - after this lot, but I > can't see that the first four are going to change. Of course they can change. > Therefore these names could be referred to by index. Perhaps. But that adds significant complexity to the compiler, and the performance benefits *in practice* may not be as good as you imagine. After all, there is usually far more to real programs than just getting and setting names. Nevertheless, such indexed name lookups do have the potential to save time, and in fact that's what CPython already does with local variables. We can get a *rough* indication of how big a difference this micro-optimization makes by disabling it. Unfortunately, this only works in Python 2 -- in Python 3, you can no longer defeat the local variable optimization. def unopt(): from math import * # Defeats the local variable optimization. x = sin; x = cos; x = tan; x = exp; x = pi x = e; x = trunc; x = log; x = hypot; x = sqrt return def opt(): from math import sin, cos, tan, exp, pi, e, trunc, log, hypot, sqrt x = sin; x = cos; x = tan; x = exp; x = pi x = e; x = trunc; x = log; x = hypot; x = sqrt return from timeit import Timer t1 = Timer("unopt()", setup="from __main__ import unopt") t2 = Timer("opt()", setup="from __main__ import opt") # Best of five trials of 1,000,000 calls each. print min(t1.repeat(repeat=5)) print min(t2.repeat(repeat=5)) When I run this code, I get 16.5607659817 seconds for unopt, and 3.58955097198 seconds for opt. That's a significant difference. But remember that not all of that difference is due to the name lookups (unopt also imports more stuff), and also remember that this is a pretty unrealistic benchmark. Real functions do more than just look up a variable name over and over. > Attributes are harder because they are more of a free-for-all, but > suppose you do have m.f(). > > "m" is easy, it's entry 0 in the global table for this module, so no > lookup-by-name is needed. You examine it, and it's a module. > > It's now necessary to find f within the global table for m. This is > where it gets tricky if you want to avoid a lookup. And I expect you > will say that any code can randomly add names within the global table of > m. Of course it can. It can even add names to builtins. Oh, and you don't know that m is a module until you inspect it at runtime. It could be any object. > But bear with me. Suppose the interpreter were to maintain a table for > each static (not runtime) attribute encountered in the source code. The > compile can produce empty tables for the attributes it's seen. In this > case the table for "f" would be empty: (). The compiler will also > produce a list of such tables for all attributes. I have no idea how well this implementation would work. My guess is that if you look at, say, Nuitka, it may perform optimizations like this. (The aim of Nuitka is to be a static optimizing compiler.) PyPy may do things like this too, using a JIT optimizing compiler. PyPy is capable of far more than such simple optimizations. As I have argued all along, it is certainly not true that Python's dynamicism prevents all such optimizations. It just makes them harder. -- Steven -- https://mail.python.org/mailman/listinfo/python-list