Cache __builtin__ name lookups so they are performed on module load rather than at...
authorRobert Bradshaw <robertwb@math.washington.edu>
Sat, 14 Apr 2007 11:13:05 +0000 (04:13 -0700)
committerRobert Bradshaw <robertwb@math.washington.edu>
Sat, 14 Apr 2007 11:13:05 +0000 (04:13 -0700)
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
Cython/Compiler/Naming.py
Cython/Compiler/Nodes.py
Cython/Compiler/Options.py
Cython/Compiler/Symtab.py

index 6c47844dab6a7c8183ba151833de6d617516ba2f..674a0392085160ee59243c7993a7b3b58d3ca5ae 100644 (file)
@@ -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
index cd30bae95486122e68b348200a0565f14e697a78..abd5a30b9b2f131b18e3e631956d24938507c60f 100644 (file)
@@ -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_"
index b950a5ca70b59fd93caab2422c6b997d04cb7d50..b53fbfd258afc141d18ac5bbae11a87de9698fcf 100644 (file)
@@ -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.
index c24fcdf0a18127dc3926bb14d415faa5840287be..1ce74656c367549a8db8144a9d16ecd3d4f00661 100644 (file)
@@ -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
index 0efd91da66c8cd213156ea583257faecebde78dc..9ef944fac5ba9af365ad1ae923d3def4c45277fd 100644 (file)
@@ -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 = {