示例#1
0
def clusterize(exprs, stencils):
    """Derive :class:`Cluster`s from an iterator of expressions; a stencil for
    each expression must be provided."""
    assert len(exprs) == len(stencils)

    exprs, stencils = aggregate(exprs, stencils)

    g = temporaries_graph(exprs)
    mapper = OrderedDict([(i.lhs, j) for i, j in zip(g.values(), stencils)
                          if i.is_tensor])

    clusters = []
    for k, v in mapper.items():
        # Determine what temporaries are needed to compute /i/
        exprs = g.trace(k)

        # Determine the Stencil of the cluster
        stencil = Stencil(v.entries)
        for i in exprs:
            stencil = stencil.add(mapper.get(i.lhs, {}))
        stencil = stencil.frozen

        # Drop all non-output tensors, as computed by other clusters
        exprs = [i for i in exprs if i.lhs.is_Symbol or i.lhs == k]

        # Create and track the cluster
        clusters.append(Cluster(exprs, stencil))

    return merge(clusters)
示例#2
0
def clusterize(exprs, stencils, atomics=None):
    """
    Derive :class:`Cluster` objetcs from an iterator of expressions; a stencil for
    each expression must be provided. A list of atomic dimensions (see description
    in Cluster.__doc__) may be provided.
    """
    assert len(exprs) == len(stencils)

    exprs, stencils = aggregate(exprs, stencils)

    Info = namedtuple('Info', 'trace stencil')

    g = temporaries_graph(exprs)
    mapper = OrderedDict([
        (k, Info(g.trace(k) + g.trace(k, readby=True, strict=True), j))
        for (k, v), j in zip(g.items(), stencils) if v.is_tensor
    ])

    # A cluster stencil is determined iteratively, by first calculating the
    # "local" stencil and then by looking at the stencils of all other clusters
    # depending on it. The stencil information is propagated until there are
    # no more updates.
    queue = list(mapper)
    while queue:
        target = queue.pop(0)

        info = mapper[target]
        strict_trace = [i.lhs for i in info.trace if i.lhs != target]

        stencil = Stencil(info.stencil.entries)
        for i in strict_trace:
            if i in mapper:
                stencil = stencil.add(mapper[i].stencil)

        mapper[target] = Info(info.trace, stencil)

        if stencil != info.stencil:
            # Something has changed, need to propagate the update
            queue.extend([i for i in strict_trace if i not in queue])

    clusters = []
    for target, info in mapper.items():
        # Drop all non-output tensors, as computed by other clusters
        exprs = [i for i in info.trace if i.lhs.is_Symbol or i.lhs == target]

        # Create and track the cluster
        clusters.append(Cluster(exprs, info.stencil.frozen, atomics))

    return merge(clusters)
示例#3
0
 def anti_stencil(self):
     handle = Stencil()
     for d, i in zip(self.dimensions, zip(*self.distances)):
         handle[d].update(set(i))
     for d, i in zip(self.dimensions, zip(*self._ghost_offsets)):
         handle[d].update(set(i))
     return handle
示例#4
0
 def _retrieve_loop_ordering(self, expressions):
     """
     Establish a partial ordering for the loops that will appear in the code
     generated by the Operator, based on the order in which dimensions
     appear in the input expressions.
     """
     ordering = []
     for i in flatten(Stencil(i).dimensions for i in expressions):
         if i not in ordering:
             ordering.extend([i, i.parent] if i.is_Buffered else [i])
     return ordering
示例#5
0
    def _retrieve_stencils(self, expressions):
        """Determine the :class:`Stencil` of each provided expression."""
        stencils = [Stencil(i) for i in expressions]
        dimensions = set.union(*[set(i.dimensions) for i in stencils])

        # Filter out aliasing buffered dimensions
        mapper = {d.parent: d for d in dimensions if d.is_Buffered}
        for i in list(stencils):
            for d in i.dimensions:
                if d in mapper:
                    i[mapper[d]] = i.pop(d).union(i.get(mapper[d], set()))

        return stencils
示例#6
0
def merge(clusters):
    """
    Given an ordered collection of :class:`Cluster` objects, return a
    (potentially) smaller sequence in which clusters with identical stencil
    have been merged into a single :class:`Cluster`.
    """
    mapper = OrderedDict()
    for c in clusters:
        mapper.setdefault(c.stencil.entries, []).append(c)

    processed = []
    for entries, clusters in mapper.items():
        # Eliminate redundant temporaries
        temporaries = OrderedDict()
        for c in clusters:
            for k, v in c.trace.items():
                if k not in temporaries:
                    temporaries[k] = v
        # Squash the clusters together
        processed.append(Cluster(temporaries.values(), Stencil(entries)))

    return processed
示例#7
0
 def stencil(self):
     """Compute the stencil of the expression."""
     return Stencil(self.expr)
示例#8
0
def collect_aliases(exprs):
    """
    Determine groups of aliasing expressions in ``exprs``.

    An expression A aliases an expression B if both A and B apply the same
    operations to the same input operands, with the possibility for indexed objects
    to index into locations at a fixed constant offset in each dimension.

    For example: ::

        exprs = (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])

    The following expressions in ``exprs`` 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 distance along ``i`` differ (+2 and +0)
    """

    ExprData = namedtuple('ExprData', 'dimensions offsets')

    # Discard expressions that surely won't alias to anything
    candidates = OrderedDict()
    for expr in exprs:
        indexeds = retrieve_indexed(expr, mode='all')
        if indexeds and not any(q_indirect(i) for i in indexeds):
            handle = calculate_offsets(indexeds)
            if handle:
                candidates[expr.rhs] = ExprData(*handle)

    aliases = OrderedDict()
    mapper = OrderedDict()
    unseen = list(candidates)
    while unseen:
        # Find aliasing expressions
        handle = unseen.pop(0)
        group = [handle]
        for e in list(unseen):
            if compare(handle, e) and\
                    is_translated(candidates[handle].offsets, candidates[e].offsets):
                group.append(e)
                unseen.remove(e)
        mapper.update([(i, group) for i in group])
        if len(group) == 1:
            continue

        # Create an alias for the group of aliasing expressions, as well as
        # any metadata needed by the caller
        offsets = [tuple(candidates[e].offsets) for e in group]
        COM, distances = calculate_COM(offsets)
        alias = create_alias(handle, COM)

        aliases[alias] = Alias(alias, group, distances, candidates[handle].dimensions)

    # Heuristically attempt to relax the aliases offsets
    # to maximize the likelyhood of loop fusion
    grouped = OrderedDict()
    for i in aliases.values():
        grouped.setdefault(i.dimensions, []).append(i)
    for dimensions, group in grouped.items():
        ideal_anti_stencil = Stencil.union(*[i.anti_stencil for i in group])
        for i in group:
            if i.anti_stencil.subtract(ideal_anti_stencil).empty:
                aliases[i.alias] = i.relax(ideal_anti_stencil)

    return mapper, aliases
示例#9
0
def collect(exprs):
    """
    Determine groups of aliasing expressions in ``exprs``.

    An expression A aliases an expression B if both A and B apply the same
    operations to the same input operands, with the possibility for indexed objects
    to index into locations at a fixed constant offset in each dimension.

    For example: ::

        exprs = (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])

    The following expressions in ``exprs`` 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 distance along ``i`` differ (+2 and +0)
    """
    ExprData = namedtuple('ExprData', 'dimensions offsets')

    # Discard expressions:
    # - that surely won't alias to anything
    # - that are non-scalar
    candidates = OrderedDict()
    for expr in exprs:
        if q_indexed(expr):
            continue
        indexeds = retrieve_indexed(expr.rhs, mode='all')
        if indexeds and not any(q_indirect(i) for i in indexeds):
            handle = calculate_offsets(indexeds)
            if handle:
                candidates[expr.rhs] = ExprData(*handle)

    aliases = OrderedDict()
    mapper = OrderedDict()
    unseen = list(candidates)
    while unseen:
        # Find aliasing expressions
        handle = unseen.pop(0)
        group = [handle]
        for e in list(unseen):
            if compare(handle, e) and\
                    is_translated(candidates[handle].offsets, candidates[e].offsets):
                group.append(e)
                unseen.remove(e)
        mapper.update([(i, group) for i in group])

        # Try creating a basis for the aliasing expressions' offsets
        offsets = [tuple(candidates[e].offsets) for e in group]
        try:
            COM, distances = calculate_COM(offsets)
        except DSEException:
            # Ignore these potential aliases and move on
            continue

        alias = create_alias(handle, COM)
        # In circumstances in which an expression has repeated coefficients, e.g.
        # ... + 0.025*a[...] + 0.025*b[...],
        # We may have found a common basis (i.e., same COM, same alias) at this point
        v = aliases.setdefault(alias, Alias(alias, candidates[handle].dimensions))
        v.extend(group, distances)

    # Heuristically attempt to relax the aliases offsets
    # to maximize the likelyhood of loop fusion
    grouped = OrderedDict()
    for i in aliases.values():
        grouped.setdefault(i.dimensions, []).append(i)
    for dimensions, group in grouped.items():
        ideal_anti_stencil = Stencil.union(*[i.anti_stencil for i in group])
        for i in group:
            if i.anti_stencil.subtract(ideal_anti_stencil).empty:
                aliases[i.alias] = i.relax(ideal_anti_stencil)

    return mapper, aliases
示例#10
0
@pytest.mark.parametrize(
    'exprs,expected',
    [
        # none (different distance)
        (['Eq(t0, fa[x] + fb[x])', 'Eq(t1, fa[x+1] + fb[x])'], {}),
        # none (different dimension)
        (['Eq(t0, fa[x] + fb[x])', 'Eq(t1, fa[x] + fb[y])'], {}),
        # none (different operation)
        (['Eq(t0, fa[x] + fb[x])', 'Eq(t1, fa[x] - fb[x])'], {}),
        # simple
        ([
            'Eq(t0, fa[x] + fb[x])', 'Eq(t1, fa[x+1] + fb[x+1])',
            'Eq(t2, fa[x-1] + fb[x-1])'
        ], {
            'fa[x] + fb[x]': Stencil([(x, {-1, 0, 1})])
        }),
        # 2D simple
        (['Eq(t0, fc[x,y] + fd[x,y])', 'Eq(t1, fc[x+1,y+1] + fd[x+1,y+1])'], {
            'fc[x,y] + fd[x,y]': Stencil([(x, {0, 1}), (y, {0, 1})])
        }),
        # 2D with stride
        ([
            'Eq(t0, fc[x,y] + fd[x+1,y+2])',
            'Eq(t1, fc[x+1,y+1] + fd[x+2,y+3])'
        ], {
            'fc[x,y] + fd[x+1,y+2]': Stencil([(x, {0, 1}), (y, {0, 1})])
        }),
        # complex (two 2D aliases with stride inducing relaxation)
        ([
            'Eq(t0, fc[x,y] + fd[x+1,y+2])',