#! /usr/bin/perl -w use strict; use File::Basename; use FileHandle; use Getopt::Long; ########################################################################### # # # How to use: # # This program creates a lot of temporary files. I suggest you create # a new directory and run the program from there. # # The basic idea is that you write some assembly code, and designate # the code you want repeated by putting #@ at the beginning of the # lines. The number of repetitions is specified using command line # parameters --start, --stop, and --step. These parameters are used # in a for loop: for ($x = $start; $x <= $stop; $x += $step) # # NOTE: $x refers to the total number of repeated *instructions*, it # does NOT refer to the number of times the entire block of code is # repeated. Thus, the following template # # rdtsc # movl %eax, -8(%ebx) # #@ addl $1, %ebx # rdtsc # # with the parameters --start 0 --stop 3 --step 1 # will produce the following three assembly programs: # # rdtsc # movl %eax, -8(%ebx) # rdtsc # # rdtsc # movl %eax, -8(%ebx) # addl $1, %ebx # rdtsc # # rdtsc # movl %eax, -8(%ebx) # addl $1, %ebx # addl $1, %ebx # rdtsc # # # rdtsc # movl %eax, -8(%ebx) # addl $1, %ebx # addl $1, %ebx # addl $1, %ebx # rdtsc # # However, the parameters --start 0 --stop 5 --step 1 with the # following assembly: # # rdtsc # movl %eax, -8(%ebx) # #@ addl $1, %ebx # #@ addl $1, %ecx # rdtsc # # will produce the following code: # # rdtsc # movl %eax, -8(%ebx) # rdtsc # # rdtsc # movl %eax, -8(%ebx) # addl $1, %ebx # addl $1, %ecx # rdtsc # # rdtsc # movl %eax, -8(%ebx) # addl $1, %ebx # addl $1, %ecx # addl $1, %ebx # rdtsc # # rdtsc # movl %eax, -8(%ebx) # addl $1, %ebx # addl $1, %ecx # addl $1, %ebx # addl $1, %ecx # rdtsc # # rdtsc # movl %eax, -8(%ebx) # addl $1, %ebx # addl $1, %ecx # addl $1, %ebx # addl $1, %ecx # addl $1, %ebx # rdtsc # # This distinction is especially important when using instructions # that must come in pairs (e.g., push and pop). In such cases, be # sure to set --step to a multiple of 2. # # If you assembly contains %i, %i will be replaced with the batch # number for the code. This will allow you to write templates with # multiple labels. # # Finally, the code that parses the output of the comiled assembly # file looks for the output of "Difference %u" on every line. # ########################################################################### my $source_file = shift || &usage(); my $stop = 10; my $start = 0; my $step = 1; my $cleanUp = 1; my $outputKeyword = "Difference"; # String to watch for when parsing output my $warmUpIterations = 100000 - 1; # number of iterations to ignore. my $iterationsToMeasure = 900000 - 1; # number of iterations to measure. &GetOptions("start=i" => \$start, "stop=i" => \$stop, "step=i" => \$step, "cleanUp!" => \$cleanUp, "outputKeyword" => \$outputKeyword, "warmUpIterations=i" => \$warmUpIterations, "iterationsToMeasure=i" => \$iterationsToMeasure); my $prev_answer = " "; for (my $x = $start; $x <= $stop; $x += $step) { &make_file($source_file, $x); &compile_file($source_file, $x); my $ans = &run_test($source_file, $x); # #if ($prev_answer eq " ") { # print "$x\t$ans\n"; #} else { # my $diff = $ans - $prev_answer; # my $pct = sprintf("%.2f", $ans/ $x); # my $spct = sprintf("%.2f", $pct/ 0.9); # print "$x\t$ans\t$diff\t$pct\t$spct\n"; #} # my $ref_cycles = $ans; my $act_cycles = sprintf("%.0f", $ans/0.86); my $rpi = sprintf("%2.2f", $ref_cycles/ $x); my $api = sprintf("%2.2f", $act_cycles / $x); #print "$x\t$ref_cycles\t$rpi\t$act_cycles\t$api\n"; printf(" %4d %4d %.2f %4d %.2f\n", $x, $ref_cycles, $rpi, $act_cycles, $api); $prev_answer = $ans; &clean_up($source_file, $x) if ($cleanUp); } sub usage { print STDERR "Usage: time_tester.pl assembly_template.s [--start int] [--stop int] [--step int]\n"; print STDERR "Your assembly template should have some lines that begin with #@. These lines will be repeated as indicated by start, stop, and step. Note: The value of each loop refers to the total number of marked instructions, not the total number of times each block is repeated.\n"; print STDERR "\nSee comment in the source code for more details.\n"; exit(0); } ################################################################# # # Convert file names of the form # name.suffix # into # name_iter.suffix # ################################################################# sub get_new_name { my $source_file = shift; my $iter = shift; # The '\..*' syntax defines the suffix as the final dot and anything following. my ($name, undef, $suffix) = &File::Basename::fileparse($source_file, '\..*'); return ("./${name}_${iter}${suffix}", "${name}_${iter}"); } sub clean_up { my $source_file = shift; my $iter = shift; my ($file_name, $prog_name) = &get_new_name($source_file, $iter); `rm ./$file_name`; `rm ./$prog_name`; } ################################################################# # # Run the sample program # ################################################################# sub run_test { my $source_file = shift; my $iters = shift; my ($file_name, $prog_name) = &get_new_name($source_file, $iters); my %results; my $fh = FileHandle->new("./$prog_name|"); # Warm up (throw out the first few results) for (my $x = 0; $x < $warmUpIterations; $x++) { $fh->getline(); } for (my $x = 0; $x < $iterationsToMeasure; $x++) { my $line = $fh->getline(); if (!defined($line)) { print STDERR "$prog_name $x Unexpected end of Input. (Did you trash your loop counter?)\n"; last; } elsif ($line =~ /$outputKeyword\s+(\d+)/) { my $answer = $1; if (!defined($results{$answer})) { $results{$answer} = 1; } else { $results{$answer}++; } } else { print STDERR "Output line doesn't match expect format. ($outputKeyword)\n"; print STDERR $line; exit 1; } # print STDERR "$iters -- $line" } $fh->close(); # print "--\n${source_file}\n"; # my @ansvals = sort {$a <=> $b} keys (%results); # foreach my $ans (@ansvals) { print "$ans\t$results{$ans}\n";} # my @cycles_list = sort {$b <=> $a} values (%results); # foreach my $key (keys %results) # { # return $key if($results{$key} == $cycles_list[0]); # } # returns the fastest answer my @values = sort {$a <=> $b} keys (%results); # return -999 if none of the lines returned contain a valid measurement. return -999 if (@values < 1); return $values[0]; } ################################################################# # # Compile the given file using gcc # ################################################################# sub compile_file { my $source_file = shift; my $iters = shift; my ($file_name, $prog_name) = &get_new_name($source_file, $iters); `gcc $file_name -o $prog_name`; # Quit if the compile was unsuccessful if ($? != 0) { print STDERR "Compilation error!\n"; exit(0); } } ################################################################# # # Generate a test file by taking the source file and repeating the # lines of code marked by #@ $iters times. (See note at the beginning # of this file. # ################################################################# sub make_file { my $source_file = shift; my $iters = shift; # Inserts $iters into the filename before the suffix. my ($new_name) = &get_new_name($source_file, $iters); my $fhIn = FileHandle->new($source_file, "r") or die "Input file \"$source_file\" doesn't exist, or is not readable.\n"; my $fhOut = FileHandle->new($new_name, "w") or die "Out: $!"; my @comment_line; my $line; while($line = $fhIn->getline()) { my $i = 0; # remember all the lines that begin with #@ # Note: This remembers only a single group of # marked lines. while ($line =~ /^\s*\#\@/) { $line =~ s/^\s*\#\@/ /; $comment_line[$i] = $line; $line = $fhIn->getline(); $i++; } # If we found some marked lines, # repeated those lines the specified number of times. if ($i > 0) { for (my $j = 0; $j < $iters; $j++) { my $line = $comment_line[$j % $i]; # replace a %i in the code with the particular # iteration for this block my $v = int ($j / $i); $line =~ s/\%i/$v/; $fhOut->print($line); } # end for } # end if # prints the "non-special" line that ends the while loop above. $fhOut->print($line); } # end for each line $fhIn->close(); $fhOut->close(); } # end make_file