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 模式
795 每月下载量
在 11 个 道具(8 个直接)中使用
76KB
1.5K SLoC
mem_dbg
用于递归显示值的布局和内存使用的特性和关联过程宏。
可以使用特性 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-size
、deepsize
、size-of
以及我们自己的MemSize
的结果。请注意,前两个crate只是测量项占用的空间,而不是数据结构占用的空间(即,它们没有考虑到哈希表的负载因子和2的幂次大小约束)。此外,所有其他crate的运行速度比我们的实现慢六个数量级,这是因为需要迭代所有元素。
填充
特性MemDbg
很有用,可以显示值的布局并了解每个部分使用了多少内存。特别是,它利用新的稳定宏std::mem::offset_of
以方括号显示每个字段的填充;此外,标志DbgFlags::RUST_LAYOUT
可以使它能够显示由Rust编译器使用的布局,而不是由声明顺序给出的布局。
这些功能也适用于使用特性offset_of_enum
的枚举,但是它需要夜间编译器,因为它启用了不稳定特性offset_of_enum
和offset_of_nested
。
特性
offset_of_enum
:支持填充以及为枚举提供DbgFlags::RUST_LAYOUT
标志。由于它启用了不稳定特性offset_of_enum
和offset_of_nested
,因此需要夜间编译器。如果没有启用此特性而调用带有DbgFlags::RUST_LAYOUT
标志的mem_dbg
,则会导致panic。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
注意事项
-
我们支持大多数基本类型和最多大小为十的元组。派生宏
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