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
769 下载/月
用于 mem_dbg
33KB
349 行
mem_dbg
特质和关联的过程宏,用于显示值的递归布局和内存使用。
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-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
。使用带有标志的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