From 8d7efe0bde47b784128f4e81c9bec3b39da77e1e Mon Sep 17 00:00:00 2001
From: Andreas Kloeckner <inform@tiker.net>
Date: Mon, 10 Aug 2015 23:05:14 -0500
Subject: [PATCH] Ensure performance-sensitive identifiers are interned

---
 loopy/frontend/fortran/translator.py |  7 ++++--
 loopy/kernel/__init__.py             | 22 ++++++++++++++----
 loopy/kernel/creation.py             | 33 +++++++++++++++++++--------
 loopy/kernel/data.py                 | 34 +++++++++++++++++++++++++++-
 loopy/kernel/tools.py                |  2 +-
 loopy/tools.py                       | 10 ++++++++
 6 files changed, 91 insertions(+), 17 deletions(-)

diff --git a/loopy/frontend/fortran/translator.py b/loopy/frontend/fortran/translator.py
index 97005cab6..8293035a9 100644
--- a/loopy/frontend/fortran/translator.py
+++ b/loopy/frontend/fortran/translator.py
@@ -25,6 +25,7 @@ THE SOFTWARE.
 import re
 
 import six
+from six.moves import intern
 
 import loopy as lp
 import numpy as np
@@ -221,7 +222,7 @@ class F2LoopyTranslator(FTreeWalkerBase):
     def add_expression_instruction(self, lhs, rhs):
         scope = self.scope_stack[-1]
 
-        new_id = "insn%d" % self.insn_id_counter
+        new_id = intern("insn%d" % self.insn_id_counter)
         self.insn_id_counter += 1
 
         if self.auto_dependencies and scope.previous_instruction_id:
@@ -447,7 +448,7 @@ class F2LoopyTranslator(FTreeWalkerBase):
     def map_IfThen(self, node):
         scope = self.scope_stack[-1]
 
-        cond_name = "loopy_cond%d" % self.condition_id_counter
+        cond_name = intern("loopy_cond%d" % self.condition_id_counter)
         self.condition_id_counter += 1
         assert cond_name not in scope.type_map
 
@@ -543,6 +544,8 @@ class F2LoopyTranslator(FTreeWalkerBase):
             loop_var_suffix += 1
             loopy_loop_var = loop_var + "_%d" % loop_var_suffix
 
+        loopy_loop_var = intern(loopy_loop_var)
+
         # }}}
 
         space = isl.Space.create_from_names(isl.DEFAULT_CONTEXT,
diff --git a/loopy/kernel/__init__.py b/loopy/kernel/__init__.py
index 16695e0ad..121a516c1 100644
--- a/loopy/kernel/__init__.py
+++ b/loopy/kernel/__init__.py
@@ -351,7 +351,18 @@ class LoopKernel(RecordWithoutPickling):
 
         for id_str in generate_unique_names(based_on):
             if id_str not in used_ids:
-                return id_str
+                return intern(id_str)
+
+    def all_group_names(self):
+        result = set()
+        for insn in self.instructions:
+            result.update(insn.groups)
+            result.update(insn.conflicts_with_groups)
+
+        return frozenset(result)
+
+    def get_group_name_generator(self):
+        return _UniqueVarNameGenerator(self.all_group_names())
 
     def get_var_descriptor(self, name):
         try:
@@ -577,7 +588,8 @@ class LoopKernel(RecordWithoutPickling):
     def all_inames(self):
         result = set()
         for dom in self.domains:
-            result.update(dom.get_var_names(dim_type.set))
+            result.update(
+                    intern(n) for n in dom.get_var_names(dim_type.set))
         return frozenset(result)
 
     @memoize_method
@@ -588,7 +600,8 @@ class LoopKernel(RecordWithoutPickling):
         for dom in self.domains:
             result.update(set(dom.get_var_names(dim_type.param)) - all_inames)
 
-        return frozenset(result)
+        from loopy.tools import intern_frozenset_of_ids
+        return intern_frozenset_of_ids(result)
 
     def outer_params(self, domains=None):
         if domains is None:
@@ -600,7 +613,8 @@ class LoopKernel(RecordWithoutPickling):
             all_inames.update(dom.get_var_names(dim_type.set))
             all_params.update(dom.get_var_names(dim_type.param))
 
-        return all_params-all_inames
+        from loopy.tools import intern_frozenset_of_ids
+        return intern_frozenset_of_ids(all_params-all_inames)
 
     @memoize_method
     def all_insn_inames(self):
diff --git a/loopy/kernel/creation.py b/loopy/kernel/creation.py
index e891f0626..556bc3a74 100644
--- a/loopy/kernel/creation.py
+++ b/loopy/kernel/creation.py
@@ -26,6 +26,7 @@ THE SOFTWARE.
 
 
 import numpy as np
+from loopy.tools import intern_frozenset_of_ids
 from loopy.symbolic import IdentityMapper, WalkMapper
 from loopy.kernel.data import (
         InstructionBase, ExpressionInstruction, SubstitutionRule)
@@ -33,7 +34,7 @@ import islpy as isl
 from islpy import dim_type
 
 import six
-from six.moves import range, zip
+from six.moves import range, zip, intern
 
 import re
 import sys
@@ -216,7 +217,7 @@ def parse_insn(insn):
                     opt_value = option[equal_idx+1:].strip()
 
                 if opt_key == "id":
-                    insn_id = opt_value
+                    insn_id = intern(opt_value)
                 elif opt_key == "id_prefix":
                     insn_id = UniqueName(opt_value)
                 elif opt_key == "priority":
@@ -235,17 +236,18 @@ def parse_insn(insn):
                         insn_deps_is_final = True
                         opt_value = (opt_value[1:]).strip()
 
-                    insn_deps = frozenset(dep.strip() for dep in opt_value.split(":")
+                    insn_deps = frozenset(
+                            intern(dep.strip()) for dep in opt_value.split(":")
                             if dep.strip())
 
                 elif opt_key == "groups":
                     insn_groups = frozenset(
-                            grp.strip() for grp in opt_value.split(":")
+                            intern(grp.strip()) for grp in opt_value.split(":")
                             if grp.strip())
 
                 elif opt_key == "conflicts":
                     conflicts_with_groups = frozenset(
-                            grp.strip() for grp in opt_value.split(":")
+                            intern(grp.strip()) for grp in opt_value.split(":")
                             if grp.strip())
 
                 elif opt_key == "inames":
@@ -255,10 +257,10 @@ def parse_insn(insn):
                     else:
                         forced_iname_deps_is_final = True
 
-                    forced_iname_deps = frozenset(opt_value.split(":"))
+                    forced_iname_deps = intern_frozenset_of_ids(opt_value.split(":"))
 
                 elif opt_key == "if":
-                    predicates = frozenset(opt_value.split(":"))
+                    predicates = intern_frozenset_of_ids(opt_value.split(":"))
 
                 elif opt_key == "tags":
                     tags = tuple(
@@ -284,7 +286,10 @@ def parse_insn(insn):
                     "be variable or subscript" % lhs)
 
         return ExpressionInstruction(
-                    id=insn_id,
+                    id=(
+                        intern(insn_id)
+                        if not isinstance(insn_id, (type(None), UniqueName))
+                        else None),
                     insn_deps=insn_deps,
                     insn_deps_is_final=insn_deps_is_final,
                     groups=insn_groups,
@@ -326,7 +331,17 @@ def parse_insn(insn):
 
 def parse_if_necessary(insn, defines):
     if isinstance(insn, InstructionBase):
-        yield insn, []
+        yield insn.copy(
+                id=intern(insn.id) if insn.id is not None else None,
+                insn_deps=frozenset(intern(dep) for dep in insn.insn_deps),
+                groups=frozenset(intern(grp) for grp in insn.groups),
+                conflicts_with_groups=frozenset(
+                    intern(grp) for grp in insn.conflicts_with_groups),
+                forced_iname_deps=frozenset(
+                    intern(iname) for iname in insn.forced_iname_deps),
+                predicates=frozenset(
+                    intern(pred) for pred in insn.predicates),
+                ), []
         return
     elif not isinstance(insn, str):
         raise TypeError("Instructions must be either an Instruction "
diff --git a/loopy/kernel/data.py b/loopy/kernel/data.py
index b88929358..2d9faa4cf 100644
--- a/loopy/kernel/data.py
+++ b/loopy/kernel/data.py
@@ -25,6 +25,7 @@ THE SOFTWARE.
 """
 
 
+from six.moves import intern
 import numpy as np
 from pytools import Record, memoize_method
 from loopy.kernel.array import ArrayBase
@@ -185,6 +186,8 @@ def parse_tag(tag):
 
 class KernelArgument(Record):
     def __init__(self, **kwargs):
+        kwargs["name"] = intern(kwargs.pop("name"))
+
         dtype = kwargs.pop("dtype", None)
 
         if isinstance(dtype, np.dtype):
@@ -339,7 +342,7 @@ class TemporaryVariable(ArrayBase):
         if base_indices is None:
             base_indices = (0,) * len(shape)
 
-        ArrayBase.__init__(self, name=name,
+        ArrayBase.__init__(self, name=intern(name),
                 dtype=dtype, shape=shape,
                 dim_tags=dim_tags, order="C",
                 base_indices=base_indices, is_local=is_local,
@@ -512,6 +515,9 @@ class InstructionBase(Record):
             forced_iname_deps_is_final, forced_iname_deps, priority,
             boostable, boostable_into, predicates, tags):
 
+        if insn_deps is None:
+            insn_deps = frozenset()
+
         if groups is None:
             groups = frozenset()
 
@@ -531,6 +537,17 @@ class InstructionBase(Record):
         if tags is None:
             tags = ()
 
+        # Periodically reenable these and run the tests to ensure all
+        # performance-relevant identifiers are interned.
+        #
+        # from loopy.tools import is_interned
+        # assert is_interned(id)
+        # assert all(is_interned(dep) for dep in insn_deps)
+        # assert all(is_interned(grp) for grp in groups)
+        # assert all(is_interned(grp) for grp in conflicts_with_groups)
+        # assert all(is_interned(iname) for iname in forced_iname_deps)
+        # assert all(is_interned(pred) for pred in predicates)
+
         assert isinstance(forced_iname_deps, frozenset)
         assert isinstance(insn_deps, frozenset) or insn_deps is None
         assert isinstance(groups, frozenset)
@@ -650,6 +667,21 @@ class InstructionBase(Record):
 
     # }}}
 
+    def __setstate__(self, val):
+        super(InstructionBase, self).__setstate__(val)
+
+        from loopy.tools import intern_frozenset_of_ids
+
+        self.id = intern(self.id)
+        self.insn_deps = intern_frozenset_of_ids(self.insn_deps)
+        self.groups = intern_frozenset_of_ids(self.groups)
+        self.conflicts_with_groups = (
+                intern_frozenset_of_ids(self.conflicts_with_groups))
+        self.forced_iname_deps = (
+                intern_frozenset_of_ids(self.forced_iname_deps))
+        self.predicates = (
+                intern_frozenset_of_ids(self.predicates))
+
 # }}}
 
 
diff --git a/loopy/kernel/tools.py b/loopy/kernel/tools.py
index b59c40731..e811935bb 100644
--- a/loopy/kernel/tools.py
+++ b/loopy/kernel/tools.py
@@ -204,7 +204,7 @@ def find_all_insn_inames(kernel):
                     # current inames refer to.
 
                     if par in kernel.all_inames():
-                        inames_new.add(par)
+                        inames_new.add(intern(par))
 
                     # If something writes the bounds of a loop in which I'm
                     # sitting, I had better be in the inames that the writer is
diff --git a/loopy/tools.py b/loopy/tools.py
index e734417d6..861d15568 100644
--- a/loopy/tools.py
+++ b/loopy/tools.py
@@ -30,6 +30,7 @@ from loopy.symbolic import WalkMapper as LoopyWalkMapper
 from pymbolic.mapper.persistent_hash import (
         PersistentHashWalkMapper as PersistentHashWalkMapperBase)
 import six  # noqa
+from six.moves import intern
 
 
 if six.PY2:
@@ -216,4 +217,13 @@ def remove_common_indentation(code, require_leading_newline=True,
 
 # }}}
 
+
+def is_interned(s):
+    return s is None or intern(s) is s
+
+
+def intern_frozenset_of_ids(fs):
+    return frozenset(intern(s) for s in fs)
+
+
 # vim: foldmethod=marker
-- 
GitLab