Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Valgrind Client Requests

Gungraun ships with its own package valgrind-requests for 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

valgrind-requests is a standalone package but is integrated and re-exported in Gungraun under the client_requests module. Client requests are deactivated by default but can be activated with the client_requests feature.

[dev-dependencies]
gungraun = { version = "0.19.2", 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 this by adding Gungraun with the client_requests_defs feature to your runtime dependencies and with the client_requests feature to your dev-dependencies:

[dependencies]
gungraun = { version = "0.19.2", default-features = false, features = [
    "client_requests_defs"
] }

[dev-dependencies]
gungraun = { version = "0.19.2", 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 “stubs”, method signatures and macros without body. Only with the activated client_requests feature they will be actually executed.

When using 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 VALGRIND_REQUESTS_VALGRIND_INCLUDE or VALGRIND_REQUESTS_<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 VALGRIND_REQUESTS_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::callgrind;

fn main() {
fn main() {
    // Start callgrind event counting if not already started earlier
    callgrind::start_instrumentation();

    // do something important

    // Switch event counting off
    callgrind::stop_instrumentation();
}
}

Library Benchmarks

In library benchmarks you might need to use EntryPoint::None to make the client requests work as expected:

extern crate gungraun;
use gungraun::prelude::*;

use std::hint::black_box;

pub mod my_lib {
    use gungraun::client_requests::callgrind;

    #[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");
        callgrind::start_instrumentation();

        let result = bubble_sort(input);

        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(black_box(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-atstart=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::prelude::*;
use gungraun::{Callgrind, EntryPoint};
use std::hint::black_box;

pub mod my_lib {
    use gungraun::client_requests::callgrind;

    #[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");
        callgrind::start_instrumentation();

        let result = bubble_sort(input);

        callgrind::stop_instrumentation();
        result
    }
}

#[library_benchmark(
    config = LibraryBenchmarkConfig::default()
        .tool(Callgrind::with_args(["--collect-atstart=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(black_box(array)))
}

library_benchmark_group!(name = my_group, benchmarks = bench_function);
fn main() {
main!(library_benchmark_groups = my_group);
}

When the standard toggle is switched off with EntryPoint::None, make sure --collect-atstart=no is set in the Callgrind arguments, for example via Callgrind::with_args(["--collect-atstart=no"]) as shown above.

Please see the docs for more details!