Skip to content

Instantly share code, notes, and snippets.

@aakropotkin
Last active August 26, 2020 17:50
Show Gist options
  • Save aakropotkin/2e59ad62460f46ca74a882dae6eb45ae to your computer and use it in GitHub Desktop.
Save aakropotkin/2e59ad62460f46ca74a882dae6eb45ae to your computer and use it in GitHub Desktop.
/* -*- mode: c; -*- */
/* ========================================================================= *
*
* This file has been written in order to easily wrap existing C programs,
* and test their runtime.
*
* It simply runs `main' of the wrapped program repeatedly, and provides an
* average of timed runs. Whatever it is that you actually want to time
* should be placed there, which is not always ideal for large existing
* programs; but this wrapper saves you from needing to use compilation flags to
* conditionally include/exclude a bunch of ugly timer code, or fool around with
* a timing API on a quick snippet of code that you want to test.
*
* You can set the number of runs ( Optional: 10 by default ) as an argument,
* and pass any arguments required by the wrapped program following a '--' arg.
* Ex: ./foo_wrapped 100 -- whatever args foo wants
*
* To build, compile the program you want to wrap alongside this file.
* Ex: gcc foo.c timer_wrapper.c -o foo_wrapped
*
* This file is intended to be used with GCC on a Linux system.
* It relies on GCC's Function Attribute Extensions in order to stop the
* wrapped program's `main' function to be called normally.
* If you try to use Clang you're going to have a bad time.
*
*
* The wrapper shenanigans were written by Alex Ameen, but the timer itself
* was written by Dr. Warren Hunt Jr.
*
* ------------------------------------------------------------------------- */
#include <stdio.h> /* for fprintf() and printf() */
#include <stdlib.h> /* for exit() */
#include <unistd.h> /* for sleep() */
#include <sys/times.h> /* For you know... time stuff. */
#include <stdint.h> /* To save us from 'unsigned long' */
#include <string.h> /* for strcmp() */
/* ------------------------------------------------------------------------- */
/** Function to be timed. */
extern int main( int, char * [], char ** );
/** Gives us the basename of the executable for <code>usage</code> prints */
extern char * program_invocation_short_name;
/* ------------------------------------------------------------------------- */
void
timer_usage( FILE * fd, uint8_t exitfailp )
{
fprintf( fd,
"Usage: %s [RUNS] [OPTION]... [--] [INFERIOR_ARG]...\n"
"Time the wrapped program RUNS times, passing it INFERIOR_ARGs.\n"
"Example: %s 100 -- foo bar baz\n"
"RUNS defaults to 10.\n"
"The use of '--' `%s' from consuming arguments which were intended "
"for the inferior/wrapped process.\n\n"
" -v,--verbose Verbose timing information will be printed for "
"each run\n"
" -h,--help Print this usage information\n\n",
program_invocation_short_name,
program_invocation_short_name,
program_invocation_short_name
);
if ( exitfailp ) exit( EXIT_FAILURE );
}
/* ------------------------------------------------------------------------- */
static __inline__ uint64_t
read_rdtsc( void )
{
int32_t hi, lo;
/**
* RDTSC; copies lower 32-bits of (MSR) time value to EAX and copies
* the upper 32-bits to EDX. Results are copied to hi and lo.
*/
__asm__ __volatile__ ("rdtsc":"=a" (lo), "=d" (hi));
uint64_t ans = 0;
ans = (uint64_t) hi << 32;
ans |= (uint64_t) lo;
return ans;
}
/* ------------------------------------------------------------------------- */
/**
* Attempt to (roughly) determine clock rate by measuring cycles
* elapsed while sleeping for sleeptime seconds.
*
* Note, this routine does not account for timer interrupts and
* context switches; thus, this returns a somewhat high value. On
* an otherwise unloaded system, this overage is on the order of 2
* to 6%.
*
* Also, this routine doesn't take into account possible difference
* in RDTSC values if process reads initial RDTSC counter value on
* one processor and reads second RDTSC value on another processor.
* So, this works better with a single-chip system.
*
* Recommendation: when using this code, ``pin'' this routine to one
* specific processor. Another way to improve accuracy is to make
* several test measurements, and attempt to account the variances.
*/
double
approx_mhz( uint64_t verbose, uint64_t sleeptime )
{
uint64_t start_rdtsc; /** RDTSC start value */
uint64_t end_rdtsc; /** RDTSC end value */
uint64_t rdtsc_per_second; /** RDTSC counts per second */
uint64_t approx_mhz; /** Approximate host processor MHz */
int64_t diff_rdtsc; /** end time minus start time */
start_rdtsc = read_rdtsc ();
sleep( sleeptime );
end_rdtsc = read_rdtsc();
diff_rdtsc = end_rdtsc - start_rdtsc;
if ( diff_rdtsc < 0 )
{
fprintf( stderr, "Problem with RDTSC counter." );
exit( EXIT_FAILURE );
}
rdtsc_per_second = diff_rdtsc / sleeptime;
approx_mhz = rdtsc_per_second / 1000000;
if ( verbose )
{
printf( "Start_rdtsc is: %ld.\n", start_rdtsc );
printf( "End rdtsc is: %ld.\n", end_rdtsc );
printf( "Clocks/sec is: %ld.\n", rdtsc_per_second );
printf( "Approx MHz is: %ld.\n", approx_mhz );
}
return approx_mhz;
}
/* ------------------------------------------------------------------------- */
/**
* Number of times to run, and <code>execve</code> style execution arguments.
*/
double
time_many( uint8_t verbose,
uint64_t runs,
int argc,
char * argv[],
char ** envp
)
{
uint64_t start, end, mhz, runtime;
double runtime_seconds;
char * argv0 = argv[0];
uintmax_t runtime_total = 0;
int status = EXIT_FAILURE; /** Default to failure. */
double total_seconds = 0.0;
/* Backup argv0 before changing it */
argv[0] = "wrapped_main";
mhz = approx_mhz( verbose, 1 );
for ( uint64_t i = 0; i < runs; i++ )
{
start = read_rdtsc(); /** Take a time stamp */
status = main( argc, argv, envp ); /** Run `main' */
/* Should I handle the return code?
* ...nah
*/
end = read_rdtsc(); /** Take another time stamp */
runtime = end - start;
runtime_total += runtime;
runtime_seconds = (double) ( end - start ) / (double) ( mhz * 1000000 );
total_seconds += runtime_seconds;
}
if ( verbose )
{
printf( "\nRan: `" );
for( int i = 1; i < argc; i++ ) printf( "%s", argv[i] );
printf( "' %lu times.\n", runs );
printf( "Returned: %d.\n", status );
printf( "RDTSC difference is: %llu.\n", runtime_total );
printf( "Approximate time is: %lf seconds.\n", total_seconds );
printf( "Average time: %lf seconds.\n", total_seconds / (double) runs );
}
/* Restore argv[0] */
argv[0] = argv0;
return total_seconds;
}
/* ------------------------------------------------------------------------- */
/**
* This is a fun GCC trick to "change <code>main</code>".
* The <code>__attribute__(( constructor ))</code> Function Attribute causes
* thie function to run immediately when loaded by <code>_start</code>.
* Meaning it will run before <code>main</code>, WITH <code>argc</code>,
* <code>argv</code>, and the user's environment!
* This is significantly more useful than simply changing the program's entry
* point.
* <p>
* Luckily for use this runs in that golden spot right between
* <code>_start</code> which pushes our arguments onto the stack ( among other
* things ), and <code>__libc_start_main</code> which ... well you can probably
* figure that out.
* <p>
* Because we invoke <code>exit</code> we prevent <code>main</code> from
* running "naturally", giving us a convenient way to behave almost identically
* to a run of the mill C program.
*/
__attribute__(( constructor )) int
timer_main( int argc, char * argv[], char ** envp )
{
double time_ave = 0.0;
size_t repeats = 10;
uint8_t verbose = 0;
int argc_t = argc;
char ** argv_t = argv;
/* Parse those stolen arguments */
if ( argc > 1 )
{
for ( int a = 1; a < argc; a++ )
{
printf( "argv[%d] = \"%s\"\n", a, argv[a] );
if ( ( '0' <= argv[a][0] ) && ( argv[a][0] <= '9' ) )
{
repeats = atol( argv[a] );
}
else if ( ( strcmp( argv[a], "-v" ) == 0 )
|| ( strcmp( argv[a], "--verbose" ) == 0 )
)
{
verbose = 1;
}
else if ( ( strcmp( argv[a], "-h" ) == 0 )
|| ( strcmp( argv[a], "--help" ) == 0 )
)
{
timer_usage( stdout, 0 );
exit( EXIT_SUCCESS );
}
else if ( strcmp( argv[a], "--" ) == 0 )
{
/* Remaining arguments are for `main' */
argc_t = argc - a;
argv_t = argv + a;
break;
}
else
{
/* This argument and remaining arguments are for `main' */
argc_t = argc - a + 1;
argv_t = argv + ( a - 1 );
break;
}
}
}
time_ave = time_many( verbose, repeats, argc_t, argv_t, envp ) / repeats;
printf( "Average Runtime of %zu runs was : %lf seconds.\n",
repeats,
time_ave
);
exit( EXIT_SUCCESS );
}
/* ========================================================================= */
/* vim: set filetype=c : */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment