Private measurement function. This function is a bit unusual in that it throws exceptions; the intention is to abort the measurement when an unreliable result is detected, but to also provide some useful context as to which benchmark was being run. The method used to determine whether the measurement is unreliable is detecting context switches using getrusage(). Pass micro_heuristics=false to t
| 40 | // is detecting context switches using getrusage(). Pass micro_heuristics=false to |
| 41 | // the class constructor if you do not want this behavior. |
| 42 | double Benchmark::Measure(BenchmarkFunction function, void* args, |
| 43 | int max_time, int batch_size, bool micro_heuristics) { |
| 44 | int64_t target_cycles = CpuInfo::cycles_per_ms() * max_time; |
| 45 | int64_t iters = 0; |
| 46 | struct rusage ru_start; |
| 47 | struct rusage ru_stop; |
| 48 | StopWatch sw; |
| 49 | int64_t lost_time = 0; |
| 50 | |
| 51 | // Run it with the default batch size to roughly estimate how many iterations |
| 52 | // it will take. |
| 53 | for (;;) { |
| 54 | int64 begin_time = sw.ElapsedTime(); |
| 55 | if (micro_heuristics) getrusage(RUSAGE_THREAD, &ru_start); |
| 56 | sw.Start(); |
| 57 | function(batch_size, args); |
| 58 | sw.Stop(); |
| 59 | if (micro_heuristics) getrusage(RUSAGE_THREAD, &ru_stop); |
| 60 | if (!micro_heuristics || ru_stop.ru_nivcsw == ru_start.ru_nivcsw) { |
| 61 | iters = batch_size; |
| 62 | break; |
| 63 | } |
| 64 | |
| 65 | // Taking too long and we keep getting switched out; either the machine is busy, |
| 66 | // or the benchmark takes too long to run and should not be used with this |
| 67 | // microbenchmark suite. Bail. |
| 68 | if (sw.ElapsedTime() > target_cycles) { |
| 69 | throw std::runtime_error("Benchmark failed to complete due to context switching."); |
| 70 | } |
| 71 | |
| 72 | // Divide the batch size by the number of context switches until we find a size |
| 73 | // small enough that we don't switch |
| 74 | batch_size = max<int>(1, batch_size / (1+(ru_stop.ru_nivcsw - ru_start.ru_nivcsw))); |
| 75 | lost_time += sw.ElapsedTime() - begin_time; |
| 76 | } |
| 77 | |
| 78 | double iters_guess = (target_cycles / (sw.ElapsedTime() - lost_time)) * batch_size; |
| 79 | // Shoot for 110% of the guess. Going a little over is not a big deal. |
| 80 | iters_guess *= 1.1; |
| 81 | // Modify the batch size based on the guess. We ran the function a small number |
| 82 | // of times to estimate how fast the function is. Run the remaining iterations at |
| 83 | // in 20% increments. |
| 84 | batch_size = max<int>(1, (iters_guess - iters) / 5); |
| 85 | |
| 86 | while (sw.ElapsedTime() < target_cycles) { |
| 87 | int64 begin_time = sw.ElapsedTime(); |
| 88 | if (micro_heuristics) getrusage(RUSAGE_THREAD, &ru_start); |
| 89 | sw.Start(); |
| 90 | function(batch_size, args); |
| 91 | sw.Stop(); |
| 92 | if (micro_heuristics) getrusage(RUSAGE_THREAD, &ru_stop); |
| 93 | if (!micro_heuristics || ru_stop.ru_nivcsw == ru_start.ru_nivcsw) { |
| 94 | iters += batch_size; |
| 95 | } else { |
| 96 | // We could have a vastly different estimate for batch size now and might have |
| 97 | // started context switching again. Divide down by 1 + the number of context |
| 98 | // switches as a guess of the number of iterations to perform with each batch. |
| 99 | lost_time += sw.ElapsedTime() - begin_time; |
no test coverage detected