#内存布局 #内存 #内存大小 #调试 #分配 #数据结构

no-std mem_dbg

用于递归显示值的布局和内存使用的特性和关联过程宏

14 个版本

0.2.4 2024 年 8 月 9 日
0.2.2 2024 年 6 月 3 日
0.1.4 2024 年 3 月 18 日
0.1.3 2023 年 12 月 15 日
0.1.1 2023 年 11 月 30 日

#126 in Rust 模式

Download history 158/week @ 2024-05-03 152/week @ 2024-05-10 135/week @ 2024-05-17 407/week @ 2024-05-24 307/week @ 2024-05-31 121/week @ 2024-06-07 126/week @ 2024-06-14 125/week @ 2024-06-21 59/week @ 2024-06-28 62/week @ 2024-07-05 77/week @ 2024-07-12 100/week @ 2024-07-19 186/week @ 2024-07-26 105/week @ 2024-08-02 330/week @ 2024-08-09 162/week @ 2024-08-16

795 每月下载量
11 道具(8 个直接)中使用

Apache-2.0 OR LGPL-2.1-or-later

76KB
1.5K SLoC

mem_dbg

downloads dependents GitHub CI license Latest version Documentation

用于递归显示值的布局和内存使用的特性和关联过程宏。

可以使用特性 MemDbg 来显示值的递归布局,包括每个部分的尺寸和相关的填充字节。我们为大多数基本类型提供了实现,为字段实现了 MemDbg 的结构体和枚举提供了 derive 宏,并支持一些其他库。

为了计算尺寸,我们提供了特性 MemSize 和一个 derive 宏,可以用来计算值的字节大小,就像标准库函数 std::mem::size_of 返回类型在字节中的堆栈大小一样,但它不考虑堆内存。

为什么是 MemSize

其他特性部分提供了 MemSize 的功能,但它们可能需要手动实现一个特性,这容易出错,或者它们没有为 MemDbg 提供足够的灵活性。最重要的是,MemSize 使用类型系统来避免在不需要时遍历容器的内容(例如向量等),这使得能够即时计算占用数百 GB 堆内存的值的尺寸。

这是 bench_hash_map 基准测试的结果,该测试包含在 examples 目录中。它构建了一个包含一亿条条目的哈希表,然后测量其堆大小

Allocated:    2281701509
get_size:     1879048240 152477833 ns
deep_size_of: 1879048240 152482000 ns
size_of:      2281701432 152261958 ns
mem_size:     2281701424 209 ns

第一行是程序分配的字节数,由cap返回。然后,我们显示get-sizedeepsizesize-of以及我们自己的MemSize的结果。请注意,前两个crate只是测量项占用的空间,而不是数据结构占用的空间(即,它们没有考虑到哈希表的负载因子和2的幂次大小约束)。此外,所有其他crate的运行速度比我们的实现慢六个数量级,这是因为需要迭代所有元素。

填充

特性MemDbg很有用,可以显示值的布局并了解每个部分使用了多少内存。特别是,它利用新的稳定宏std::mem::offset_of以方括号显示每个字段的填充;此外,标志DbgFlags::RUST_LAYOUT可以使它能够显示由Rust编译器使用的布局,而不是由声明顺序给出的布局。

这些功能也适用于使用特性offset_of_enum的枚举,但是它需要夜间编译器,因为它启用了不稳定特性offset_of_enumoffset_of_nested

特性

  • offset_of_enum:支持填充以及为枚举提供DbgFlags::RUST_LAYOUT标志。由于它启用了不稳定特性offset_of_enumoffset_of_nested,因此需要夜间编译器。如果没有启用此特性而调用带有DbgFlags::RUST_LAYOUT标志的mem_dbg,则会导致panic。
  • half:支持halfcrate。
  • maligned:支持malignedcrate。
  • mmap-rs:支持mmap-rscrate。
  • rand:支持randcrate。

示例

# #![cfg_attr(feature = "offset_of_enum", feature(offset_of_enum, offset_of_nested))]
# fn main() -> Result<(), Box<dyn std::error::Error>> {
use mem_dbg::*;

#[derive(MemSize, MemDbg)]
struct Struct<A, B> {
    a: A,
    b: B,
    test: isize,
}

#[derive(MemSize, MemDbg)]
struct Data<A> {
    a: A,
    b: Vec<i32>,
    c: (u8, String),
}

#[derive(MemSize, MemDbg)]
enum TestEnum {
    Unit,
    Unit2(),
    Unit3 {},
    Unnamed(usize, u8),
    Named { first: usize, second: u8 },
}

let b = Vec::with_capacity(100);

let s = Struct {
    a: TestEnum::Unnamed(0, 16),
    b: Data {
        a: vec![0x42_u8; 700],
        b,
        c: (1, "foo".to_owned()),
    },
    test: -0xbadf00d,
};

println!("size:     {}", s.mem_size(SizeFlags::default()));
println!("capacity: {}", s.mem_size(SizeFlags::CAPACITY));
println!();

s.mem_dbg(DbgFlags::empty())?;

println!();

println!("size:     {}", s.mem_size(SizeFlags::default()));
println!("capacity: {}", s.mem_size(SizeFlags::CAPACITY));
println!();

s.mem_dbg(DbgFlags::default() | DbgFlags::CAPACITY | DbgFlags::HUMANIZE)?;

#[cfg(feature = "offset_of_enum")]
{
    println!();

    println!("size:     {}", s.mem_size(SizeFlags::default()));
    println!("capacity: {}", s.mem_size(SizeFlags::CAPACITY));
    println!();

    s.mem_dbg(DbgFlags::empty() | DbgFlags::RUST_LAYOUT)?;
}
# Ok(())
# }

前面的程序打印

size:     807
capacity: 1207

807 B ⏺
 16 B ├╴a
      │ ├╴Variant: Unnamed
  8 B │ ├╴0
  1 B │ ╰╴1
783 B ├╴b
724 B │ ├╴a
 24 B │ ├╴b
 35 B │ ╰╴c
  1 B │   ├╴0 [7B]
 27 B │   ╰╴1
  8 B ╰╴test

size:     807
capacity: 1207

1.207 kB 100.00%: readme::main::Struct<readme::main::TestEnum, readme::main::Data<alloc::vec::Vec<u8>>>
   16  B   1.33% ├╴a: readme::main::TestEnum
                 │ ├╴Variant: Unnamed
    8  B   0.66% │ ├╴0: usize
    1  B   0.08% │ ╰╴1: u8
1.183 kB  98.01% ├╴b: readme::main::Data<alloc::vec::Vec<u8>>
  724  B  59.98% │ ├╴a: alloc::vec::Vec<u8>
  424  B  35.13% │ ├╴b: alloc::vec::Vec<i32>
   35  B   2.90% │ ╰╴c: (u8, alloc::string::String)
    1  B   0.08% │   ├╴0: u8 [7B]
   27  B   2.24% │   ╰╴1: alloc::string::String
    8  B   0.66% ╰╴test: isize

如果使用特性offset_of_enum运行,它将打印

size:     807
capacity: 1207

807 B ⏺
 16 B ├╴a
      │ ├╴Variant: Unnamed
  8 B │ ├╴0
  1 B │ ╰╴1 [6B]
783 B ├╴b
724 B │ ├╴a
 24 B │ ├╴b
 35 B │ ╰╴c
  1 B │   ├╴0 [7B]
 27 B │   ╰╴1
  8 B ╰╴test

size:     807
capacity: 1207

1.207 kB 100.00% ⏺: readme::main::Struct<readme::main::TestEnum, readme::main::Data<alloc::vec::Vec<u8>>>
   16  B   1.33% ├╴a: readme::main::TestEnum
                 │ ├╴Variant: Unnamed
    8  B   0.66% │ ├╴0: usize
    1  B   0.08% │ ╰╴1: u8 [6B]
1.183 kB  98.01% ├╴b: readme::main::Data<alloc::vec::Vec<u8>>
  724  B  59.98% │ ├╴a: alloc::vec::Vec<u8>
  424  B  35.13% │ ├╴b: alloc::vec::Vec<i32>
   35  B   2.90% │ ╰╴c: (u8, alloc::string::String)
    1  B   0.08% │   ├╴0: u8 [7B]
   27  B   2.24% │   ╰╴1: alloc::string::String
    8  B   0.66% ╰╴test: isize

size:     807
capacity: 1207

807 B ⏺
783 B ├╴b
724 B │ ├╴a
 24 B │ ├╴b
 35 B │ ╰╴c
  1 B │   ├╴0 [7B]
 27 B │   ╰╴1
 16 B ├╴a
      │ ├╴Variant: Unnamed
  1 B │ ├╴1 [6B]
  8 B │ ╰╴0
  8 B ╰╴test

注意事项

  • 我们支持大多数基本类型和最多大小为十的元组。派生宏MemSize/MemDbg将为那些字段实现了相关接口的结构体和枚举生成实现:如果这不是这种情况(例如,因为孤儿规则),则可以手动实现特性。

  • 如果您在共享引用上调用此crate的方法,编译器将自动解引用它,并将方法调用到引用的类型

# fn main() -> Result<(), Box<dyn std::error::Error>> {
use mem_dbg::*;

let mut x: [i32; 4] = [0, 0, 0, 0];

assert_eq!(
    (&x).mem_size(SizeFlags::default()),
    std::mem::size_of::<[i32; 4]>()
);

assert_eq!(
    (&mut x).mem_size(SizeFlags::default()),
    std::mem::size_of::<&mut [i32; 4]>()
);

assert_eq!(
    <&[i32; 4] as MemSize>::mem_size(&&x, SizeFlags::default()),
    std::mem::size_of::<&[i32; 4]>()
);
# Ok(())
# }
  • 数组的、切片和向量的尺寸计算将通过遍历其元素来执行,除非类型是包含非'static引用的副本类型,并且使用属性#[copy_type]声明为这样。有关更多详细信息,请参阅CopyType。(见CopyType)。

  • 向量和切片的内容不会递归展开,因为输出可能太复杂;如果出现有趣的使用案例,这可能在将来(例如,通过一个标志)改变。

  • 目前不支持BTreeMap/BTreeSet,因为我们仍然需要找出精确测量它们内存大小和容量的方法。

依赖关系

~0–29MB
~368K SLoC