diff --git a/devito/ir/clusters/cluster.py b/devito/ir/clusters/cluster.py index 172567bb8e..c4b98ca4f9 100644 --- a/devito/ir/clusters/cluster.py +++ b/devito/ir/clusters/cluster.py @@ -248,6 +248,14 @@ def is_phase_marker(self): def is_critical_region(self): return self._is_type(CriticalRegion) + @cached_property + def is_thread_rendezvous(self): + """ + True if it contains a synchronization point at which all participating + threads must arrive before any may proceed. + """ + return self.is_thread_pool_sync and not self.is_thread_wait + @cached_property def is_thread_pool_sync(self): return self._is_type(ThreadPoolSync) @@ -655,6 +663,12 @@ def __hash__(self): def concatenate(cls, *cgroups): return list(chain(*cgroups)) + def rebuild(self, **kwargs): + clusters = kwargs.get('clusters', self) + ispace = kwargs.get('ispace', self.ispace) + + return self.__class__(clusters, ispace=ispace) + @cached_property def exprs(self): return flatten(c.exprs for c in self) @@ -663,6 +677,10 @@ def exprs(self): def scope(self): return Scope(exprs=self.exprs) + @cached_property + def functions(self): + return self.scope.functions + @cached_property def ispace(self): return self._ispace diff --git a/devito/passes/clusters/aliases.py b/devito/passes/clusters/aliases.py index ad40063a82..ec549d89e1 100644 --- a/devito/passes/clusters/aliases.py +++ b/devito/passes/clusters/aliases.py @@ -13,6 +13,7 @@ maximum, minimum, normalize_properties, relax_properties, unbounded, vmax, vmin ) from devito.passes.clusters.cse import _cse +from devito.passes.clusters.utils import expose_tuning_knobs from devito.symbolics import ( Uxmapper, estimate_cost, retrieve_functions, reuse_if_untouched, search, sympy_dtype, uxreplace @@ -1080,27 +1081,6 @@ def optimize_clusters_msds(clusters): return processed -def expose_tuning_knobs(clusters, sregistry): - """ - Replace all pre-existing BlockDimensions with fresh ones, to enable - separate tuning for the CIRE-generated temporaries. - """ - # Create the new BlockDimensions - callback = lambda i: sregistry.make_name(prefix=i) - - mapper = {} - for d in set().union(*[c.used_dimensions for c in clusters]): - if d.is_Block: - mapper.update(d._rebuild_hierarchy(callback)) - - if not mapper: - return clusters - - processed = [c.subs(mapper) for c in clusters] - - return processed - - def pick_best(variants): """ Return the variant with the best theoretical performance. diff --git a/devito/passes/clusters/misc.py b/devito/passes/clusters/misc.py index 68b982eedc..44ce1653c3 100644 --- a/devito/passes/clusters/misc.py +++ b/devito/passes/clusters/misc.py @@ -1,10 +1,10 @@ from itertools import groupby, product from devito.ir.clusters import Queue, cluster_pass -from devito.ir.support import SEPARABLE, SEQUENTIAL, Scope +from devito.ir.support import SEPARABLE, Scope from devito.passes.clusters.utils import in_critical_region from devito.symbolics import pow_to_mul -from devito.tools import Stamp, flatten, frozendict, timed_pass +from devito.tools import Stamp, flatten, timed_pass from devito.types import Hyperplane __all__ = ['Lift', 'fission', 'optimize_hyperplanes', 'optimize_pows'] @@ -123,7 +123,7 @@ def callback(self, clusters, prefix): d = prefix[-1].dim # Do not waste time if definitely illegal - if any(SEQUENTIAL in c.properties[d] for c in clusters): + if any(c.properties.is_sequential(d) for c in clusters): return clusters # Do not waste time if definitely nothing to do @@ -132,21 +132,21 @@ def callback(self, clusters, prefix): # Analyze and abort if fissioning would break a dependence scope = Scope(flatten(c.exprs for c in clusters)) - if any(d._defines & dep.cause or dep.is_reduce(d) for dep in scope.d_all_gen()): + if any(d._defines & dep.cause or dep.is_reduce(d) or dep.is_local + for dep in scope.d_all_gen()): return clusters processed = [] - for (it, guards), g in groupby(clusters, key=lambda c: self._key(c, prefix)): + for it, g in groupby(clusters, key=lambda c: self._key(c, prefix)): group = list(g) try: - test0 = any(SEQUENTIAL in c.properties[it.dim] for c in group) + test0 = any(c.properties.is_sequential(it.dim) for c in group) except AttributeError: - # `it` is None because `c`'s IterationSpace has no `d` Dimension, - # hence `key = (it, guards) = (None, guards)` + # `it` is None because `c`'s IterationSpace has no `d` Dimension test0 = True - if test0 or guards: + if test0: # Heuristic: no gain from fissioning if unable to ultimately # increase the number of collapsible iteration spaces, hence give up processed.extend(group) @@ -161,14 +161,10 @@ def callback(self, clusters, prefix): def _key(self, c, prefix): try: index = len(prefix) - dims = tuple(i.dim for i in prefix) - it = c.ispace[index] - guards = frozendict({d: v for d, v in c.guards.items() if d in dims}) - - return (it, guards) + return it except IndexError: - return (None, c.guards) + return None @timed_pass() diff --git a/devito/passes/clusters/utils.py b/devito/passes/clusters/utils.py index ff9cc2d95e..babf8689b5 100644 --- a/devito/passes/clusters/utils.py +++ b/devito/passes/clusters/utils.py @@ -2,7 +2,8 @@ from devito.tools import as_tuple from devito.types import CriticalRegion, Eq, Symbol -__all__ = ['in_critical_region', 'is_memcpy', 'make_critical_sequence'] +__all__ = ['expose_tuning_knobs', 'in_critical_region', 'is_memcpy', + 'make_critical_sequence'] def is_memcpy(expr): @@ -50,3 +51,24 @@ def in_critical_region(cluster, clusters): elif c.is_critical_region: maybe_found = c return None + + +def expose_tuning_knobs(clusters, sregistry): + """ + Replace all pre-existing BlockDimensions with fresh ones, to enable + separate tuning for the CIRE-generated temporaries. + """ + # Create the new BlockDimensions + callback = lambda i: sregistry.make_name(prefix=i) + + mapper = {} + for d in set().union(*[c.used_dimensions for c in clusters]): + if d.is_Block: + mapper.update(d._rebuild_hierarchy(callback)) + + if not mapper: + return clusters + + processed = [c.subs(mapper) for c in clusters] + + return processed diff --git a/tests/test_fission.py b/tests/test_fission.py index 60464d1743..0da06b34a1 100644 --- a/tests/test_fission.py +++ b/tests/test_fission.py @@ -80,6 +80,25 @@ def test_nofission_as_illegal(): assert_structure(op, ['t,x,y', 't,x,y'], 't,x,y,y') +def test_nofission_local_scalar_dependence(): + """ + Test there's no fission across a local scalar dependence. + """ + grid = Grid(shape=(3, 3)) + time = grid.time_dim + x, y = grid.dimensions + + g = Function(name='g', grid=grid, dtype=np.int32, space_order=0) + h = Function(name='h', grid=grid, space_order=0) + + eqns = [Eq(y.symbolic_max, g[x, 0], implicit_dims=(time, x)), + Eq(h, y, implicit_dims=(time, x, y))] + + op = Operator(eqns, opt='fission') + + assert_structure(op, ['t,x', 't,x,y'], 't,x,y') + + def test_fission_partial(): """ Test there's no fission if no increase in number of collapsible loops.