2018-11-13 19:01:59 +00:00
/*
2020-03-26 22:19:05 +00:00
* Copyright ( c ) 2016 - 2020 , Yann Collet , Facebook , Inc .
2018-11-13 19:01:59 +00:00
* All rights reserved .
*
* This source code is licensed under both the BSD - style license ( found in the
* LICENSE file in the root directory of this source tree ) and the GPLv2 ( found
* in the COPYING file in the root directory of this source tree ) .
* You may select , at your option , one of the above - listed licenses .
*/
/* *************************************
* Includes
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include <stdlib.h> /* malloc, free */
# include <string.h> /* memset */
# include <assert.h> /* assert */
2019-04-10 19:37:03 +00:00
# include "timefn.h" /* UTIL_time_t, UTIL_getTime */
2018-11-13 19:01:59 +00:00
# include "benchfn.h"
/* *************************************
* Constants
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-04-10 19:37:03 +00:00
# define TIMELOOP_MICROSEC SEC_TO_MICRO /* 1 second */
2018-11-13 19:01:59 +00:00
# define TIMELOOP_NANOSEC (1*1000000000ULL) /* 1 second */
# define KB *(1 <<10)
# define MB *(1 <<20)
# define GB *(1U<<30)
/* *************************************
2019-04-10 19:37:03 +00:00
* Debug errors
2018-11-13 19:01:59 +00:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-04-10 19:37:03 +00:00
# if defined(DEBUG) && (DEBUG >= 1)
# include <stdio.h> /* fprintf */
# define DISPLAY(...) fprintf(stderr, __VA_ARGS__)
# define DEBUGOUTPUT(...) { if (DEBUG) DISPLAY(__VA_ARGS__); }
# else
# define DEBUGOUTPUT(...)
2018-11-13 19:01:59 +00:00
# endif
/* error without displaying */
2018-11-13 20:01:17 +00:00
# define RETURN_QUIET_ERROR(retValue, ...) { \
2018-11-13 19:01:59 +00:00
DEBUGOUTPUT ( " %s: %i: \n " , __FILE__ , __LINE__ ) ; \
2018-11-13 20:01:17 +00:00
DEBUGOUTPUT ( " Error : " ) ; \
2018-11-13 19:01:59 +00:00
DEBUGOUTPUT ( __VA_ARGS__ ) ; \
DEBUGOUTPUT ( " \n " ) ; \
return retValue ; \
}
2019-06-21 22:58:55 +00:00
/* Abort execution if a condition is not met */
# define CONTROL(c) { if (!(c)) { DEBUGOUTPUT("error: %s \n", #c); abort(); } }
2018-11-13 19:01:59 +00:00
/* *************************************
* Benchmarking an arbitrary function
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int BMK_isSuccessful_runOutcome ( BMK_runOutcome_t outcome )
{
2018-11-13 20:01:17 +00:00
return outcome . error_tag_never_ever_use_directly = = 0 ;
2018-11-13 19:01:59 +00:00
}
/* warning : this function will stop program execution if outcome is invalid !
* check outcome validity first , using BMK_isValid_runResult ( ) */
BMK_runTime_t BMK_extract_runTime ( BMK_runOutcome_t outcome )
{
2019-06-21 22:58:55 +00:00
CONTROL ( outcome . error_tag_never_ever_use_directly = = 0 ) ;
2018-11-13 20:01:17 +00:00
return outcome . internal_never_ever_use_directly ;
2018-11-13 19:01:59 +00:00
}
2018-11-13 20:01:17 +00:00
size_t BMK_extract_errorResult ( BMK_runOutcome_t outcome )
{
2019-06-21 22:58:55 +00:00
CONTROL ( outcome . error_tag_never_ever_use_directly ! = 0 ) ;
2018-11-13 20:01:17 +00:00
return outcome . error_result_never_ever_use_directly ;
}
static BMK_runOutcome_t BMK_runOutcome_error ( size_t errorResult )
2018-11-13 19:01:59 +00:00
{
BMK_runOutcome_t b ;
memset ( & b , 0 , sizeof ( b ) ) ;
2018-11-13 20:01:17 +00:00
b . error_tag_never_ever_use_directly = 1 ;
b . error_result_never_ever_use_directly = errorResult ;
2018-11-13 19:01:59 +00:00
return b ;
}
static BMK_runOutcome_t BMK_setValid_runTime ( BMK_runTime_t runTime )
{
BMK_runOutcome_t outcome ;
2018-11-13 20:01:17 +00:00
outcome . error_tag_never_ever_use_directly = 0 ;
outcome . internal_never_ever_use_directly = runTime ;
2018-11-13 19:01:59 +00:00
return outcome ;
}
/* initFn will be measured once, benchFn will be measured `nbLoops` times */
/* initFn is optional, provide NULL if none */
2018-11-13 20:01:17 +00:00
/* benchFn must return a size_t value that errorFn can interpret */
2018-11-13 19:01:59 +00:00
/* takes # of blocks and list of size & stuff for each. */
/* can report result of benchFn for each block into blockResult. */
/* blockResult is optional, provide NULL if this information is not required */
2018-11-13 20:01:17 +00:00
/* note : time per loop can be reported as zero if run time < timer resolution */
2018-11-13 21:05:39 +00:00
BMK_runOutcome_t BMK_benchFunction ( BMK_benchParams_t p ,
unsigned nbLoops )
2018-11-13 19:01:59 +00:00
{
size_t dstSize = 0 ;
2018-11-13 20:01:17 +00:00
nbLoops + = ! nbLoops ; /* minimum nbLoops is 1 */
2018-11-13 19:01:59 +00:00
/* init */
{ size_t i ;
2018-11-13 21:05:39 +00:00
for ( i = 0 ; i < p . blockCount ; i + + ) {
memset ( p . dstBuffers [ i ] , 0xE5 , p . dstCapacities [ i ] ) ; /* warm up and erase result buffer */
2019-04-10 19:37:03 +00:00
} }
2018-11-13 19:01:59 +00:00
/* benchmark */
{ UTIL_time_t const clockStart = UTIL_getTime ( ) ;
unsigned loopNb , blockNb ;
2018-11-13 21:05:39 +00:00
if ( p . initFn ! = NULL ) p . initFn ( p . initPayload ) ;
2018-11-13 19:01:59 +00:00
for ( loopNb = 0 ; loopNb < nbLoops ; loopNb + + ) {
2018-11-13 21:05:39 +00:00
for ( blockNb = 0 ; blockNb < p . blockCount ; blockNb + + ) {
size_t const res = p . benchFn ( p . srcBuffers [ blockNb ] , p . srcSizes [ blockNb ] ,
p . dstBuffers [ blockNb ] , p . dstCapacities [ blockNb ] ,
p . benchPayload ) ;
2018-11-13 19:01:59 +00:00
if ( loopNb = = 0 ) {
2018-11-13 21:05:39 +00:00
if ( p . blockResults ! = NULL ) p . blockResults [ blockNb ] = res ;
if ( ( p . errorFn ! = NULL ) & & ( p . errorFn ( res ) ) ) {
2018-11-13 20:01:17 +00:00
RETURN_QUIET_ERROR ( BMK_runOutcome_error ( res ) ,
2018-11-13 19:01:59 +00:00
" Function benchmark failed on block %u (of size %u) with error %i " ,
fix confusion between unsigned <-> U32
as suggested in #1441.
generally U32 and unsigned are the same thing,
except when they are not ...
case : 32-bit compilation for MIPS (uint32_t == unsigned long)
A vast majority of transformation consists in transforming U32 into unsigned.
In rare cases, it's the other way around (typically for internal code, such as seeds).
Among a few issues this patches solves :
- some parameters were declared with type `unsigned` in *.h,
but with type `U32` in their implementation *.c .
- some parameters have type unsigned*,
but the caller user a pointer to U32 instead.
These fixes are useful.
However, the bulk of changes is about %u formating,
which requires unsigned type,
but generally receives U32 values instead,
often just for brevity (U32 is shorter than unsigned).
These changes are generally minor, or even annoying.
As a consequence, the amount of code changed is larger than I would expect for such a patch.
Testing is also a pain :
it requires manually modifying `mem.h`,
in order to lie about `U32`
and force it to be an `unsigned long` typically.
On a 64-bit system, this will break the equivalence unsigned == U32.
Unfortunately, it will also break a few static_assert(), controlling structure sizes.
So it also requires modifying `debug.h` to make `static_assert()` a noop.
And then reverting these changes.
So it's inconvenient, and as a consequence,
this property is currently not checked during CI tests.
Therefore, these problems can emerge again in the future.
I wonder if it is worth ensuring proper distinction of U32 != unsigned in CI tests.
It's another restriction for coding, adding more frustration during merge tests,
since most platforms don't need this distinction (hence contributor will not see it),
and while this can matter in theory, the number of platforms impacted seems minimal.
Thoughts ?
2018-12-22 00:19:44 +00:00
blockNb , ( unsigned ) p . srcSizes [ blockNb ] , ( int ) res ) ;
2018-11-13 19:01:59 +00:00
}
dstSize + = res ;
} }
} /* for (loopNb = 0; loopNb < nbLoops; loopNb++) */
2019-04-10 19:37:03 +00:00
{ PTime const totalTime = UTIL_clockSpanNano ( clockStart ) ;
2018-11-13 19:01:59 +00:00
BMK_runTime_t rt ;
2019-01-25 22:22:25 +00:00
rt . nanoSecPerRun = ( double ) totalTime / nbLoops ;
2018-11-13 19:01:59 +00:00
rt . sumOfReturn = dstSize ;
return BMK_setValid_runTime ( rt ) ;
} }
}
/* ==== Benchmarking any function, providing intermediate results ==== */
struct BMK_timedFnState_s {
2019-04-10 19:37:03 +00:00
PTime timeSpent_ns ;
PTime timeBudget_ns ;
PTime runBudget_ns ;
2018-11-13 19:01:59 +00:00
BMK_runTime_t fastestRun ;
unsigned nbLoops ;
UTIL_time_t coolTime ;
} ; /* typedef'd to BMK_timedFnState_t within bench.h */
BMK_timedFnState_t * BMK_createTimedFnState ( unsigned total_ms , unsigned run_ms )
{
BMK_timedFnState_t * const r = ( BMK_timedFnState_t * ) malloc ( sizeof ( * r ) ) ;
if ( r = = NULL ) return NULL ; /* malloc() error */
BMK_resetTimedFnState ( r , total_ms , run_ms ) ;
return r ;
}
2019-04-10 23:05:02 +00:00
void BMK_freeTimedFnState ( BMK_timedFnState_t * state ) { free ( state ) ; }
2018-11-13 19:01:59 +00:00
2019-04-10 22:54:55 +00:00
BMK_timedFnState_t *
BMK_initStatic_timedFnState ( void * buffer , size_t size , unsigned total_ms , unsigned run_ms )
2019-01-25 22:22:25 +00:00
{
2019-04-10 23:05:02 +00:00
typedef char check_size [ 2 * ( sizeof ( BMK_timedFnState_shell ) > = sizeof ( struct BMK_timedFnState_s ) ) - 1 ] ; /* static assert : a compilation failure indicates that BMK_timedFnState_shell is not large enough */
typedef struct { check_size c ; BMK_timedFnState_t tfs ; } tfs_align ; /* force tfs to be aligned at its next best position */
size_t const tfs_alignment = offsetof ( tfs_align , tfs ) ; /* provides the minimal alignment restriction for BMK_timedFnState_t */
2019-01-25 22:22:25 +00:00
BMK_timedFnState_t * const r = ( BMK_timedFnState_t * ) buffer ;
2019-04-10 23:05:02 +00:00
if ( buffer = = NULL ) return NULL ;
2019-01-25 22:22:25 +00:00
if ( size < sizeof ( struct BMK_timedFnState_s ) ) return NULL ;
2019-04-10 23:05:02 +00:00
if ( ( size_t ) buffer % tfs_alignment ) return NULL ; /* buffer must be properly aligned */
2019-01-25 22:22:25 +00:00
BMK_resetTimedFnState ( r , total_ms , run_ms ) ;
return r ;
}
2018-11-13 19:01:59 +00:00
void BMK_resetTimedFnState ( BMK_timedFnState_t * timedFnState , unsigned total_ms , unsigned run_ms )
{
if ( ! total_ms ) total_ms = 1 ;
if ( ! run_ms ) run_ms = 1 ;
if ( run_ms > total_ms ) run_ms = total_ms ;
timedFnState - > timeSpent_ns = 0 ;
2019-04-10 19:37:03 +00:00
timedFnState - > timeBudget_ns = ( PTime ) total_ms * TIMELOOP_NANOSEC / 1000 ;
timedFnState - > runBudget_ns = ( PTime ) run_ms * TIMELOOP_NANOSEC / 1000 ;
2019-01-25 22:22:25 +00:00
timedFnState - > fastestRun . nanoSecPerRun = ( double ) TIMELOOP_NANOSEC * 2000000000 ; /* hopefully large enough : must be larger than any potential measurement */
2018-11-13 19:01:59 +00:00
timedFnState - > fastestRun . sumOfReturn = ( size_t ) ( - 1LL ) ;
timedFnState - > nbLoops = 1 ;
timedFnState - > coolTime = UTIL_getTime ( ) ;
}
/* Tells if nb of seconds set in timedFnState for all runs is spent.
* note : this function will return 1 if BMK_benchFunctionTimed ( ) has actually errored . */
int BMK_isCompleted_TimedFn ( const BMK_timedFnState_t * timedFnState )
{
return ( timedFnState - > timeSpent_ns > = timedFnState - > timeBudget_ns ) ;
}
# undef MIN
# define MIN(a,b) ( (a) < (b) ? (a) : (b) )
# define MINUSABLETIME (TIMELOOP_NANOSEC / 2) /* 0.5 seconds */
2018-11-13 21:05:39 +00:00
BMK_runOutcome_t BMK_benchTimedFn ( BMK_timedFnState_t * cont ,
BMK_benchParams_t p )
2018-11-13 19:01:59 +00:00
{
2019-04-10 19:37:03 +00:00
PTime const runBudget_ns = cont - > runBudget_ns ;
PTime const runTimeMin_ns = runBudget_ns / 2 ;
2018-11-13 19:01:59 +00:00
int completed = 0 ;
BMK_runTime_t bestRunTime = cont - > fastestRun ;
while ( ! completed ) {
2019-04-10 19:37:03 +00:00
BMK_runOutcome_t const runResult = BMK_benchFunction ( p , cont - > nbLoops ) ;
2018-11-13 19:01:59 +00:00
if ( ! BMK_isSuccessful_runOutcome ( runResult ) ) { /* error : move out */
2018-11-13 20:01:17 +00:00
return runResult ;
2018-11-13 19:01:59 +00:00
}
{ BMK_runTime_t const newRunTime = BMK_extract_runTime ( runResult ) ;
2019-01-25 22:22:25 +00:00
double const loopDuration_ns = newRunTime . nanoSecPerRun * cont - > nbLoops ;
2018-11-13 19:01:59 +00:00
2019-01-25 22:22:25 +00:00
cont - > timeSpent_ns + = ( unsigned long long ) loopDuration_ns ;
2018-11-13 19:01:59 +00:00
/* estimate nbLoops for next run to last approximately 1 second */
if ( loopDuration_ns > ( runBudget_ns / 50 ) ) {
2019-01-25 22:22:25 +00:00
double const fastestRun_ns = MIN ( bestRunTime . nanoSecPerRun , newRunTime . nanoSecPerRun ) ;
2019-04-10 19:37:03 +00:00
cont - > nbLoops = ( unsigned ) ( runBudget_ns / fastestRun_ns ) + 1 ;
2018-11-13 19:01:59 +00:00
} else {
/* previous run was too short : blindly increase workload by x multiplier */
const unsigned multiplier = 10 ;
assert ( cont - > nbLoops < ( ( unsigned ) - 1 ) / multiplier ) ; /* avoid overflow */
cont - > nbLoops * = multiplier ;
}
if ( loopDuration_ns < runTimeMin_ns ) {
/* don't report results for which benchmark run time was too small : increased risks of rounding errors */
assert ( completed = = 0 ) ;
continue ;
} else {
if ( newRunTime . nanoSecPerRun < bestRunTime . nanoSecPerRun ) {
bestRunTime = newRunTime ;
}
completed = 1 ;
}
}
} /* while (!completed) */
return BMK_setValid_runTime ( bestRunTime ) ;
}