如何基准测试函数的内存使用情况?
How to benchmark memory usage of a function?
我注意到 Rust 的测试有一个基准模式,可以在 ns/iter
中测量执行时间,但我找不到测量内存使用的方法。
我将如何实施这样的基准?让我们暂时假设我现在只关心堆内存(尽管堆栈使用肯定也很有趣)。
编辑:我发现 this issue 要求完全相同的东西。
目前获取分配信息的唯一方法是alloc::heap::stats_print();
方法(在#![feature(alloc)]
之后),它调用jemalloc的print_stats()
.
一旦了解输出的含义,我将使用更多信息更新此答案。
(请注意,我不会接受这个答案,所以如果有人想出更好的解决方案...)
就测量数据结构大小而言,这可以通过使用 traits 和一个小的编译器插件来相当容易地完成。 Nicholas Nethercote 在他的文章 Measuring data structure sizes: Firefox (C++) vs. Servo (Rust) 中演示了它在 Servo 中的工作原理;它归结为向您关心的每种类型添加 #[derive(HeapSizeOf)]
(或偶尔手动实现)。这也是允许精确检查内存去向的好方法;然而,它相对具有侵入性,因为它需要首先进行更改,而像 jemalloc 的 print_stats()
则不需要。尽管如此,对于良好和精确的测量,这是一种合理的方法。
您可以使用 jemalloc 分配器打印分配统计信息。例如,
Cargo.toml:
[package]
name = "Whosebug-30869007"
version = "0.1.0"
edition = "2018"
[dependencies]
jemallocator = "0.3"
jemalloc-sys = {version = "0.3", features = ["stats"]}
libc = "0.2"
src/main.rs:
use libc::{c_char, c_void};
use std::ptr::{null, null_mut};
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
extern "C" fn write_cb(_: *mut c_void, message: *const c_char) {
print!("{}", String::from_utf8_lossy(unsafe {
std::ffi::CStr::from_ptr(message as *const i8).to_bytes()
}));
}
fn main() {
unsafe { jemalloc_sys::malloc_stats_print(Some(write_cb), null_mut(), null()) };
}
在单线程程序中,您应该可以很好地测量结构占用的内存量。只需打印结构创建前后的统计数据并计算差异即可。
您也可以使用 Valgrind (Massif) to get the heap profile. It works just like with any other C program. Make sure you have debug symbols enabled in the executable (e.g. using debug build or custom Cargo configuration). You can use, say, http://massiftool.sourceforge.net/ 来分析生成的堆配置文件。
(我验证了它可以在 Debian Jessie 上运行,在不同的设置中你的里程可能会有所不同)。
(为了将 Rust 与 Valgrind 一起使用,您可能必须切换回系统分配器)。
P.S。现在还有 a better DHAT.
jemalloc can be told 转储内存配置文件。您可能可以使用 Rust FFI 执行此操作,但我还没有研究过这条路线。
现在有 jemalloc_ctl
板条箱,它提供 方便的保险箱类型 API。将其添加到您的 Cargo.toml
:
[dependencies]
jemalloc-ctl = "0.3"
jemallocator = "0.3"
然后配置jemalloc
为global allocator and use methods from jemalloc_ctl::stats
模块:
这里是official example:
use std::thread;
use std::time::Duration;
use jemalloc_ctl::{stats, epoch};
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
fn main() {
loop {
// many statistics are cached and only updated when the epoch is advanced.
epoch::advance().unwrap();
let allocated = stats::allocated::read().unwrap();
let resident = stats::resident::read().unwrap();
println!("{} bytes allocated/{} bytes resident", allocated, resident);
thread::sleep(Duration::from_secs(10));
}
}
有人在这里整理了一个巧妙的小解决方案:https://github.com/discordance/trallocator/blob/master/src/lib.rs
use std::alloc::{GlobalAlloc, Layout};
use std::sync::atomic::{AtomicU64, Ordering};
pub struct Trallocator<A: GlobalAlloc>(pub A, AtomicU64);
unsafe impl<A: GlobalAlloc> GlobalAlloc for Trallocator<A> {
unsafe fn alloc(&self, l: Layout) -> *mut u8 {
self.1.fetch_add(l.size() as u64, Ordering::SeqCst);
self.0.alloc(l)
}
unsafe fn dealloc(&self, ptr: *mut u8, l: Layout) {
self.0.dealloc(ptr, l);
self.1.fetch_sub(l.size() as u64, Ordering::SeqCst);
}
}
impl<A: GlobalAlloc> Trallocator<A> {
pub const fn new(a: A) -> Self {
Trallocator(a, AtomicU64::new(0))
}
pub fn reset(&self) {
self.1.store(0, Ordering::SeqCst);
}
pub fn get(&self) -> u64 {
self.1.load(Ordering::SeqCst)
}
}
用法:(来自:https://www.reddit.com/r/rust/comments/8z83wc/comment/e2h4dp9)
// needed for Trallocator struct (as written, anyway)
#![feature(integer_atomics, const_fn_trait_bound)]
use std::alloc::System;
#[global_allocator]
static GLOBAL: Trallocator<System> = Trallocator::new(System);
fn main() {
GLOBAL.reset();
println!("memory used: {} bytes", GLOBAL.get());
{
let mut vec = vec![1, 2, 3, 4];
for i in 5..20 {
vec.push(i);
println!("memory used: {} bytes", GLOBAL.get());
}
for v in vec {
println!("{}", v);
}
}
// For some reason this does not print zero =/
println!("memory used: {} bytes", GLOBAL.get());
}
刚开始用,感觉还不错! Straight-forward,实时,不需要外部包,也不需要更改基本内存分配器。
这也很好,因为它拦截了 allocate/deallocate 调用,如果需要,您应该能够添加自定义逻辑(例如,如果内存使用量超过 X,打印 stack-trace 以查看是什么触发了分配)——虽然我还没有尝试过。
我还没有测试过这种方法会增加多少开销。如果有人对此进行测试,请告诉我!
我注意到 Rust 的测试有一个基准模式,可以在 ns/iter
中测量执行时间,但我找不到测量内存使用的方法。
我将如何实施这样的基准?让我们暂时假设我现在只关心堆内存(尽管堆栈使用肯定也很有趣)。
编辑:我发现 this issue 要求完全相同的东西。
目前获取分配信息的唯一方法是alloc::heap::stats_print();
方法(在#![feature(alloc)]
之后),它调用jemalloc的print_stats()
.
一旦了解输出的含义,我将使用更多信息更新此答案。
(请注意,我不会接受这个答案,所以如果有人想出更好的解决方案...)
就测量数据结构大小而言,这可以通过使用 traits 和一个小的编译器插件来相当容易地完成。 Nicholas Nethercote 在他的文章 Measuring data structure sizes: Firefox (C++) vs. Servo (Rust) 中演示了它在 Servo 中的工作原理;它归结为向您关心的每种类型添加 #[derive(HeapSizeOf)]
(或偶尔手动实现)。这也是允许精确检查内存去向的好方法;然而,它相对具有侵入性,因为它需要首先进行更改,而像 jemalloc 的 print_stats()
则不需要。尽管如此,对于良好和精确的测量,这是一种合理的方法。
您可以使用 jemalloc 分配器打印分配统计信息。例如,
Cargo.toml:
[package]
name = "Whosebug-30869007"
version = "0.1.0"
edition = "2018"
[dependencies]
jemallocator = "0.3"
jemalloc-sys = {version = "0.3", features = ["stats"]}
libc = "0.2"
src/main.rs:
use libc::{c_char, c_void};
use std::ptr::{null, null_mut};
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
extern "C" fn write_cb(_: *mut c_void, message: *const c_char) {
print!("{}", String::from_utf8_lossy(unsafe {
std::ffi::CStr::from_ptr(message as *const i8).to_bytes()
}));
}
fn main() {
unsafe { jemalloc_sys::malloc_stats_print(Some(write_cb), null_mut(), null()) };
}
在单线程程序中,您应该可以很好地测量结构占用的内存量。只需打印结构创建前后的统计数据并计算差异即可。
您也可以使用 Valgrind (Massif) to get the heap profile. It works just like with any other C program. Make sure you have debug symbols enabled in the executable (e.g. using debug build or custom Cargo configuration). You can use, say, http://massiftool.sourceforge.net/ 来分析生成的堆配置文件。
(我验证了它可以在 Debian Jessie 上运行,在不同的设置中你的里程可能会有所不同)。
(为了将 Rust 与 Valgrind 一起使用,您可能必须切换回系统分配器)。
P.S。现在还有 a better DHAT.
jemalloc can be told 转储内存配置文件。您可能可以使用 Rust FFI 执行此操作,但我还没有研究过这条路线。
现在有 jemalloc_ctl
板条箱,它提供 方便的保险箱类型 API。将其添加到您的 Cargo.toml
:
[dependencies]
jemalloc-ctl = "0.3"
jemallocator = "0.3"
然后配置jemalloc
为global allocator and use methods from jemalloc_ctl::stats
模块:
这里是official example:
use std::thread;
use std::time::Duration;
use jemalloc_ctl::{stats, epoch};
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
fn main() {
loop {
// many statistics are cached and only updated when the epoch is advanced.
epoch::advance().unwrap();
let allocated = stats::allocated::read().unwrap();
let resident = stats::resident::read().unwrap();
println!("{} bytes allocated/{} bytes resident", allocated, resident);
thread::sleep(Duration::from_secs(10));
}
}
有人在这里整理了一个巧妙的小解决方案:https://github.com/discordance/trallocator/blob/master/src/lib.rs
use std::alloc::{GlobalAlloc, Layout};
use std::sync::atomic::{AtomicU64, Ordering};
pub struct Trallocator<A: GlobalAlloc>(pub A, AtomicU64);
unsafe impl<A: GlobalAlloc> GlobalAlloc for Trallocator<A> {
unsafe fn alloc(&self, l: Layout) -> *mut u8 {
self.1.fetch_add(l.size() as u64, Ordering::SeqCst);
self.0.alloc(l)
}
unsafe fn dealloc(&self, ptr: *mut u8, l: Layout) {
self.0.dealloc(ptr, l);
self.1.fetch_sub(l.size() as u64, Ordering::SeqCst);
}
}
impl<A: GlobalAlloc> Trallocator<A> {
pub const fn new(a: A) -> Self {
Trallocator(a, AtomicU64::new(0))
}
pub fn reset(&self) {
self.1.store(0, Ordering::SeqCst);
}
pub fn get(&self) -> u64 {
self.1.load(Ordering::SeqCst)
}
}
用法:(来自:https://www.reddit.com/r/rust/comments/8z83wc/comment/e2h4dp9)
// needed for Trallocator struct (as written, anyway)
#![feature(integer_atomics, const_fn_trait_bound)]
use std::alloc::System;
#[global_allocator]
static GLOBAL: Trallocator<System> = Trallocator::new(System);
fn main() {
GLOBAL.reset();
println!("memory used: {} bytes", GLOBAL.get());
{
let mut vec = vec![1, 2, 3, 4];
for i in 5..20 {
vec.push(i);
println!("memory used: {} bytes", GLOBAL.get());
}
for v in vec {
println!("{}", v);
}
}
// For some reason this does not print zero =/
println!("memory used: {} bytes", GLOBAL.get());
}
刚开始用,感觉还不错! Straight-forward,实时,不需要外部包,也不需要更改基本内存分配器。
这也很好,因为它拦截了 allocate/deallocate 调用,如果需要,您应该能够添加自定义逻辑(例如,如果内存使用量超过 X,打印 stack-trace 以查看是什么触发了分配)——虽然我还没有尝试过。
我还没有测试过这种方法会增加多少开销。如果有人对此进行测试,请告诉我!