Last active
August 26, 2020 17:50
-
-
Save aakropotkin/2e59ad62460f46ca74a882dae6eb45ae to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* -*- 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