// file: powers.cc // author: R. Keller // purpose: timing of power computation functions // This program times two functions for computing powers: // slow_power uses the obvious method of repeated multiplication // fast_power uses the Russian peasant's method // Each power implementation is tried with two different number // implementations: double (double-precision floating point) // Integer (gnu Integer class) // compile with: // g++ -o powers -I ~cs/cs60/c++ powers.cc -L ~cs/cs60/lib -lm -ltimer #include // to get log (natural logarithm) #include #include #include #include "minmax.h" #include "timer.h" // compute k to the power n using the obvious method template Number slow_power(Number k, long n) { Number p = 1; while( n-- > 0 ) // loop invariant: p is k to the power n0-n p = p * k; // where n0 is the original value of n return p; } // compute k to the power n using the Russian peasant's method template Number fast_power(Number k, long n) { Number p = 1; while( n > 0 ) // loop invariant: p is k to the power N { // where N is the product of the low-order I if( n % 2 == 1 ) // powers of 2 in the binary expansion of n0, with p = p * k; // I being the number of iterations of the loop so far n = n/2; if( n == 0 ) // avoid computing k*k if done now break; k = k*k; } return p; } // testing machinery // dummy function for timing loops template Number dummy(Number k, long n) { return k; } enum Version {slow, fast}; // two versions of function // // test1 runs both slow_power and fast_power on a given number type // and value, as determined by the type of argument 'base', and // on a given exponent, 'n'. The value of 'repetitions' determines // how many times the function is called. The purpose of multiple // repetitions is to get a significant timer reading, since the timer // has a low resolution (hundredths of a second). // template void test1(Number base, // number being raised to a power long n, // power to which the number is raised long repetitions, // number of repetitions for timing Version version, // slow or fast Number & result, // the result (base to the power n) float & time) // time for the power operation { timer t; // timer object t.elapsed(); int rep; for( rep = 0; rep < repetitions; rep++ ) switch( version ) { case slow: result = slow_power(base, n); break; case fast: result = fast_power(base, n); break; } time = t.elapsed(); // time to do the power with repetitions // run empty loop to determine iteration overhead for( rep = 0; rep < repetitions; rep++ ) switch( version ) { case slow: result = dummy(base, n); break; case fast: result = dummy(base, n); break; } // t.elapsed() is time to do nothing time = (time - t.elapsed())/repetitions; // get time per function call } // // test2 calls test1 and prints results // template void test2(Version version, Number base, long n, int repetitions) { Number result; // numeric result is ignored float time; // measured time for the operation test1(base, n, repetitions, version, result, time); cout << setw(5) << n << " " << time << " " << time/log(n) << " " << time/n << " " << time/(n*log(n)) << " " << time/pow(n, 2) << " " << repetitions << endl; } // // test tests both versions of the power function for a specified // range of exponents and a specified number of repetitions // We use a power of two and a power of two minus one as two // extremes in the number of multiplications to be performed for // a given exponent. // template void test(char* type, // string printed after "Using " Version version, // fast or slow Number base, // number being raised to a power long low_exp, // lowest power to which number is raised long top_exp, // highest power to which number is raised long repetitions, // number of repetitions long rep_factor) // factor by which to divide repetitions { cout << "Using " << type << endl; cout << " time/" << endl; cout << " n 1 log n n n log n n*n reps" << endl; for( long n = low_exp; n <= top_exp; n *= 2 ) { test2(version, base, n, repetitions); repetitions = max(long(repetitions/rep_factor), long(1)); } cout << endl; } // main prints headers and calls test main() { cout << setiosflags(ios::scientific | ios::right) << setprecision(2); // test using double-precision floating point. The number of repetitions // is set very high so as to make the timer reading significant enough // to produce a value. The last argument is a repetition factor, by // which the repetitions are divided as the size of the exponent is // doubled. test("doubles slow", slow, double(7), 128, 4096, 10000, 1); test("doubles fast", fast, double(7), 128, 4096, 1000000, 1); // test using Integer. The number of repetitions is decreased by 4 each time. test("Integers slow", slow, Integer(7), 128, 8192, 256, 4); test("Integers fast", fast, Integer(7), 128, 8192, 256, 4); }