【问题标题】:How to benchmark memory usage of a function?如何对函数的内存使用情况进行基准测试?
【发布时间】:2015-09-07 03:56:29
【问题描述】:

我注意到 Rust 的测试有一个基准模式,可以测量 ns/iter 中的执行时间,但我找不到测量内存使用情况的方法。

我将如何实施这样的基准?让我们暂时假设我现在只关心堆内存(尽管堆栈的使用肯定也很有趣)。

编辑:我发现 this issue 要求完全相同的东西。

【问题讨论】:

  • 我怀疑泛型方法(在 C/C++ 中也可用)会起作用,但我从未找到以泛型方式进行测量的细粒度方法:(
  • @Matthieu M. 是的,这可行,但要求我将所有基准测试方法分解为单独的二进制文件,这很麻烦。此外,它可能会或可能不会给出正确的结果。
  • 要从程序内部执行此操作,我希望您必须等到分配器可插入。然后你必须确保你所做的每个堆分配都使用一个提供的分配器,然后实现一个分配器来跟踪在任何给定时间借出多少内存。我希望 valgrind 的内存跟踪与 jemalloc 一起工作......

标签: memory rust benchmarking


【解决方案1】:

现在有 jemalloc_ctl crate 提供方便的安全类型化 API。将其添加到您的Cargo.toml

[dependencies]
jemalloc-ctl = "0.3"
jemallocator = "0.3"

然后将jemalloc配置为global allocator,并使用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));
    }
}

【讨论】:

  • 有没有办法打印/可视化 stack 内存状态?
  • @invis 最好在另一个(新)问题中提出。但一种可能的解决方案是在main 函数的开头创建变量,并通过引用该变量来获取指针。稍后当您想要测量当前线程的堆栈大小时,您可以创建另一个变量并引用第二个变量。堆栈大小是这两个指针之间的差异。
【解决方案2】:

您可以使用 jemalloc 分配器来打印分配统计信息。例如,

Cargo.toml:

[package]
name = "stackoverflow-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) 来获取堆配置文件。它与任何其他 C 程序一样工作。确保您在可执行文件中启用了调试符号(例如,使用调试构建或自定义 Cargo 配置)。例如,您可以使用 http://massiftool.sourceforge.net/ 来分析生成的堆配置文件。

(我已验证这可以在 Debian Jessie 上运行,在不同的设置中,您的里程可能会有所不同)。

(为了在 Valgrind 中使用 Rust,您可能必须切换回系统分配器)。

附:现在还有a better DHAT


jemalloc can be told 转储内存配置文件。你可能可以用 Rust FFI 做到这一点,但我还没有研究过这条路线。

【讨论】:

  • 我在调试模式下编译(使用cargo build),但在massif 转储中看不到行号。我也尝试了rustc -g,结果相同。你知道为什么吗?
  • @antoyo 尝试使用extern crate alloc_system;切换到系统分配器。
  • 还有jemalloc_ctl crate,提供安全方便的类型化API(例如jemalloc_ctl::stats::resident
【解决方案3】:

就测量数据结构大小而言,这可以通过使用特征和一个小型编译器插件相当容易地完成。 Nicholas Nethercote 在他的文章Measuring data structure sizes: Firefox (C++) vs. Servo (Rust) 中演示了它在 Servo 中是如何工作的;它归结为将#[derive(HeapSizeOf)](或偶尔手动实现)添加到您关心的每种类型。这也是允许精确检查内存去向的好方法;然而,它相对具有侵入性,因为它首先需要进行更改,而像 jemalloc 的 print_stats() 这样的东西则不需要。尽管如此,对于良好和精确的测量,这是一种合理的方法。

【讨论】:

  • 这是一个很好的观点,如果我们想测量特定结构的内存使用情况,那就太好了。但是,正如您所说,它非常具有侵入性(尽管对于许多用例来说这可能是可以的),并且它不一定能了解全部情况(因为可能存在存储数据的侧通道,例如全局表)。
  • @lllogiq:正如 Nicholas 所提到的,处理共享所有权等问题仍然是一个悬而未决的问题,但您可以决定如何处理它。特征使得决定如何实现这样的事情变得非常容易。你可以用你选择的任何方式来处理这些事情。
  • 很公平。我真的很喜欢这种方法,它提供了很多控制。但是,它也有点挑剔并且容易出错。因此,虽然在许多情况下它可能是合理的,但这不是我要寻找的解决方案。
【解决方案4】:

目前,获取分配信息的唯一方法是alloc::heap::stats_print();方法(在#![feature(alloc)]后面),它调用了jemalloc的print_stats()

了解输出的含义后,我将使用更多信息更新此答案。

(请注意,我不会接受这个答案,所以如果有人提出更好的解决方案......)

【讨论】:

  • 您提到不喜欢其他答案“因为可能存在存储数据的侧通道”,但请注意,此答案仅跟踪来自 jemalloc 的内存,因此如果您的函数调用使用任何其他的 C 代码分配器,它不会包含在此报告中。
  • 是的。这是一个警告,一旦我们处理非 rust 代码(或者一旦我们有可插入的分配器,另一方面这可能会使整个问题变得毫无意义),基于 jemalloc 的方法就变得不那么有用了。我对这两个答案都不太满意的另一个原因。
  • alloc::heap::stats_print() 函数在当前 rust 中不再存在(例如 1.42)
  • @diralik 你知道用什么代替吗?
  • @invis 我用的是jemalloc_ctl,这里是details
猜你喜欢
  • 2015-03-06
  • 1970-01-01
  • 1970-01-01
  • 2011-11-01
  • 1970-01-01
  • 2012-05-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多