home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


3.9. High-Resolution Timers

Problem

You need to measure time with a finer granularity than the full seconds that time returns.

Solution

This might not be possible. If your system supports both the syscall function in Perl as well as a system call like gettimeofday (2), then you could possibly use them to measure the time. The procedure for using syscall varies from system to system. The Discussion has sample code using it, but this is not necessarily portable.

The Time::HiRes module (available from CPAN) encapsulates this functionality for some systems:

use Time::HiRes qw(gettimeofday);
$t0 = gettimeofday;
## do your operation here
$t1 = gettimeofday;
$elapsed = $t1-$t0;
# $elapsed is a floating point value, representing number
# of seconds between $t0 and $t1

Discussion

Here's some code that uses Time::HiRes to time how long the user takes to press RETURN:

use Time::HiRes qw(gettimeofday);
print "Press return when ready: ";
$before = gettimeofday;
$line = <>;
$elapsed = gettimeofday-$before;
print "You took $elapsed seconds.\n";




Press return when ready: 








You took 0.228149 seconds.



Compare this to the equivalent syscall code:

require 'sys/syscall.ph';

# initialize the structures returned by gettimeofday
$TIMEVAL_T = "LL";
$done = $start = pack($TIMEVAL_T, ());

# prompt
print "Press return when ready: ";

# read the time into $start
syscall(&SYS_gettimeofday, $start, 0) != -1
           || die "gettimeofday: $!";

# read a line
$line = <>;

# read the time into $done
syscall(&SYS_gettimeofday, $done, 0) != -1
       || die "gettimeofday: $!";

# expand the structure
@start = unpack($TIMEVAL_T, $start);
@done  = unpack($TIMEVAL_T, $done);

# fix microseconds
for ($done[1], $start[1]) { $_ /= 1_000_000 }
    
# calculate time difference
$delta_time = sprintf "%.4f", ($done[0]  + $done[1]  )
                                         -
                              ($start[0] + $start[1] );

print "That took $delta_time seconds\n";




Press return when ready: 








That took 0.3037 seconds



It's longer because it's doing system calls in Perl, while Time::HiRes does them in C providing a single function. It's complex because directly accessing system calls peculiar to your operating system requires you to know details about the underlying C structures that the system call takes and returns. Some programs that come with the Perl distribution try to automatically calculate the formats to pack and unpack for you, if fed the appropriate C header file. In the example, sys/syscall.ph is a Perl library file generated with h2ph , which converts the sys/syscall.h header file into sys/syscall.ph that defines (among other things) &SYS_gettimeofday as a subroutine that returns the system call number of gettimeofday .

Here's another example of Time::HiRes, showing how you could use it to benchmark a sort:

use Time::HiRes qw(gettimeofday);
# take mean sorting time
$size = 500;
$number_of_times = 100;
$total_time = 0;

for ($i = 0; $i < $number_of_times; $i++) {
    my (@array, $j, $begin, $time);
    # populate array
    @array = ();
    for ($j=0; $j<$size; $j++) { push(@array, rand) }

    # sort it
    $begin = gettimeofday;
    @array = sort { $a <=> $b } @array;
    $time = gettimeofday-$begin;
    $total_time += $time;
}

printf "On average, sorting %d random numbers takes %.5f seconds\n",
    $size, ($total_time/$number_of_times);




On average, sorting 500 random numbers takes 0.02821 seconds



See Also

The documentation for the CPAN modules Time::HiRes and Benchmark; the syscall function in perlfunc (1) and Chapter 3 of Programming Perl ; your system's syscall (2) manpage