in reply to Testing a number for oddness

It's kind of funny, runrig even mentioned that when benchmarking you should exclude operations that don't concern the thing you are trying to test, yet every single example called their "perl_and/mod" function for one operation. Don't you think the function call from timethese/timeit to perl_and/mod is taking time?

So I did it this way:

sub perl_mod { for my $z (1..10_000) { $_[0] % 2; } } sub perl_and { for my $z (1..10_000) { $_[0] & 1; } } timethese(10_000, { perl_mod => sub { perl_mod($num) }, perl_and => sub { perl_and($num) }, });

and got the following results:

nicholas(/5)@neko [106 /<1>nicholas/tmp] > ./time Benchmark: timing 10000 iterations of perl_and, perl_mod... perl_and: 210 wallclock secs (209.32 usr + 0.79 sys = 210.11 CPU) perl_mod: 260 wallclock secs (258.62 usr + 0.93 sys = 259.55 CPU) nicholas(/5)@neko [107 /<1>nicholas/tmp] >

Which shows the perl_and to be around 20% faster than perl_mod: about what I would expect.

Then, I did a pure C test:

#include <stdio.h> void c_and(int i) { int a; for (a=0;a<10000;a++) { i & 1; } } void c_mod(int i) { int a; for (a=0;a<10000;a++) { i % 2; } } int main(void) { int a; for (a=0;a<100000;a++) { c_and(a); } //for (a=0;a<100000;a++) { // c_mod(a); //} exit(0); }
and ran it seperately for each of the types (I was using /bin/time to test):

For c_and: 29.800u 0.040s 0:29.84 100.0% 0+0k 0+0io 69pf+0w
and
for c_mod: 29.540u 0.390s 0:29.99 99.7% 0+0k 0+0io 69pf+0w

Which again shows and to be faster, though no wheres as much of a difference as with perl (I presume gcc is optimized).

test platform: dual PII-233 with a load avg of around 7 :)

Replies are listed 'Best First'.
Re: Re: Testing a number for oddness
by Anonymous Monk on Jan 23, 2001 at 22:41 UTC
    gcc is OBVIOUSLY optimized. % is always craploads slower than &. On a modern processor, a multiply, add, subtract, bitshift, etc., can be done in one cycle (assuming all operands are already loaded into registers), whereas a divide could take 10-50 or even more cycles.

    To get gcc to not optimize anything away do something like this:

    #include <stdio.h> volatile int yb; int foo; void blah(int t) { foo = (t==0)? 1: 2; } int main(int argc, char **argv) { int x = 0xdeadbeef; volatile int *y = &yb; int i; int t = atoi(argv[1]); blah(t); if (t == 0) { printf("Using the & method\n"); for (i=0;i<100000000;++i) *y=x & foo; } else { printf("Using the %% method\n"); for (i=0;i<100000000;++i) *y=x % foo; } return 0; }
    NOTES: x is initialized with a non-zero value. On x86 I have noticed sometimes ALU/Mult operations on zero-valued inputs can be significantly faster than on non-zero inputs.

    y is a volatile pointer to prevent gcc from optimizing the loops away, if it was so inclined.

    'foo' is not a constant to prevent gcc from optimizing an odd/even test.

    'foo' is initialized in a non-static function to prevent gcc from (if it could/would do this) optimizing the loops to odd/even test anyway.

    We do 100 million iterations, to drown out any noise.

    Running on an unloaded 933Mhz P-III:

    % time ./foo 0 Using the & method 0.77user 0.01system 0:00.80elapsed 96%CPU (blah blah) % time ./foo 1 Using the % method 4.29user 0.00system 0:04.43elapsed 96%CPU (blah blah)

    I ran the tests about 10 times each and the results are about the same.

    Now if you are just doing a odd-even test, gcc will likely be able to optimize it for you to the point that you can use whatever method you want and it will be fast. If you programming on a DSP, however, or maybe in a super-high-speed driver or something, you'd want to make sure you use the fast version, cuz the compiler in that case likely can't help you (or you don't want it to).

    And in Perl anyway, you are operating many levels about the underlying machine architecture. % vs & is the least of your worries speed-wise.

Re: Re: Testing a number for oddness
by runrig (Abbot) on Jan 24, 2001 at 05:58 UTC
    See the Benchmark docs. It factors out the null loop, so even looping within your subroutine adds operations that you'd rather not time (for '&' and '%' anyway; if you are timing more expensive operations/algorithms then it is not significant). See this:
    #!/usr/local/bin/perl -w use strict; use Benchmark; my $i = 34; timethese(3000000, { AND=>\&and_them, MOD=>\&mod_them, NULL_LOOP=>\&null_loop, }); sub null_loop { } sub and_them { $i & 1; } sub mod_them { $i % 2; } ~ "tst" 23 lines, 229 characters [rover:DEV]~/tst>./tst Benchmark: timing 3000000 iterations of AND, MOD, NULL_LOOP ... AND: 1 wallclock secs ( 1.68 usr + 0.00 sys = 1.68 CPU) @ 17 +85714.29/s (n=3000000) MOD: 2 wallclock secs ( 2.33 usr + 0.00 sys = 2.33 CPU) @ 12 +87553.65/s (n=3000000) NULL_LOOP: -1 wallclock secs (-0.07 usr + 0.00 sys = -0.07 CPU) @ -4 +2857142.86/s (n=3000000) (warning: too few iterations for a reliable count)
    (I realize that in the end it really doesn't matter, I'd probably use '%' anyway, and this is all just for the sake of discusssion/fun/curiousity).