Valgrind Client Requests
Gungraun ships with its own interface to the Valgrind's Client Request Mechanism. Gungraun's client requests have zero overhead (relative to the "C" implementation of Valgrind) on many targets which are also natively supported by valgrind. In short, Gungraun provides a complete and performant implementation of Valgrind Client Requests.
Installation
Client requests are deactivated by default but can be activated with the
client_requests feature.
[dev-dependencies]
gungraun = { version = "0.17.0", features = ["client_requests"] }
If you need the client requests in your production code, you don't want them to
do anything when not running under valgrind with Gungraun benchmarks. You
can achieve that by adding Gungraun with the client_requests_defs feature
to your runtime dependencies and with the client_requests feature to your
dev-dependencies like so:
[dependencies]
gungraun = { version = "0.17.0", default-features = false, features = [
"client_requests_defs"
] }
[dev-dependencies]
gungraun = { version = "0.17.0", features = ["client_requests"] }
With just the client_requests_defs feature activated, the client requests
compile down to nothing and don't add any overhead to your production code. It
simply provides the "definitions", method signatures and macros without body.
Only with the activated client_requests feature they will be actually
executed. Note that the client requests do not depend on any other part of
Gungraun, so you could even use the client requests without the rest of
Gungraun by disabling the default-features:
gungraun = { version = "0.17.0", default-features = false, features = [
"client_requests"
] }
When building Gungraun with client requests, the valgrind header files must
exist in your standard include path (most of the time /usr/include). This is
usually the case if you've installed valgrind with your distribution's package
manager. If not, you can point the GUNGRAUN_VALGRIND_INCLUDE or
GUNGRAUN_<triple>_VALGRIND_INCLUDE environment variables to the include
path. So, if the headers can be found in /home/foo/repo/valgrind/{valgrind.h, callgrind.h, ...}, the correct include path would be
GUNGRAUN_VALGRIND_INCLUDE=/home/foo/repo (not /home/foo/repo/valgrind)
Usage
Use them in your code for example like so:
extern crate gungraun; use gungraun::client_requests; fn main() { fn main() { // Start callgrind event counting if not already started earlier client_requests::callgrind::start_instrumentation(); // do something important // Switch event counting off client_requests::callgrind::stop_instrumentation(); } }
Library Benchmarks
In library benchmarks you might need to
use EntryPoint::None in order to make the client requests work
as expected:
extern crate gungraun; use gungraun::{main, library_benchmark_group, library_benchmark}; use std::hint::black_box; pub mod my_lib { #[inline(never)] fn bubble_sort(input: Vec<i32>) -> Vec<i32> { // The algorithm input } pub fn pre_bubble_sort(input: Vec<i32>) -> Vec<i32> { println!("Doing something before the function call"); gungraun::client_requests::callgrind::start_instrumentation(); let result = bubble_sort(input); gungraun::client_requests::callgrind::stop_instrumentation(); result } } #[library_benchmark] #[bench::small(vec![3, 2, 1])] #[bench::bigger(vec![5, 4, 3, 2, 1])] fn bench_function(array: Vec<i32>) -> Vec<i32> { black_box(my_lib::pre_bubble_sort(array)) } library_benchmark_group!(name = my_group; benchmarks = bench_function); fn main() { main!(library_benchmark_groups = my_group); }
The default EntryPoint sets the --toggle-collect to the benchmark function (here bench_function) and
--collect-at-start=no. So, Callgrind starts collecting the events when
entering the benchmark function, not the moment start_instrumentation is
called. This behaviour can be remedied with EntryPoint::None:
extern crate gungraun; use gungraun::{ main, library_benchmark_group, library_benchmark, LibraryBenchmarkConfig, client_requests, EntryPoint, Callgrind }; use std::hint::black_box; pub mod my_lib { #[inline(never)] fn bubble_sort(input: Vec<i32>) -> Vec<i32> { // The algorithm input } pub fn pre_bubble_sort(input: Vec<i32>) -> Vec<i32> { println!("Doing something before the function call"); gungraun::client_requests::callgrind::start_instrumentation(); let result = bubble_sort(input); gungraun::client_requests::callgrind::stop_instrumentation(); result } } #[library_benchmark( config = LibraryBenchmarkConfig::default() .tool(Callgrind::with_args(["--collect-at-start=no"]) .entry_point(EntryPoint::None) ) )] #[bench::small(vec![3, 2, 1])] #[bench::bigger(vec![5, 4, 3, 2, 1])] fn bench_function(array: Vec<i32>) -> Vec<i32> { black_box(my_lib::pre_bubble_sort(array)) } library_benchmark_group!(name = my_group; benchmarks = bench_function); fn main() { main!(library_benchmark_groups = my_group); }
As the standard toggle is now switched off and the option
--collect-at-start=no is also omitted, you must specify
--collect-at-start=no manually in
LibraryBenchmarkConfig::raw_callgrind_args.
Please see the
docs for
more details!