-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Support State::KeepRunningBatch(). #521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
❌ Build benchmark 973 failed (commit d090cf4b56 by @sam-panzer) |
// while (state.KeepRunningBatch(1000)) { | ||
// // process 1000 elements | ||
// } | ||
bool KeepRunningBatch(size_t n); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should likely be an int
. int64_t
if we expect very large batches.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still should be an int or int64_t. So should the iterations tracking really. I'd say leave this, but i'd like the public API not to change very often, even in the signedness of a parameter.
include/benchmark/benchmark.h
Outdated
return res; | ||
} | ||
|
||
inline bool State::KeepRunning() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BENCHMARK_ALWAYS_INLINE ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
I appreciate the quick review! This is my first time using Github in years, so I apologize in advance for any procedural mistakes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like someone else to take a look before i merge, but this lgtm.
Ide like to take a look at this, but I'm busy until Friday. |
❌ Build benchmark 974 failed (commit 6629056c2e by @sam-panzer) |
State::KeepRunning() can take large amounts of time relative to quick operations (on the order of 1ns, depending on hardware). For such sensitive operations, it is recommended to run batches of repeated operations. This commit simplifies handling of total_iterations_. Rather than predecrementing such that total_iterations_ == 1 signals that KeepRunning() should exit, total_iterations_ == 0 now signals the intention for the benchmark to exit.
✅ Build benchmark 975 completed (commit de38336323 by @sam-panzer) |
✅ Build benchmark 976 completed (commit fb400d9288 by @sam-panzer) |
So, this patch actually results in worse codegen for the Sample Code: #include <benchmark/benchmark.h>
using namespace benchmark;
extern "C" void foo();
void BM_TestKeepRunning(State &S) {
while (S.KeepRunning())
foo();
} ASM Before Change: BM_TestKeepRunning(benchmark::State&): # @BM_TestKeepRunning(benchmark::State&)
pushq %rbx
movq %rdi, %rbx
cmpb $1, (%rbx)
je .LBB0_3
jmp .LBB0_2
.LBB0_4: # %while.body
callq foo
cmpb $1, (%rbx)
jne .LBB0_2
.LBB0_3: # %if.end.i
addq $-1, 8(%rbx)
jne .LBB0_4
jmp .LBB0_5
.LBB0_2: # %if.then.i
movq %rbx, %rdi
callq benchmark::State::StartKeepRunning()
addq $-1, 8(%rbx)
jne .LBB0_4
.LBB0_5: # %while.end
movq %rbx, %rdi
popq %rbx
jmp benchmark::State::FinishKeepRunning() # TAILCALL ASM After Change: BM_TestKeepRunning(benchmark::State&): # @BM_TestKeepRunning(benchmark::State&)
pushq %rbx
movq %rdi, %rbx
cmpb $1, (%rbx)
je .LBB0_3
jmp .LBB0_2
.LBB0_4: # %while.body
callq foo
cmpb $1, (%rbx)
jne .LBB0_2
.LBB0_3: # %if.end.i
movq 8(%rbx), %rax # Worse codegen starts here.
leaq -1(%rax), %rcx
movq %rcx, 8(%rbx)
testq %rax, %rax
jne .LBB0_4
jmp .LBB0_5
.LBB0_2: # %if.then.i
movq %rbx, %rdi
callq benchmark::State::StartKeepRunning()
jmp .LBB0_3
.LBB0_5: # %while.end
xorl %esi, %esi
movq %rbx, %rdi
popq %rbx
jmp benchmark::State::FinishKeepRunning(unsigned long) # TAILCALL The simple benchmark doesn't exactly show a concerning performance difference in terms of "reported iteration time", but I would still like to keep this loop as tight as possible (and an empty benchmark runs fewer iterations than it previously did). More investigation/suggestions coming later. |
I'm wondering if it might be better to formulate this as a For example: for (auto It=State.begin_batch(1000); It != State.end_batch(); ++It) {
/* Do stuff */
} Or for (auto It=State.begin_batch(); It != State.end_batch(); It += 1000) {
/* Do stuff */
} |
My goal with this PR is to unify the open-source Google Benchmark API with the internal version. KeepRunningBatch() is missing from the open-source version. I've taken a cue from the internal version to create a fast path in the common case that the benchmark has already started and will continue running. What do you think? |
❌ Build benchmark 980 failed (commit 8065d70394 by @sam-panzer) |
❌ Build benchmark 981 failed (commit 91e1d51239 by @sam-panzer) |
include/benchmark/benchmark.h
Outdated
batch_leftover_ = n - total_iterations_; | ||
total_iterations_ = 0; | ||
if (!error_occurred_) { | ||
if (total_iterations_ >= n) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how do we get here? on line 642 if total_iterations_ >= n, we return. if we're here then total_iterations_ must be < n.
i think you can avoid this check and improve the code generation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. total_iterations_ is now set to 0 when State is initialized. StartKeepRunning() can set total_iterations_ to any positive number of iterations. For example, if max_iterations is 10 and KeepRunningBatch() is given a batch size of 100, we should hit this path. I see that I can fold it into the if block just above the call to FinishKeepRunning(), though I wouldn't be surprised if it doesn't change generated code with optimizations enabled. Does this address your comment, or were you looking for something else?
On another front, how much does code generation matter this far down? The fast path should be hit every time within a benchmark except for up to two calls:
- The first call, which starts the benchmark
- The last call, either due to error or because it's the last iteration/batch.
❌ Build benchmark 982 failed (commit a81b440a8d by @sam-panzer) |
// max_iterations > 0. The first iteration is always valid. | ||
--total_iterations_; | ||
return true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can replace this with something like:
bool State::KeepRunningInternal(int batch) {
if (BENCHMARK_BUILTIN_EXPECT(total_iterations_ >= n, true)) {
total_iterations -= n;
return true;
}
if (!started_) {
StartKeepRunning();
if (!error_occurred_ && total_iterations_ >= n) {
total_iterations_ -= n;
return true;
}
}
if (total_iterations_ > 0) {
batch_leftover_ = n - total_iterations_;
total_iterations_ = 0;
return true;
}
FinishKeepRunning();
return false;
}
bool State::KeepRunning() { return KeepRunningInternal(1); }
bool State::KeepRunningBatch(int n) { return KeepRunningInternal(n); }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll send a PR to implement that.
✅ Build benchmark 983 completed (commit 7cc0b0a728 by @sam-panzer) |
This LGTM. The codegen looks amazing! Thanks. |
@dominichamon Woops, sorry I missed your last comment. Could we address it in a follow up commit since I just merged this change :-S |
bool KeepRunning(); | ||
|
||
// Returns true iff the benchmark should run n more iterations. | ||
// NOTE: A benchmark must not return from the test until KeepRunningBatch() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should note the requirement that n != 0
since we'll enter an infinite loop if a user passes 0
. And users are silly, they might attempt to do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll send a PR to assert and note it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixes are in #526.
* Support State::KeepRunningBatch(). State::KeepRunning() can take large amounts of time relative to quick operations (on the order of 1ns, depending on hardware). For such sensitive operations, it is recommended to run batches of repeated operations. This commit simplifies handling of total_iterations_. Rather than predecrementing such that total_iterations_ == 1 signals that KeepRunning() should exit, total_iterations_ == 0 now signals the intention for the benchmark to exit. * Create better fast path in State::KeepRunningBatch() * Replace int parameter with size_t to fix signed mismatch warnings * Ensure benchmark State has been started even on error. * Simplify KeepRunningBatch()
State::KeepRunning() can take large amounts of time relative to quick
operations (on the order of 1ns, depending on hardware). For such
sensitive operations, it is recommended to run batches of repeated
operations.
This commit simplifies handling of total_iterations_. Rather than
predecrementing such that total_iterations_ == 1 signals that
KeepRunning() should exit, total_iterations_ == 0 now signals the
intention for the benchmark to exit.