Skip to content
Snippets Groups Projects
kerncraft_interface.py 7.04 KiB
Newer Older
Martin Bauer's avatar
Martin Bauer committed
from tempfile import TemporaryDirectory
Martin Bauer's avatar
Martin Bauer committed
import sympy as sp
Martin Bauer's avatar
Martin Bauer committed
import os
Martin Bauer's avatar
Martin Bauer committed
from collections import defaultdict
Martin Bauer's avatar
Martin Bauer committed
import subprocess
import kerncraft
Martin Bauer's avatar
Martin Bauer committed
import kerncraft.kernel
from kerncraft.machinemodel import MachineModel
from kerncraft.models import ECM, Benchmark
from kerncraft.iaca import iaca_analyse_instrumented_binary, iaca_instrumentation
Martin Bauer's avatar
Martin Bauer committed
from pystencils.kerncraft_coupling.generate_benchmark import generate_benchmark
Martin Bauer's avatar
Martin Bauer committed
from pystencils.astnodes import LoopOverCoordinate, SympyAssignment, ResolvedFieldAccess
Martin Bauer's avatar
Martin Bauer committed
from pystencils.field import get_layout_from_strides
Martin Bauer's avatar
Martin Bauer committed
from pystencils.sympyextensions import count_operations_in_ast
Martin Bauer's avatar
Martin Bauer committed
from pystencils.utils import DotDict


class PyStencilsKerncraftKernel(kerncraft.kernel.Kernel):
    """
    Implementation of kerncraft's kernel interface for pystencils CPU kernels.
    Analyses a list of equations assuming they will be executed on a CPU
    """
Martin Bauer's avatar
Martin Bauer committed
    LIKWID_BASE = '/usr/local/likwid'

Jan Hönig's avatar
Jan Hönig committed
    def __init__(self, ast, machine=None):
        super(PyStencilsKerncraftKernel, self).__init__(machine)
Martin Bauer's avatar
Martin Bauer committed

        self.ast = ast
Martin Bauer's avatar
Martin Bauer committed
        self.temporary_dir = TemporaryDirectory()
Martin Bauer's avatar
Martin Bauer committed

        # Loops
Martin Bauer's avatar
Martin Bauer committed
        inner_loops = [l for l in ast.atoms(LoopOverCoordinate) if l.is_innermost_loop]
        if len(inner_loops) == 0:
Martin Bauer's avatar
Martin Bauer committed
            raise ValueError("No loop found in pystencils AST")
Martin Bauer's avatar
Martin Bauer committed
        elif len(inner_loops) > 1:
Martin Bauer's avatar
Martin Bauer committed
            raise ValueError("pystencils AST contains multiple inner loops - only one can be analyzed")
        else:
Martin Bauer's avatar
Martin Bauer committed
            inner_loop = inner_loops[0]
Martin Bauer's avatar
Martin Bauer committed

        self._loop_stack = []
Martin Bauer's avatar
Martin Bauer committed
        cur_node = inner_loop
        while cur_node is not None:
            if isinstance(cur_node, LoopOverCoordinate):
Martin Bauer's avatar
Martin Bauer committed
                loop_counter_sym = cur_node.loop_counter_symbol
                loop_info = (loop_counter_sym.name, cur_node.start, cur_node.stop, cur_node.step)
                self._loop_stack.append(loop_info)
Martin Bauer's avatar
Martin Bauer committed
            cur_node = cur_node.parent
Martin Bauer's avatar
Martin Bauer committed
        self._loop_stack = list(reversed(self._loop_stack))

        # Data sources & destinations
        self.sources = defaultdict(list)
        self.destinations = defaultdict(list)
Martin Bauer's avatar
Martin Bauer committed

Martin Bauer's avatar
Martin Bauer committed
        reads, writes = search_resolved_field_accesses_in_ast(inner_loop)
Martin Bauer's avatar
Martin Bauer committed
        for accesses, target_dict in [(reads, self.sources), (writes, self.destinations)]:
Martin Bauer's avatar
Martin Bauer committed
            for fa in accesses:
Martin Bauer's avatar
Martin Bauer committed
                coord = [sp.Symbol(LoopOverCoordinate.get_loop_counter_name(i), positive=True, integer=True) + off
Martin Bauer's avatar
Martin Bauer committed
                         for i, off in enumerate(fa.offsets)]
Martin Bauer's avatar
Martin Bauer committed
                coord += list(fa.idx_coordinate_values)
Martin Bauer's avatar
Martin Bauer committed
                layout = get_layout_from_strides(fa.field.strides)
                permuted_coord = [coord[i] for i in layout]
Martin Bauer's avatar
Martin Bauer committed
                target_dict[fa.field.name].append(permuted_coord)
Martin Bauer's avatar
Martin Bauer committed

        # Variables (arrays)
Martin Bauer's avatar
Martin Bauer committed
        fields_accessed = ast.fields_accessed
        for field in fields_accessed:
            layout = get_layout_from_strides(field.strides)
            permuted_shape = list(field.shape[i] for i in layout)
            self.set_variable(field.name, str(field.dtype), tuple(permuted_shape))
Martin Bauer's avatar
Martin Bauer committed

        for param in ast.parameters:
Martin Bauer's avatar
Martin Bauer committed
            if not param.is_field_argument:
Martin Bauer's avatar
Martin Bauer committed
                self.set_variable(param.name, str(param.dtype), None)
Martin Bauer's avatar
Martin Bauer committed

        # data type
        self.datatype = list(self.variables.values())[0][0]

        # flops
Martin Bauer's avatar
Martin Bauer committed
        operation_count = count_operations_in_ast(inner_loop)
Martin Bauer's avatar
Martin Bauer committed
        self._flops = {
Martin Bauer's avatar
Martin Bauer committed
            '+': operation_count['adds'],
            '*': operation_count['muls'],
            '/': operation_count['divs'],
Martin Bauer's avatar
Martin Bauer committed
        }
Jan Hönig's avatar
Jan Hönig committed
        for k in [k for k, v in self._flops.items() if v == 0]:
            del self._flops[k]
Martin Bauer's avatar
Martin Bauer committed
        self.check()

Jan Hönig's avatar
Jan Hönig committed
    def iaca_analysis(self, micro_architecture, asm_block='auto',
                      pointer_increment='auto_with_manual_fallback', verbose=False):
        compiler, compiler_args = self._machine.get_compiler()
Martin Bauer's avatar
Martin Bauer committed
        if '-std=c99' not in compiler_args:
            compiler_args += ['-std=c99']
Martin Bauer's avatar
Martin Bauer committed
        header_path = kerncraft.get_header_path()
Martin Bauer's avatar
Martin Bauer committed
        compiler_cmd = [compiler] + compiler_args + ['-I' + header_path]
Martin Bauer's avatar
Martin Bauer committed
        src_file = os.path.join(self.temporary_dir.name, "source.c")
        asm_file = os.path.join(self.temporary_dir.name, "source.s")
        iaca_asm_file = os.path.join(self.temporary_dir.name, "source.iaca.s")
Martin Bauer's avatar
Martin Bauer committed
        dummy_src_file = os.path.join(header_path, "dummy.c")
Martin Bauer's avatar
Martin Bauer committed
        dummy_asm_file = os.path.join(self.temporary_dir.name, "dummy.s")
        binary_file = os.path.join(self.temporary_dir.name, "binary")
Martin Bauer's avatar
Martin Bauer committed

        # write source code to file
Martin Bauer's avatar
Martin Bauer committed
        with open(src_file, 'w') as f:
            f.write(generate_benchmark(self.ast, likwid=False))
Martin Bauer's avatar
Martin Bauer committed

        # compile to asm files
Martin Bauer's avatar
Martin Bauer committed
        subprocess.check_output(compiler_cmd + [src_file,      '-S', '-o', asm_file])
        subprocess.check_output(compiler_cmd + [dummy_src_file, '-S', '-o', dummy_asm_file])
Martin Bauer's avatar
Martin Bauer committed
        with open(asm_file) as read, open(iaca_asm_file, 'w') as write:
            instrumented_asm_block = iaca_instrumentation(read, write)
Martin Bauer's avatar
Martin Bauer committed

        # assemble asm files to executable
Martin Bauer's avatar
Martin Bauer committed
        subprocess.check_output(compiler_cmd + [iaca_asm_file, dummy_asm_file, '-o', binary_file])
Martin Bauer's avatar
Martin Bauer committed
        result = iaca_analyse_instrumented_binary(binary_file, micro_architecture)
Martin Bauer's avatar
Martin Bauer committed
        return result, instrumented_asm_block
Jan Hönig's avatar
Jan Hönig committed
    def build(self, lflags=None, verbose=False):
        compiler, compiler_args = self._machine.get_compiler()
Martin Bauer's avatar
Martin Bauer committed
        if '-std=c99' not in compiler_args:
            compiler_args.append('-std=c99')
Martin Bauer's avatar
Martin Bauer committed
        header_path = kerncraft.get_header_path()
Martin Bauer's avatar
Martin Bauer committed

        cmd = [compiler] + compiler_args + [
            '-I' + os.path.join(self.LIKWID_BASE, 'include'),
            '-L' + os.path.join(self.LIKWID_BASE, 'lib'),
Martin Bauer's avatar
Martin Bauer committed
            '-I' + header_path,
Martin Bauer's avatar
Martin Bauer committed
            '-Wl,-rpath=' + os.path.join(self.LIKWID_BASE, 'lib'),
        ]

Martin Bauer's avatar
Martin Bauer committed
        dummy_src_file = os.path.join(header_path, 'dummy.c')
Martin Bauer's avatar
Martin Bauer committed
        src_file = os.path.join(self.temporary_dir.name, "source_likwid.c")
        bin_file = os.path.join(self.temporary_dir.name, "benchmark")
Martin Bauer's avatar
Martin Bauer committed
        with open(src_file, 'w') as f:
            f.write(generate_benchmark(self.ast, likwid=True))
Martin Bauer's avatar
Martin Bauer committed
        subprocess.check_output(cmd + [src_file, dummy_src_file, '-pthread', '-llikwid', '-o', bin_file])
        return bin_file
Martin Bauer's avatar
Martin Bauer committed

class KerncraftParameters(DotDict):
    def __init__(self):
        self['asm_block'] = 'auto'
        self['asm_increment'] = 0
        self['cores'] = 1
        self['cache_predictor'] = 'SIM'
        self['verbose'] = 0
Jan Hönig's avatar
Jan Hönig committed
        self['pointer_increment'] = 'auto'
Jan Hönig's avatar
Jan Hönig committed
        self['iterations'] = 10

Martin Bauer's avatar
Martin Bauer committed

# ------------------------------------------- Helper functions ---------------------------------------------------------


Martin Bauer's avatar
Martin Bauer committed
def search_resolved_field_accesses_in_ast(ast):
Martin Bauer's avatar
Martin Bauer committed
    def visit(node, reads, writes):
        if not isinstance(node, SympyAssignment):
            for a in node.args:
                visit(a, reads, writes)
            return

        for expr, accesses in [(node.lhs, writes), (node.rhs, reads)]:
            accesses.update(expr.atoms(ResolvedFieldAccess))

Martin Bauer's avatar
Martin Bauer committed
    read_accesses = set()
    write_accesses = set()
    visit(ast, read_accesses, write_accesses)
    return read_accesses, write_accesses