6个版本

0.1.6 2024年8月9日
0.1.5 2024年6月3日
0.1.4 2024年4月27日
0.1.2 2023年12月4日
0.1.0 2023年11月30日

#18 in #memory-size

Download history 146/week @ 2024-05-01 125/week @ 2024-05-08 144/week @ 2024-05-15 239/week @ 2024-05-22 272/week @ 2024-05-29 201/week @ 2024-06-05 106/week @ 2024-06-12 140/week @ 2024-06-19 73/week @ 2024-06-26 48/week @ 2024-07-03 59/week @ 2024-07-10 108/week @ 2024-07-17 179/week @ 2024-07-24 141/week @ 2024-07-31 264/week @ 2024-08-07 165/week @ 2024-08-14

769 下载/月
用于 mem_dbg

Apache-2.0 OR LGPL-2.1-or-later

33KB
349

mem_dbg

downloads dependents GitHub CI license Latest version Documentation

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

MemDbg 特质可以用于显示值的递归布局,包括每个部分的大小和相关的填充字节。我们为大多数基本类型提供了实现,为字段实现 MemDbg 的结构和枚举提供了 derive 宏,并支持几个其他 crate。

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

为什么需要 MemSize

其他特质部分地提供了 MemSize 的功能,但它们要么需要手动实现特质,这容易出错,要么没有提供足够的灵活性来满足 MemDbg 的需求。最重要的是,MemSize 使用类型系统来避免在不需要时迭代容器的内容(向量等),从而能够即时计算占用数百GB堆内存的值的大小。

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

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。使用带有标志的mem_dbg调用此功能将导致恐慌。
  • half:支持half crate。
  • maligned:支持maligned crate。
  • mmap-rs:支持mmap-rs crate。
  • rand:支持rand crate。

示例

# #![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

注意事项

  • 我们支持大多数基本类型和大小不超过十的元组。对于实现相关接口的struct和enum, derive宏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 [链接]

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

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

依赖项

~255–700KB
~17K SLoC