Valgrind Client Requests

Iai-Callgrind ships with its own interface to the Valgrind's Client Request Mechanism. Iai-Callgrind'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, Iai-Callgrind 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]
iai-callgrind = { version = "0.14.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 Iai-Callgrind benchmarks. You can achieve that by adding Iai-Callgrind with the client_requests_defs feature to your runtime dependencies and with the client_requests feature to your dev-dependencies like so:

[dependencies]
iai-callgrind = { version = "0.14.0", default-features = false, features = [
    "client_requests_defs"
] }

[dev-dependencies]
iai-callgrind = { version = "0.14.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 Iai-Callgrind, so you could even use the client requests without the rest of Iai-Callgrind.

When building Iai-Callgrind 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 IAI_CALLGRIND_VALGRIND_INCLUDE or IAI_CALLGRIND_<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 IAI_CALLGRIND_VALGRIND_INCLUDE=/home/foo/repo (not /home/foo/repo/valgrind)

Usage

Use them in your code for example like so:

extern crate iai_callgrind;
use iai_callgrind::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 iai_callgrind;
use iai_callgrind::{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");
         iai_callgrind::client_requests::callgrind::start_instrumentation();

         let result = bubble_sort(input);

         iai_callgrind::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 iai_callgrind;
use iai_callgrind::{
    main, library_benchmark_group, library_benchmark, LibraryBenchmarkConfig,
    client_requests, EntryPoint
};
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");
         iai_callgrind::client_requests::callgrind::start_instrumentation();

         let result = bubble_sort(input);

         iai_callgrind::client_requests::callgrind::stop_instrumentation();
         result
     }
}

#[library_benchmark(
    config = LibraryBenchmarkConfig::default()
        .callgrind_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!