kerncraft/models/benchmark.py
def analyze(self):
        """Run analysis."""
        bench = self.kernel.build_executable(verbose=self.verbose > 1, openmp=self._args.cores > 1)
        element_size = self.kernel.datatypes_size[self.kernel.datatype]

        # Build arguments to pass to command:
        args = [str(s) for s in list(self.kernel.constants.values())]

        # Determine base runtime with 10 iterations
        runtime = 0.0
        time_per_repetition = 2.0 / 10.0
        repetitions = self.iterations // 10
        mem_results = {}

        # TODO if cores > 1, results are for openmp run. Things might need to be changed here!

        while runtime < 1.5:
            # Interpolate to a 2.0s run
            if time_per_repetition != 0.0:
                repetitions = 2.0 // time_per_repetition
            else:
                repetitions = int(repetitions * 10)

            mem_results = self.perfctr([bench] + [str(repetitions)] + args, group="MEM")
            runtime = mem_results['Runtime (RDTSC) [s]']
            time_per_repetition = runtime / float(repetitions)
        raw_results = [mem_results]

        # Base metrics for further metric computations:
        # An iteration is equal to one high-level code inner-most-loop iteration
        iterations_per_repetition = reduce(
            operator.mul,
            [self.kernel.subs_consts(max_ - min_) / self.kernel.subs_consts(step)
             for idx, min_, max_, step in self.kernel._loop_stack],
            1)
        iterations_per_cacheline = (float(self.machine['cacheline size']) /
                                    self.kernel.bytes_per_iteration)
        cys_per_repetition = time_per_repetition * float(self.machine['clock'])

        # Gather remaining counters
        if not self.no_phenoecm:
            # Build events and sympy expressions for all model metrics
            T_OL, event_counters = self.machine.parse_perfmetric(
                self.machine['overlapping model']['performance counter metric'])
            T_data, event_dict = self.machine.parse_perfmetric(
                self.machine['non-overlapping model']['performance counter metric'])
            event_counters.update(event_dict)
            cache_metrics = defaultdict(dict)
            for i in range(len(self.machine['memory hierarchy']) - 1):
                cache_info = self.machine['memory hierarchy'][i]
                name = cache_info['level']
                for k, v in cache_info['performance counter metrics'].items():
                    cache_metrics[name][k], event_dict = self.machine.parse_perfmetric(v)
                    event_counters.update(event_dict)

            # Compile minimal runs to gather all required events
            minimal_runs = build_minimal_runs(list(event_counters.values()))
            measured_ctrs = {}
            for run in minimal_runs:
                ctrs = ','.join([eventstr(e) for e in run])
                r = self.perfctr([bench] + [str(repetitions)] + args, group=ctrs)
                raw_results.append(r)
                measured_ctrs.update(r)
            # Match measured counters to symbols
            event_counter_results = {}
            for sym, ctr in event_counters.items():
                event, regs, parameter = ctr[0], register_options(ctr[1]), ctr[2]
                for r in regs:
                    if r in measured_ctrs[event]:
                        event_counter_results[sym] = measured_ctrs[event][r]

            # Analytical metrics needed for futher calculation
            cl_size = float(self.machine['cacheline size'])
            total_iterations = iterations_per_repetition * repetitions
            total_cachelines = total_iterations / iterations_per_cacheline

            T_OL_result = T_OL.subs(event_counter_results) / total_cachelines
            cache_metric_results = defaultdict(dict)
            for cache, mtrcs in cache_metrics.items():
                for m, e in mtrcs.items():
                    cache_metric_results[cache][m] = e.subs(event_counter_results)

            # Inter-cache transfers per CL
            cache_transfers_per_cl = {cache: {k: PrefixedUnit(v / total_cachelines, 'CL/CL')
                                              for k, v in d.items()}
                                      for cache, d in cache_metric_results.items()}
            cache_transfers_per_cl['L1']['accesses'].unit = 'LOAD/CL'

            # Select appropriate bandwidth
            mem_bw, mem_bw_kernel = self.machine.get_bandwidth(
                -1,  # mem
                cache_metric_results['L3']['misses'],  # load_streams
                cache_metric_results['L3']['evicts'],  # store_streams
                1)

            data_transfers = {
                # Assuming 0.5 cy / LOAD (SSE on SNB or IVB; AVX on HSW, BDW, SKL or SKX)
                'T_nOL': (cache_metric_results['L1']['accesses'] / total_cachelines * 0.5),
                'T_L1L2': ((cache_metric_results['L1']['misses'] +
                            cache_metric_results['L1']['evicts']) /
                           total_cachelines * cl_size /
                           self.machine['memory hierarchy'][1]['non-overlap upstream throughput'][0]),
                'T_L2L3': ((cache_metric_results['L2']['misses'] +
                            cache_metric_results['L2']['evicts']) /
                           total_cachelines * cl_size /
                           self.machine['memory hierarchy'][2]['non-overlap upstream throughput'][0]),
                'T_L3MEM': ((cache_metric_results['L3']['misses'] +
                             cache_metric_results['L3']['evicts']) *
                            float(self.machine['cacheline size']) /
                            total_cachelines / mem_bw *
                            float(self.machine['clock']))
            }

            # Build phenomenological ECM model:
            ecm_model = {'T_OL': T_OL_result}
            ecm_model.update(data_transfers)
        else:
            event_counters = {}
            ecm_model = None
            cache_transfers_per_cl = None

        self.results = {'raw output': raw_results, 'ECM': ecm_model,
                        'data transfers': cache_transfers_per_cl,
                        'Runtime (per repetition) [s]': time_per_repetition,
                        'event counters': event_counters,
                        'Iterations per repetition': iterations_per_repetition,
                        'Iterations per cacheline': iterations_per_cacheline}

        # TODO make more generic to support other (and multiple) constant names
        self.results['Runtime (per cacheline update) [cy/CL]'] = \
            (cys_per_repetition / iterations_per_repetition) * iterations_per_cacheline
        self.results['MEM volume (per repetition) [B]'] = \
            mem_results['Memory data volume [GBytes]'] * 1e9 / repetitions
        self.results['Performance [MFLOP/s]'] = \
            sum(self.kernel._flops.values()) / (
            time_per_repetition / iterations_per_repetition) / 1e6
        if 'Memory bandwidth [MBytes/s]' in mem_results:
            self.results['MEM BW [MByte/s]'] = mem_results['Memory bandwidth [MBytes/s]']
        else:
            self.results['MEM BW [MByte/s]'] = mem_results['Memory BW [MBytes/s]']
        self.results['Performance [MLUP/s]'] = (
                                               iterations_per_repetition / time_per_repetition) / 1e6
        self.results['Performance [MIt/s]'] = (
                                              iterations_per_repetition / time_per_repetition) / 1e6
def analyze(self):
        """Run analysis."""
        bench = self.kernel.build_executable(verbose=self.verbose > 1, openmp=self._args.cores > 1)
        element_size = self.kernel.datatypes_size[self.kernel.datatype]

        # Build arguments to pass to command:
        args = [str(s) for s in list(self.kernel.constants.values())]

        # Determine base runtime with 10 iterations
        runtime = 0.0
        time_per_repetition = 2.0 / 10.0
        repetitions = self.iterations // 10
        mem_results = {}

        # TODO if cores > 1, results are for openmp run. Things might need to be changed here!

        while runtime < 1.5:
            # Interpolate to a 2.0s run
            if time_per_repetition != 0.0:
                repetitions = 2.0 // time_per_repetition
            else:
                repetitions = int(repetitions * 10)

            mem_results = self.perfctr([bench] + [str(repetitions)] + args, group="MEM")
            runtime = mem_results['Runtime (RDTSC) [s]']
            time_per_repetition = runtime / float(repetitions)
        raw_results = [mem_results]

        # Base metrics for further metric computations:
        # An iteration is equal to one high-level code inner-most-loop iteration
        iterations_per_repetition = reduce(
            operator.mul,
            [self.kernel.subs_consts(max_ - min_) / self.kernel.subs_consts(step)
             for idx, min_, max_, step in self.kernel._loop_stack],
            1)
        iterations_per_cacheline = (float(self.machine['cacheline size']) /
                                    self.kernel.bytes_per_iteration)
        cys_per_repetition = time_per_repetition * float(self.machine['clock'])

        # Gather remaining counters
        if not self.no_phenoecm:
            # Build events and sympy expressions for all model metrics
            T_OL, event_counters = self.machine.parse_perfmetric(
                self.machine['overlapping model']['performance counter metric'])
            T_data, event_dict = self.machine.parse_perfmetric(
                self.machine['non-overlapping model']['performance counter metric'])
            event_counters.update(event_dict)
            cache_metrics = defaultdict(dict)
            for i in range(len(self.machine['memory hierarchy']) - 1):
                cache_info = self.machine['memory hierarchy'][i]
                name = cache_info['level']
                for k, v in cache_info['performance counter metrics'].items():
                    cache_metrics[name][k], event_dict = self.machine.parse_perfmetric(v)
                    event_counters.update(event_dict)

            # Compile minimal runs to gather all required events
            minimal_runs = build_minimal_runs(list(event_counters.values()))
            measured_ctrs = {}
            for run in minimal_runs:
                ctrs = ','.join([eventstr(e) for e in run])
                r = self.perfctr([bench] + [str(repetitions)] + args, group=ctrs)
                raw_results.append(r)
                measured_ctrs.update(r)
            # Match measured counters to symbols
            event_counter_results = {}
            for sym, ctr in event_counters.items():
                event, regs, parameter = ctr[0], register_options(ctr[1]), ctr[2]
                for r in regs:
                    if r in measured_ctrs[event]:
                        event_counter_results[sym] = measured_ctrs[event][r]

            # Analytical metrics needed for futher calculation
            cl_size = float(self.machine['cacheline size'])
            total_iterations = iterations_per_repetition * repetitions
            total_cachelines = total_iterations / iterations_per_cacheline

            T_OL_result = T_OL.subs(event_counter_results) / total_cachelines
            cache_metric_results = defaultdict(dict)
            for cache, mtrcs in cache_metrics.items():
                for m, e in mtrcs.items():
                    cache_metric_results[cache][m] = e.subs(event_counter_results)

            # Inter-cache transfers per CL
            cache_transfers_per_cl = {cache: {k: PrefixedUnit(v / total_cachelines, 'CL/CL')
                                              for k, v in d.items()}
                                      for cache, d in cache_metric_results.items()}
            cache_transfers_per_cl['L1']['accesses'].unit = 'LOAD/CL'

            # Select appropriate bandwidth
            mem_bw, mem_bw_kernel = self.machine.get_bandwidth(
                -1,  # mem
                cache_metric_results['L3']['misses'],  # load_streams
                cache_metric_results['L3']['evicts'],  # store_streams
                1)

            data_transfers = {
                # Assuming 0.5 cy / LOAD (SSE on SNB or IVB; AVX on HSW, BDW, SKL or SKX)
                'T_nOL': (cache_metric_results['L1']['accesses'] / total_cachelines * 0.5),
                'T_L1L2': ((cache_metric_results['L1']['misses'] +
                            cache_metric_results['L1']['evicts']) /
                           total_cachelines * cl_size /
                           self.machine['memory hierarchy'][1]['non-overlap upstream throughput'][0]),
                'T_L2L3': ((cache_metric_results['L2']['misses'] +
                            cache_metric_results['L2']['evicts']) /
                           total_cachelines * cl_size /
                           self.machine['memory hierarchy'][2]['non-overlap upstream throughput'][0]),
                'T_L3MEM': ((cache_metric_results['L3']['misses'] +
                             cache_metric_results['L3']['evicts']) *
                            float(self.machine['cacheline size']) /
                            total_cachelines / mem_bw *
                            float(self.machine['clock']))
            }

            # Build phenomenological ECM model:
            ecm_model = {'T_OL': T_OL_result}
            ecm_model.update(data_transfers)
        else:
            event_counters = {}
            ecm_model = None
            cache_transfers_per_cl = None

        self.results = {'raw output': raw_results, 'ECM': ecm_model,
                        'data transfers': cache_transfers_per_cl,
                        'Runtime (per repetition) [s]': time_per_repetition,
                        'event counters': event_counters,
                        'Iterations per repetition': iterations_per_repetition,
                        'Iterations per cacheline': iterations_per_cacheline}

        # TODO make more generic to support other (and multiple) constant names
        self.results['Runtime (per cacheline update) [cy/CL]'] = \
            (cys_per_repetition / iterations_per_repetition) * iterations_per_cacheline
        self.results['MEM volume (per repetition) [B]'] = \
            mem_results['Memory data volume [GBytes]'] * 1e9 / repetitions
        self.results['Performance [MFLOP/s]'] = \
            sum(self.kernel._flops.values()) / (
            time_per_repetition / iterations_per_repetition) / 1e6
        if 'Memory bandwidth [MBytes/s]' in mem_results:
            self.results['MEM BW [MByte/s]'] = mem_results['Memory bandwidth [MBytes/s]']
        else:
            self.results['MEM BW [MByte/s]'] = mem_results['Memory BW [MBytes/s]']
        self.results['Performance [MLUP/s]'] = (
                                               iterations_per_repetition / time_per_repetition) / 1e6
        self.results['Performance [MIt/s]'] = (
                                              iterations_per_repetition / time_per_repetition) / 1e6
def analyze ( self ) : bench = self . kernel . build_executable ( verbose = self . verbose > 1 , openmp = self . _args . cores > 1 ) element_size = self . kernel . datatypes_size [ self . kernel . datatype ] # Build arguments to pass to command: args = [ str ( s ) for s in list ( self . kernel . constants . values ( ) ) ] # Determine base runtime with 10 iterations runtime = 0.0 time_per_repetition = 2.0 / 10.0 repetitions = self . iterations // 10 mem_results = { } # TODO if cores > 1, results are for openmp run. Things might need to be changed here! while runtime < 1.5 : # Interpolate to a 2.0s run if time_per_repetition != 0.0 : repetitions = 2.0 // time_per_repetition else : repetitions = int ( repetitions * 10 ) mem_results = self . perfctr ( [ bench ] + [ str ( repetitions ) ] + args , group = "MEM" ) runtime = mem_results [ 'Runtime (RDTSC) [s]' ] time_per_repetition = runtime / float ( repetitions ) raw_results = [ mem_results ] # Base metrics for further metric computations: # An iteration is equal to one high-level code inner-most-loop iteration iterations_per_repetition = reduce ( operator . mul , [ self . kernel . subs_consts ( max_ - min_ ) / self . kernel . subs_consts ( step ) for idx , min_ , max_ , step in self . kernel . _loop_stack ] , 1 ) iterations_per_cacheline = ( float ( self . machine [ 'cacheline size' ] ) / self . kernel . bytes_per_iteration ) cys_per_repetition = time_per_repetition * float ( self . machine [ 'clock' ] ) # Gather remaining counters if not self . no_phenoecm : # Build events and sympy expressions for all model metrics T_OL , event_counters = self . machine . parse_perfmetric ( self . machine [ 'overlapping model' ] [ 'performance counter metric' ] ) T_data , event_dict = self . machine . parse_perfmetric ( self . machine [ 'non-overlapping model' ] [ 'performance counter metric' ] ) event_counters . update ( event_dict ) cache_metrics = defaultdict ( dict ) for i in range ( len ( self . machine [ 'memory hierarchy' ] ) - 1 ) : cache_info = self . machine [ 'memory hierarchy' ] [ i ] name = cache_info [ 'level' ] for k , v in cache_info [ 'performance counter metrics' ] . items ( ) : cache_metrics [ name ] [ k ] , event_dict = self . machine . parse_perfmetric ( v ) event_counters . update ( event_dict ) # Compile minimal runs to gather all required events minimal_runs = build_minimal_runs ( list ( event_counters . values ( ) ) ) measured_ctrs = { } for run in minimal_runs : ctrs = ',' . join ( [ eventstr ( e ) for e in run ] ) r = self . perfctr ( [ bench ] + [ str ( repetitions ) ] + args , group = ctrs ) raw_results . append ( r ) measured_ctrs . update ( r ) # Match measured counters to symbols event_counter_results = { } for sym , ctr in event_counters . items ( ) : event , regs , parameter = ctr [ 0 ] , register_options ( ctr [ 1 ] ) , ctr [ 2 ] for r in regs : if r in measured_ctrs [ event ] : event_counter_results [ sym ] = measured_ctrs [ event ] [ r ] # Analytical metrics needed for futher calculation cl_size = float ( self . machine [ 'cacheline size' ] ) total_iterations = iterations_per_repetition * repetitions total_cachelines = total_iterations / iterations_per_cacheline T_OL_result = T_OL . subs ( event_counter_results ) / total_cachelines cache_metric_results = defaultdict ( dict ) for cache , mtrcs in cache_metrics . items ( ) : for m , e in mtrcs . items ( ) : cache_metric_results [ cache ] [ m ] = e . subs ( event_counter_results ) # Inter-cache transfers per CL cache_transfers_per_cl = { cache : { k : PrefixedUnit ( v / total_cachelines , 'CL/CL' ) for k , v in d . items ( ) } for cache , d in cache_metric_results . items ( ) } cache_transfers_per_cl [ 'L1' ] [ 'accesses' ] . unit = 'LOAD/CL' # Select appropriate bandwidth mem_bw , mem_bw_kernel = self . machine . get_bandwidth ( - 1 , # mem cache_metric_results [ 'L3' ] [ 'misses' ] , # load_streams cache_metric_results [ 'L3' ] [ 'evicts' ] , # store_streams 1 ) data_transfers = { # Assuming 0.5 cy / LOAD (SSE on SNB or IVB; AVX on HSW, BDW, SKL or SKX) 'T_nOL' : ( cache_metric_results [ 'L1' ] [ 'accesses' ] / total_cachelines * 0.5 ) , 'T_L1L2' : ( ( cache_metric_results [ 'L1' ] [ 'misses' ] + cache_metric_results [ 'L1' ] [ 'evicts' ] ) / total_cachelines * cl_size / self . machine [ 'memory hierarchy' ] [ 1 ] [ 'non-overlap upstream throughput' ] [ 0 ] ) , 'T_L2L3' : ( ( cache_metric_results [ 'L2' ] [ 'misses' ] + cache_metric_results [ 'L2' ] [ 'evicts' ] ) / total_cachelines * cl_size / self . machine [ 'memory hierarchy' ] [ 2 ] [ 'non-overlap upstream throughput' ] [ 0 ] ) , 'T_L3MEM' : ( ( cache_metric_results [ 'L3' ] [ 'misses' ] + cache_metric_results [ 'L3' ] [ 'evicts' ] ) * float ( self . machine [ 'cacheline size' ] ) / total_cachelines / mem_bw * float ( self . machine [ 'clock' ] ) ) } # Build phenomenological ECM model: ecm_model = { 'T_OL' : T_OL_result } ecm_model . update ( data_transfers ) else : event_counters = { } ecm_model = None cache_transfers_per_cl = None self . results = { 'raw output' : raw_results , 'ECM' : ecm_model , 'data transfers' : cache_transfers_per_cl , 'Runtime (per repetition) [s]' : time_per_repetition , 'event counters' : event_counters , 'Iterations per repetition' : iterations_per_repetition , 'Iterations per cacheline' : iterations_per_cacheline } # TODO make more generic to support other (and multiple) constant names self . results [ 'Runtime (per cacheline update) [cy/CL]' ] = ( cys_per_repetition / iterations_per_repetition ) * iterations_per_cacheline self . results [ 'MEM volume (per repetition) [B]' ] = mem_results [ 'Memory data volume [GBytes]' ] * 1e9 / repetitions self . results [ 'Performance [MFLOP/s]' ] = sum ( self . kernel . _flops . values ( ) ) / ( time_per_repetition / iterations_per_repetition ) / 1e6 if 'Memory bandwidth [MBytes/s]' in mem_results : self . results [ 'MEM BW [MByte/s]' ] = mem_results [ 'Memory bandwidth [MBytes/s]' ] else : self . results [ 'MEM BW [MByte/s]' ] = mem_results [ 'Memory BW [MBytes/s]' ] self . results [ 'Performance [MLUP/s]' ] = ( iterations_per_repetition / time_per_repetition ) / 1e6 self . results [ 'Performance [MIt/s]' ] = ( iterations_per_repetition / time_per_repetition ) / 1e6
Run analysis.
Run analysis .