Exemplo n.º 1
0
def estimate_memory(handle, mode='realistic'):
    """
    Estimate the number of memory reads and writes.

    :param handle: a SymPy expression or an iterator of SymPy expressions.
    :param mode: Mode for computing the estimate:

    Estimate ``mode`` might be any of: ::

        * ideal: Also known as "compulsory traffic", which is the minimum
                 number of read/writes to be performed (ie, models an infinite cache).
        * ideal_with_stores: Like ideal, but a data item which is both read.
                             and written is counted twice (ie both load an
                             store are counted).
        * realistic: Assume that all datasets, even the time-independent ones,
                     need to be re-read at each time iteration.
    """
    assert mode in ['ideal', 'ideal_with_stores', 'realistic']

    def access(symbol):
        assert isinstance(symbol, Indexed)
        # Irregular accesses (eg A[B[i]]) are counted as compulsory traffic
        if any(i.atoms(Indexed) for i in symbol.indices):
            return symbol
        else:
            return symbol.base

    try:
        # Is it a plain SymPy object ?
        iter(handle)
    except TypeError:
        handle = [handle]

    if mode in ['ideal', 'ideal_with_stores']:
        filter = lambda s: t in s.atoms()
    else:
        filter = lambda s: s
    reads = set(flatten([retrieve_indexed(e.rhs) for e in handle]))
    writes = set(flatten([retrieve_indexed(e.lhs) for e in handle]))
    reads = set([access(s) for s in reads if filter(s)])
    writes = set([access(s) for s in writes if filter(s)])
    if mode == 'ideal':
        return len(set(reads) | set(writes))
    else:
        return len(reads) + len(writes)
Exemplo n.º 2
0
    def extract(cls, expr):
        """
        Compute the stencil of ``expr``.
        """
        assert expr.is_Equality

        # Collect all indexed objects appearing in /expr/
        indexed = list(retrieve_indexed(expr.lhs))
        indexed += list(retrieve_indexed(expr.rhs))
        indexed += flatten([retrieve_indexed(i) for i in e.indices]
                           for e in indexed)

        # Enforce deterministic ordering
        dims = []
        for e in indexed:
            d = []
            for a in e.indices:
                found = [
                    idx for idx in a.free_symbols
                    if isinstance(idx, Dimension)
                ]
                d.extend([idx for idx in found if idx not in d])
            dims.append(tuple(d))
        stencil = Stencil([(i, set()) for i in partial_order(dims)])

        # Determine the points accessed along each dimension
        for e in indexed:
            for a in e.indices:
                if isinstance(a, Dimension):
                    stencil[a].update([0])
                d = None
                off = [0]
                for idx in a.args:
                    if isinstance(idx, Dimension):
                        d = idx
                    elif idx.is_integer:
                        off += [idx]
                if d is not None:
                    stencil[d].update(off)

        return stencil
Exemplo n.º 3
0
 def is_index(self, key):
     """
     Return True if ``key`` is used as array index in an expression of the
     TemporariesGraph, False otherwise.
     """
     if key not in self:
         return False
     match = key.base.label if self[key].is_tensor else key
     for i in self.extract(key, readby=True):
         for e in retrieve_indexed(i):
             if any(match in idx.free_symbols for idx in e.indices):
                 return True
     return False
Exemplo n.º 4
0
 def tensors(self):
     """
     Return all occurrences of the tensors in ``self`` keyed by function.
     """
     mapper = {}
     for v in self.values():
         handle = retrieve_indexed(v)
         for i in handle:
             found = mapper.setdefault(i.base.function, [])
             if i not in found:
                 # Not using sets to preserve order
                 found.append(i)
     return mapper
Exemplo n.º 5
0
def q_indirect(expr):
    """
    Return True if ``indexed`` has indirect accesses, False otherwise.

    Examples
    ========
    a[i] --> False
    a[b[i]] --> True
    """
    from devito.dse.search import retrieve_indexed

    if not q_indexed(expr):
        return False
    return any(retrieve_indexed(i) for i in expr.indices)
Exemplo n.º 6
0
    def extract(cls, expr):
        """
        Compute the stencil of ``expr``.
        """
        assert expr.is_Equality

        # Collect all indexed objects appearing in /expr/
        terminals = retrieve_terminals(expr, mode='all')
        indexeds = [i for i in terminals if q_indexed(i)]
        indexeds += flatten([retrieve_indexed(i) for i in e.indices]
                            for e in indexeds)

        # Enforce deterministic dimension ordering...
        dims = OrderedDict()
        for e in terminals:
            if isinstance(e, Dimension):
                dims[(e, )] = e
            elif q_indexed(e):
                d = []
                for a in e.indices:
                    found = [
                        i for i in a.free_symbols if isinstance(i, Dimension)
                    ]
                    d.extend([i for i in found if i not in d])
                dims[tuple(d)] = e
        # ... giving higher priority to TimeData objects; time always go first
        dims = sorted(list(dims),
                      key=lambda i: not (isinstance(dims[i], Dimension) or
                                         dims[i].base.function.is_TimeData))
        stencil = Stencil([(i, set()) for i in partial_order(dims)])

        # Determine the points accessed along each dimension
        for e in indexeds:
            for a in e.indices:
                if isinstance(a, Dimension):
                    stencil[a].update([0])
                d = None
                off = [0]
                for i in a.args:
                    if isinstance(i, Dimension):
                        d = i
                    elif i.is_integer:
                        off += [i]
                if d is not None:
                    stencil[d].update(off)

        return stencil
Exemplo n.º 7
0
def create_alias(expr, offsets):
    """
    Create an aliasing expression of ``expr`` by replacing the offsets of each
    indexed object in ``expr`` with the new values in ``offsets``. ``offsets``
    is an ordered sequence of tuples with as many elements as the number of
    indexed objects in ``expr``.
    """
    indexeds = retrieve_indexed(expr, mode='all')
    assert len(indexeds) == len(offsets)

    mapper = {}
    for indexed, ofs in zip(indexeds, offsets):
        base = indexed.base
        dimensions = base.function.indices
        assert len(dimensions) == len(ofs)
        mapper[indexed] = indexed.func(base, *[sum(i) for i in zip(dimensions, ofs)])

    return expr.xreplace(mapper)
Exemplo n.º 8
0
    def time_invariant(self, expr=None):
        """
        Check if ``expr`` is time invariant. ``expr`` may be an expression ``e``
        explicitly tracked by the TemporariesGraph or even a generic subexpression
        of ``e``. If no ``expr`` is provided, then time invariance of the entire
        TemporariesGraph is assessed.
        """
        if expr is None:
            return all(self.time_invariant(v) for v in self.values())

        if any(i in expr.free_symbols for i in self.time_indices):
            return False
        queue = [expr.rhs] if expr.is_Equality else [expr]
        while queue:
            item = queue.pop()
            for i in retrieve_indexed(item):
                if any(j in i.free_symbols for j in self.time_indices):
                    return False
            temporaries = [i for i in item.free_symbols if i in self]
            queue.extend(
                [self[i].rhs for i in temporaries if self[i].rhs != item])
        return True
Exemplo n.º 9
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
Exemplo n.º 10
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