From 6fd9a1952f118fd9d0668f778d2e02717e25c870 Mon Sep 17 00:00:00 2001 From: Robert Bradshaw Date: Sat, 14 Apr 2007 04:13:05 -0700 Subject: [PATCH] Cache __builtin__ name lookups so they are performed on module load rather than at every use. The code "__Pyx_GetName(__pyx_b, __pyx_n_[string])" is performed in several thousand places throughout the sage library, and can be quite expensive (a dictionary lookup, possibly raising an error, etc.) This is redundant as the result will always be the same. I perform the lookup once (on loading the module), then have a pointer to the result for all subsequent use. The most common examples are bool/str/int (both as function calls and in isinstance), True/False, and raisign errors. A side feature is that on loading a module with an illegal __builtin__ name, it will complain at load time rather than at run time. --- Cython/Compiler/ExprNodes.py | 9 +++++++-- Cython/Compiler/Naming.py | 1 + Cython/Compiler/Nodes.py | 36 +++++++++++++++++++++++++++++++++++- Cython/Compiler/Options.py | 3 ++- Cython/Compiler/Symtab.py | 22 +++++++++++++++++++--- 5 files changed, 64 insertions(+), 7 deletions(-) diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 6c47844d..674a0392 100644 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -714,7 +714,10 @@ class NameNode(AtomicExprNode): self.type = self.type.element_ptr_type() if self.entry.is_pyglobal or self.entry.is_builtin: assert self.type.is_pyobject, "Python global or builtin not a Python object" - self.is_temp = 1 + if Options.intern_names and self.entry.is_builtin: + self.is_temp = 0 + else: + self.is_temp = 1 if Options.intern_names: env.use_utility_code(get_name_interned_utility_code) else: @@ -777,7 +780,9 @@ class NameNode(AtomicExprNode): entry = self.entry if entry is None: return # There was an error earlier - if entry.is_pyglobal or entry.is_builtin: + if entry.is_builtin and Options.cache_builtins: + return # Lookup already cached + elif entry.is_pyglobal or entry.is_builtin: if entry.is_builtin: namespace = Naming.builtins_cname else: # entry.is_pyglobal diff --git a/Cython/Compiler/Naming.py b/Cython/Compiler/Naming.py index cd30bae9..abd5a30b 100644 --- a/Cython/Compiler/Naming.py +++ b/Cython/Compiler/Naming.py @@ -8,6 +8,7 @@ pyrex_prefix = "__pyx_" +builtin_prefix = pyrex_prefix + "builtin_" arg_prefix = pyrex_prefix + "arg_" funcdoc_prefix = pyrex_prefix + "doc_" enum_prefix = pyrex_prefix + "e_" diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index b950a5ca..b53fbfd2 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -117,6 +117,14 @@ class BlockNode: code.putln( "static PyObject *%s;" % entry.pystring_cname) + def generate_cached_builtins_decls(self, env, code): + entries = env.builtin_scope().undeclared_cached_entries + if len(entries) > 0: + code.putln("") + for entry in entries: + code.putln("static PyObject *%s;" % entry.cname) + del entries[:] + class ModuleNode(Node, BlockNode): # doc string or None @@ -203,6 +211,7 @@ class ModuleNode(Node, BlockNode): self.generate_const_definitions(env, code) self.generate_interned_name_decls(env, code) self.generate_py_string_decls(env, code) + self.generate_cached_builtins_decls(env, code) self.body.generate_function_definitions(env, code) self.generate_interned_name_table(env, code) self.generate_py_string_table(env, code) @@ -1143,6 +1152,8 @@ class ModuleNode(Node, BlockNode): self.generate_intern_code(env, code) #code.putln("/*--- String init code ---*/") self.generate_string_init_code(env, code) + #code.putln("/*--- Builtin init code ---*/") + self.generate_builtin_init_code(env, code) #code.putln("/*--- Global init code ---*/") self.generate_global_init_code(env, code) #code.putln("/*--- Type import code ---*/") @@ -1208,6 +1219,28 @@ class ModuleNode(Node, BlockNode): Naming.stringtab_cname, code.error_goto(self.pos))) + def generate_builtin_init_code(self, env, code): + # Lookup and cache builtin objects. + if Options.cache_builtins: + for entry in env.builtin_scope().cached_entries: + if Options.intern_names: + #assert entry.interned_cname is not None + code.putln( + '%s = __Pyx_GetName(%s, %s); if (!%s) %s' % ( + entry.cname, + Naming.builtins_cname, + entry.interned_cname, + entry.cname, + code.error_goto(entry.pos))) + else: + code.putln( + '%s = __Pyx_GetName(%s, "%s"); if (!%s) %s' % ( + entry.cname, + Naming.builtins_cname, + self.entry.name, + entry.cname, + code.error_goto(entry.pos))) + def generate_global_init_code(self, env, code): # Generate code to initialise global PyObject * # variables to None. @@ -1771,6 +1804,7 @@ class FuncDefNode(StatNode, BlockNode): # ----- Top-level constants used by this function self.generate_interned_name_decls(lenv, code) self.generate_py_string_decls(lenv, code) + self.generate_cached_builtins_decls(lenv, code) #code.putln("") #code.put_var_declarations(lenv.const_entries, static = 1) self.generate_const_definitions(lenv, code) @@ -1871,7 +1905,7 @@ class FuncDefNode(StatNode, BlockNode): def generate_execution_code(self, code): pass - + class CFuncDefNode(FuncDefNode): # C function definition. diff --git a/Cython/Compiler/Options.py b/Cython/Compiler/Options.py index c24fcdf0..1ce74656 100644 --- a/Cython/Compiler/Options.py +++ b/Cython/Compiler/Options.py @@ -2,6 +2,7 @@ # Pyrex - Compilation-wide options # -intern_names = 1 # Intern global variable and attribute names +intern_names = 1 # Intern global variable and attribute names +cache_builtins = 1 # Perform lookups on builtin names only once embed_pos_in_docstring = 0 diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py index 0efd91da..9ef944fa 100644 --- a/Cython/Compiler/Symtab.py +++ b/Cython/Compiler/Symtab.py @@ -187,9 +187,13 @@ class Scope: # Return the module-level scope containing this scope. return self.outer_scope.global_scope() + def builtin_scope(self): + # Return the module-level scope containing this scope. + return self.outer_scope.builtin_scope() + def declare(self, name, cname, type, pos): # Create new entry, and add to dictionary if - # name is not None. Reports an error if already + # name is not None. Reports a warning if already # declared. dict = self.entries if name and dict.has_key(name): @@ -451,12 +455,24 @@ class BuiltinScope(Scope): cname, type, arg_types, exception_value, exception_check = definition function = CFuncType(type, [CFuncTypeArg("", t, None) for t in arg_types], False, exception_value, exception_check) self.add_cfunction(name, function, None, cname, False) - + self.cached_entries = [] + self.undeclared_cached_entries = [] + def declare_builtin(self, name, pos): entry = self.declare(name, name, py_object_type, pos) - entry.is_builtin = 1 + if Options.cache_builtins: + entry.is_builtin = 1 + entry.is_const = 1 + entry.cname = Naming.builtin_prefix + name + self.cached_entries.append(entry) + self.undeclared_cached_entries.append(entry) + else: + entry.is_builtin = 1 return entry + def builtin_scope(self): + return self + # TODO: built in functions conflict with built in types of same name... builtin_functions = { -- 2.26.2