/** * Benchmark to determine the size of each cache in the cache hierarchy. **/ #include #include #include #include #include "inc/mob.h" #include "inc/routines.h" #include "inc/zone.h" #include "inc/genCode.h" /* global variables externally defined */ extern struct globalSystem sys; /* local constants */ #define NUM_SIZES 14 /* total number of cache sizes to check */ #define MIN_SIZE M_SIZE(M_1K) /* minimum cache size to check for */ #define MAX_SIZE (MIN_SIZE << (NUM_SIZES-1)) /* maximum cache size to check for, each cache is twice as large as previous */ #define MIN_SET M_SIZE(M_2M) /* minimum block to read between gettimeofday() calls */ #define INTER_DIV 10 /* number of intermmediate division to probe prior to transition */ /* local function prototypes */ static int cacheSizeRun( type_t type ); static double cacheSizeAccess( char *array, unsigned int size, type_t type, int access ); static int checkCache( struct cache *cache ); /* Determine size of each cache in the hierarchy. return - 0 if successful, non-zero if failed */ int cacheSize( void ) { int error; /* clear any current cache values */ cache_clearObjects(); /* detect data and instruction caches */ show(VERB_NORMAL,"Data Caches:\n"); show(VERB_PLOT,"# PLOT using 2 title \"Data Cache Time\" \n"); error = cacheSizeRun( TYPE_DATA ) ; show(VERB_NORMAL,"Instruction Caches:\n"); show(VERB_PLOT,"\n\n# PLOT using 2 title \"Instruction Cache Time\" \n"); error = cacheSizeRun( TYPE_INST ); show(VERB_PLOT,"\n\n");/* Separate the sets of the output data file */ return( error ); } /* Perform the benchmark for a particular cache set (data or instruction). type - type of caches to check for return - 0 if successful, non-zero if failed */ static int cacheSizeRun( type_t type ) { unsigned int i, size, trial, level, *levels; double *access, *control; struct cache **caches; /* ensure memory block is large enough */ if( arrayPrepare( MAX_SIZE ) ) handleError( "checking memory in cacheSize", EnoMem ); /* allocate measurment result arrays */ access = (double *)calloc( NUM_SIZES, sizeof( double ) ); control = (double *)calloc( NUM_SIZES, sizeof( double ) ); if( ! (access && control) ) handleError( "getting memory in cacheSize", EnoMem ); /* take the minimum of a number of trials */ for( trial = 1; trial <= sys.args.trials; trial++ ) /* perform the benchmark for both access and control times, sizes are doubled for each subsequent test */ for( size = MIN_SIZE, i = 0; i < NUM_SIZES; size <<= 1, i++ ) { if( trial == 1 ) access[i] = control[i] = (double)LONG_MAX; /* run access tests */ access[i] = min( access[i], cacheSizeAccess( sys.array, size, type, 1 ) ); control[i] = min( control[i], cacheSizeAccess( sys.array, size, type, 0 ) ); if( trial == sys.args.trials ){ show(VERB_INSPECT,"Time: %.4f ns Control: %.4f ns [size %lu]\n", access[i], control[i] , size ); show(VERB_PLOT,"Time: %.4f ns Control: %.4f ns [ size %lu ]\n", access[i], control[i] , size ); } } /* attempt to detect distinct levels */ levels = detectTransitions( access, control, NUM_SIZES ); if( ! levels ) handleError( "getting memory in for level detection", EnoMem ); /* determine which cache set to work with */ caches = (type == TYPE_DATA) ? sys.dataCaches : sys.instCaches; /* for each level, check between to determine which side is the transition point */ for( level = 1, i = 0; i < NUM_SIZES; i++) { unsigned long low, high, check; double acum = 0; /* check if current size is marked as a transition */ if( levels[i] ) { /* allocate a new cache structure and store values */ caches[level] = cache_createObject(); caches[level]->level = level; caches[level]->type = type; /* compute transition size and one size lower */ high = MIN_SIZE << i; low = high >> 1; show( VERB_DEBUG, "Adjusting cache level %d [%lu]...\n", level, high ); /* check for a number of points between */ for( check = low; check <= high; check += (high - low) / INTER_DIV ) { /* perform benchmark for intermediate point */ double tmp = cacheSizeAccess( sys.array, check, type, 1 ); show( VERB_DEBUG, "\tTime %.4f [size %lu]\n", tmp, check ); acum += tmp - access[i-1]; } /* check if most values are low, then transition is truly high */ if( acum / (INTER_DIV + 1) < (access[i] - access[i-1]) / 2 ) { caches[level]->size = high; if( type == TYPE_DATA ) /* Subtract overhead time for data caches */ caches[level]->latency = max( access[i] - control[i], 0 ); else caches[level]->latency = access[i] ; }else { caches[level]->size = low; if( type == TYPE_DATA ) /* Subtract overhead time for data caches */ caches[level]->latency = max( access[i-1] - control[i-1], 0 ); else caches[level]->latency = access[i-1]; } show(VERB_DEBUG,"Selected L%d cache: [%lu]\n",level,caches[level]->size); show(VERB_NORMAL,"Found L%d: [%lu]\n",level,caches[level]->size); /* increment level */ level++; } } /* store the number of levels found (one less since incremeneted in loop) */ if( type == TYPE_DATA ) sys.numDataCaches = level - 1; else sys.numInstCaches = level - 1; /* free acquired memory */ free( levels ); free( access ); free( control ); return( 0 ); } /* Run size benchmark for data or instruction caches, providing either actual or control times for comparison. array - memory to access size - size to access in bytes type - type of cache to benchmark access - whether to run real or control benchmark return - result of benchmark of trial */ static double cacheSizeAccess( char *array, unsigned int size, type_t type, int access ) { unsigned int sets; double result; /* use minimum size for instruction control times */ if( type == TYPE_INST && ! access ) size = MIN_SIZE; /* compute the number of sets needed to read the minimum between time calls */ sets = maxVal( MIN_SET / size, 1 ); /* perform the appropriate benchmark test */ if( type == TYPE_DATA ) if( access ) result = dataRead( array, size, 0, sets ); else result = ctrlRead( array, size, 0, sets ); else result = instExecute( array, size, 0, sets ); /* return the average result per operation in ns */ return( result ); } /* Determine whether the current data is valid for a given benchmark. If it is valid then another run is unecessary unless explicitly requested. Will return valid if: - Every cache (Data or Inst) has at least the params set by cacheSize Bench. - If a cache is shared, its twin (Data/Inst) has to be the same. - Cache levels have to be consecutive return - 1 if valid, 0 otherwise */ int cacheSizeValid( void ) { int i,dcaches=0,icaches=0, error=0, *count, last, run; struct cache ** cache, **ocache; /* First run checks data caches with respect to instruction caches, second run checks the icaches with respect to dcaches*/ cache = &sys.dataCaches[0]; ocache = &sys.instCaches[0]; count = &dcaches; last = -1; for( run=0; run< 2; run ++){ for( i=0; i 1 ) error ++;/* Error if there are holes in the cache hierarchy */ last = i; } /* Check that if shared, they are the same...*/ if( (cache[i] && cache[i]->type == TYPE_SHARED) && cache[i]!=ocache[i] ){ error ++; } } cache = &sys.instCaches[0]; ocache = &sys.dataCaches[0]; count = &icaches; last = -1; } /* Check if system cache numbers are correct */ if( (sys.numDataCaches != dcaches ) || (sys.numInstCaches != icaches ) ) error++; /* Return not valid (rerun test) if we don't have at least one cache in the system */ if( (dcaches+icaches) == 0 ) error++; return(! error ); } /* Check if the cache object has defined the minimum fields, that is the fields that the cacheSize test sets. cache - particular cache to check return - 1 if valid, 0 otherwise */ static int checkCache( struct cache *cache ) { if( ! (cache->level && cache->size && cache->latency) ) return 1; return 0; }