def Toffsets(self): return LabeledVector.transpose(*self.offsets)
def Toffsets(self): return [LabeledVector.transpose(*i) for i in zip(*[i.offsets for i in self])]
def collect(exprs, min_storage, ignore_collected): """ Find groups of aliasing expressions. We shall introduce the following (loose) terminology: * A ``terminal`` is the leaf of a mathematical operation. Terminals can be numbers (n), literals (l), or Indexeds (I). * ``R`` is the relaxation operator := ``R(n) = n``, ``R(l) = l``, ``R(I) = J``, where ``J`` has the same base as ``I`` but with all offsets stripped away. For example, ``R(a[i+2,j-1]) = a[i,j]``. * A ``relaxed expression`` is an expression in which all of the terminals are relaxed. Now we define the concept of aliasing. We say that an expression A aliases an expression B if: * ``R(A) == R(B)`` * all pairwise Indexeds in A and B access memory locations at a fixed constant distance along each Dimension. For example, consider the following expressions: * a[i+1] + b[i+1] * a[i+1] + b[j+1] * a[i] + c[i] * a[i+2] - b[i+2] * a[i+2] + b[i] * a[i-1] + b[i-1] Out of the expressions above, the following alias to `a[i] + b[i]`: * a[i+1] + b[i+1] : same operands and operations, distance along i: 1 * a[i-1] + b[i-1] : same operands and operations, distance along i: -1 Whereas the following do not: * a[i+1] + b[j+1] : because at least one index differs * a[i] + c[i] : because at least one of the operands differs * a[i+2] - b[i+2] : because at least one operation differs * a[i+2] + b[i] : because the distances along ``i`` differ (+2 and +0) """ # Find the potential aliases found = [] for expr in exprs: if expr.lhs.is_Indexed or expr.is_Increment: continue indexeds = retrieve_indexed(expr.rhs) bases = [] offsets = [] for i in indexeds: ii = IterationInstance(i) if ii.is_irregular: break base = [] offset = [] for e, ai in zip(ii, ii.aindices): if q_constant(e): base.append(e) else: base.append(ai) offset.append((ai, e - ai)) bases.append(tuple(base)) offsets.append(LabeledVector(offset)) if indexeds and len(bases) == len(indexeds): found.append(Candidate(expr, indexeds, bases, offsets)) # Create groups of aliasing expressions mapper = OrderedDict() unseen = list(found) while unseen: c = unseen.pop(0) group = [c] for u in list(unseen): # Is the arithmetic structure of `c` and `u` equivalent ? if not compare_ops(c.expr, u.expr): continue # Is `c` translated w.r.t. `u` ? if not c.translated(u): continue group.append(u) unseen.remove(u) group = Group(group) # Apply callback to heuristically discard groups if ignore_collected(group): continue if min_storage: k = group.dimensions_translated else: k = group.dimensions mapper.setdefault(k, []).append(group) aliases = Aliases() for _groups in list(mapper.values()): groups = list(_groups) while groups: # For each Dimension, determine the Minimum Intervals (MI) spanning # all of the Groups diameters # Example: x's largest_diameter=2 => [x[-2,0], x[-1,1], x[0,2]] # Note: Groups that cannot evaluate their diameter are dropped mapper = defaultdict(int) for g in list(groups): try: mapper.update( {d: max(mapper[d], v) for d, v in g.diameter.items()}) except ValueError: groups.remove(g) intervalss = { d: make_rotations_table(d, v) for d, v in mapper.items() } # For each Group, find a rotation that is compatible with a given MI mapper = {} for d, intervals in intervalss.items(): for interval in list(intervals): found = { g: g.find_rotation_distance(d, interval) for g in groups } if all(distance is not None for distance in found.values()): # `interval` is OK ! mapper[interval] = found break if len(mapper) == len(intervalss): break # Try again with fewer groups smallest = len(min(groups, key=len)) groups = [g for g in groups if len(g) > smallest] for g in groups: c = g.pivot distances = defaultdict(int, [(i.dim, v[g]) for i, v in mapper.items()]) # Create the basis alias offsets = [ LabeledVector([(l, v[l] + distances[l]) for l in v.labels]) for v in c.offsets ] subs = { i: i.function[[l + v.fromlabel(l, 0) for l in b]] for i, b, v in zip(c.indexeds, c.bases, offsets) } alias = uxreplace(c.expr, subs) # All aliased expressions aliaseds = [i.expr for i in g] # Distance of each aliased expression from the basis alias distances = [] for i in g: distance = [o.distance(v) for o, v in zip(i.offsets, offsets)] distance = [(d, set(v)) for d, v in LabeledVector.transpose(*distance)] distances.append( LabeledVector([(d, v.pop()) for d, v in distance])) aliases.add(alias, list(mapper), aliaseds, distances) return aliases
def collect(extracted, ispace, minstorage): """ Find groups of aliasing expressions. We shall introduce the following (loose) terminology: * A ``terminal`` is the leaf of a mathematical operation. Terminals can be numbers (n), literals (l), or Indexeds (I). * ``R`` is the relaxation operator := ``R(n) = n``, ``R(l) = l``, ``R(I) = J``, where ``J`` has the same base as ``I`` but with all offsets stripped away. For example, ``R(a[i+2,j-1]) = a[i,j]``. * A ``relaxed expression`` is an expression in which all of the terminals are relaxed. Now we define the concept of aliasing. We say that an expression A aliases an expression B if: * ``R(A) == R(B)`` * all pairwise Indexeds in A and B access memory locations at a fixed constant distance along each Dimension. For example, consider the following expressions: * a[i+1] + b[i+1] * a[i+1] + b[j+1] * a[i] + c[i] * a[i+2] - b[i+2] * a[i+2] + b[i] * a[i-1] + b[i-1] Out of the expressions above, the following alias to `a[i] + b[i]`: * a[i+1] + b[i+1] : same operands and operations, distance along i: 1 * a[i-1] + b[i-1] : same operands and operations, distance along i: -1 Whereas the following do not: * a[i+1] + b[j+1] : because at least one index differs * a[i] + c[i] : because at least one of the operands differs * a[i+2] - b[i+2] : because at least one operation differs * a[i+2] + b[i] : because the distances along ``i`` differ (+2 and +0) """ # Find the potential aliases found = [] for expr in extracted: assert not expr.is_Equality indexeds = retrieve_indexed(expr) bases = [] offsets = [] for i in indexeds: ii = IterationInstance(i) if ii.is_irregular: break base = [] offset = [] for e, ai in zip(ii, ii.aindices): if q_constant(e): base.append(e) else: base.append(ai) offset.append((ai, e - ai)) bases.append(tuple(base)) offsets.append(LabeledVector(offset)) if not indexeds or len(bases) == len(indexeds): found.append(Candidate(expr, ispace, indexeds, bases, offsets)) # Create groups of aliasing expressions mapper = OrderedDict() unseen = list(found) while unseen: c = unseen.pop(0) group = [c] for u in list(unseen): # Is the arithmetic structure of `c` and `u` equivalent ? if not compare_ops(c.expr, u.expr): continue # Is `c` translated w.r.t. `u` ? if not c.translated(u): continue group.append(u) unseen.remove(u) group = Group(group) if minstorage: k = group.dimensions_translated else: k = group.dimensions mapper.setdefault(k, []).append(group) aliases = AliasList() queue = list(mapper.values()) while queue: groups = queue.pop(0) while groups: # For each Dimension, determine the Minimum Intervals (MI) spanning # all of the Groups diameters # Example: x's largest_diameter=2 => [x[-2,0], x[-1,1], x[0,2]] # Note: Groups that cannot evaluate their diameter are dropped mapper = defaultdict(int) for g in list(groups): try: mapper.update( {d: max(mapper[d], v) for d, v in g.diameter.items()}) except ValueError: groups.remove(g) intervalss = { d: make_rotations_table(d, v) for d, v in mapper.items() } # For each Group, find a rotation that is compatible with a given MI mapper = {} for d, intervals in intervalss.items(): # Not all groups may access all dimensions # Example: `d=t` and groups=[Group(...[t, x]...), Group(...[time, x]...)] impacted = [g for g in groups if d in g.dimensions] for interval in list(intervals): found = { g: g.find_rotation_distance(d, interval) for g in impacted } if all(distance is not None for distance in found.values()): # `interval` is OK ! mapper[interval] = found break if len(mapper) == len(intervalss): break # Try again with fewer groups # Heuristic: first try retaining the larger ones smallest = len(min(groups, key=len)) fallback = groups groups, remainder = split(groups, lambda g: len(g) > smallest) if groups: queue.append(remainder) elif len(remainder) > 1: # No luck with the heuristic, e.g. there are two groups # and both have same `len` queue.append(fallback[1:]) groups = [fallback.pop(0)] else: break for g in groups: c = g.pivot distances = defaultdict(int, [(i.dim, v.get(g)) for i, v in mapper.items()]) # Create the basis alias offsets = [ LabeledVector([(l, v[l] + distances[l]) for l in v.labels]) for v in c.offsets ] subs = { i: i.function[[l + v.fromlabel(l, 0) for l in b]] for i, b, v in zip(c.indexeds, c.bases, offsets) } pivot = uxreplace(c.expr, subs) # All aliased expressions aliaseds = [extracted[i.expr] for i in g] # Distance of each aliased expression from the basis alias distances = [] for i in g: distance = [o.distance(v) for o, v in zip(i.offsets, offsets)] distance = [(d, set(v)) for d, v in LabeledVector.transpose(*distance)] distances.append( LabeledVector([(d, v.pop()) for d, v in distance])) # Compute the alias score na = len(aliaseds) nr = nredundants(ispace, pivot) score = estimate_cost(pivot, True) * ((na - 1) + nr) if score > 0: aliases.add(pivot, aliaseds, list(mapper), distances, score) return aliases