17 个版本
0.2.15 | 2023 年 9 月 11 日 |
---|---|
0.2.14 | 2023 年 3 月 8 日 |
0.2.13 | 2022 年 12 月 27 日 |
0.2.11 | 2022 年 11 月 4 日 |
0.2.3 | 2020 年 11 月 18 日 |
#129 在 内存管理
14,355 每月下载量
在 31 个 crates 中使用 (12 直接)
45KB
973 代码行
堆数据估算器。
datasize
crate 允许估算值的堆内存使用量。它通过提供或推导 DataSize
特性的实现来完成,该特性知道如何为许多 std
类型和原语计算大小。
目标是得到内存使用的合理近似,特别是对于像 Vec
这样的可变大小类型。虽然在某些情况下偏离几个字节是可以接受的,但任何用户都应该能够通过查看报告的数字轻松判断其内存是线性增长还是对数增长。
该 crate 不考虑对齐或内存布局,也不考虑分配器的异常行为或优化。它完全依赖于类型内部的数据,因此得名。
通用用法
对于实现了 DataSize
的任何类型,可以使用 data_size
便利函数来猜测其堆分配的大小
use datasize::data_size;
let data: Vec<u64> = vec![1, 2, 3];
#[cfg(feature = "std")]
assert_eq!(data_size(&data), 24);
实现该特质的类型还提供两个附加常量,IS_DYNAMIC
和 STATIC_HEAP_SIZE
。
IS_DYNAMIC
表示值的尺寸是否可以随时间变化
use datasize::DataSize;
#[cfg(feature = "std")]
// A `Vec` of any kind may have elements added or removed, so it changes size.
assert!(Vec::<u64>::IS_DYNAMIC);
// The elements of type `u64` in it are not dynamic. This allows the implementation to
// simply estimate the size as number_of_elements * size_of::<u64>.
assert!(!u64::IS_DYNAMIC);
此外,STATIC_HEAP_SIZE
表示类型将始终使用的堆内存量。一个很好的例子是 Box<u64>
-- 它将始终使用 8 字节的堆内存,但大小不会改变
use datasize::DataSize;
#[cfg(feature = "std")]
assert_eq!(Box::<u64>::STATIC_HEAP_SIZE, 8);
#[cfg(feature = "std")]
assert!(!Box::<u64>::IS_DYNAMIC);
覆盖单个字段的推导数据大小计算。
在struct中(但不包括enum!)可以覆盖单个字段的堆大小计算,这在处理第三方crate时很有用,这些crate的字段没有实现DataSize
,只需简单地用#[data_size(with = ...)]
注解并指向一个Fn(T) -> usize
函数
use datasize::DataSize;
// Let's pretend this type is from a foreign crate.
struct ThirdPartyType;
fn estimate_third_party_type(value: &Vec<ThirdPartyType>) -> usize {
// We assume every item is 512 bytes in heap size.
value.len() * 512
}
#[cfg(feature = "std")]
#[derive(DataSize)]
struct MyStruct {
items: Vec<u32>,
#[data_size(with = estimate_third_party_type)]
other_stuff: Vec<ThirdPartyType>,
}
这会自动将整个struct标记为始终是动态的,因此每次在计算MyStruct
的大小时都会调用自定义估计函数。
为自定义类型实现DataSize
可以手动为自定义类型实现DataSize
trait
struct MyType {
items: Vec<i64>,
flag: bool,
counter: Box<u64>,
}
#[cfg(feature = "std")]
impl DataSize for MyType {
// `MyType` contains a `Vec`, so `IS_DYNAMIC` is set to true.
const IS_DYNAMIC: bool = true;
// The only always present heap item is the `counter` value, which is 8 bytes.
const STATIC_HEAP_SIZE: usize = 8;
#[inline]
fn estimate_heap_size(&self) -> usize {
// We can be lazy here and delegate to all the existing implementations:
data_size(&self.items) + data_size(&self.flag) + data_size(&self.counter)
}
}
let my_data = MyType {
items: vec![1, 2, 3],
flag: true,
counter: Box::new(42),
};
#[cfg(feature = "std")]
// Three i64 and one u64 on the heap sum up to 32 bytes:
assert_eq!(data_size(&my_data), 32);
由于为struct类型实现这个 trait 很繁琐且重复,crate提供了一个方便的DataSize
宏
// Equivalent to the manual implementation above:
#[cfg(feature = "std")]
#[derive(DataSize)]
struct MyType {
items: Vec<i64>,
flag: bool,
counter: Box<u64>,
}
有关详细信息,请参阅datasize_derive
crate中的DataSize
宏文档。
性能考虑
确定数据的完整大小可能相当昂贵,尤其是在使用了多层嵌套的动态类型时。crate使用IS_DYNAMIC
和STATIC_HEAP_SIZE
在可能的情况下进行优化,因此在许多情况下不需要逐个检查向量的每个元素。
然而,如果包含的类型是动态的,则必须(并且将)检查每个元素,因此在性能问题时请记住这一点。
处理引用、Arc
和类似类型
任何引用都将被计为具有0的数据大小,因为它不拥有值。还有一些特殊的类似引用类型,如Arc
,下面将进行讨论。
Arc
和Rc
目前不支持Arc
。计划中的开发是允许用户将Arc
的实例标记为“主要”,并计算其堆内存使用情况,但目前尚未实现。
任何Arc
都将估计为具有0的堆大小,以避免循环导致无限循环。
Rc
类型以相同的方式处理。
其他类型
一些外部crate的类型可以通过功能标志使用。
fake_clock-types
:支持fake_instant::FakeClock
类型。futures-types
:来自futures
crate的一些类型。smallvec-types
:支持smallvec::SmallVec
类型。tokio-types
:来自tokio
crate的一些类型。
no_std
支持
虽然由于没有std
或至少alloc
,在大多数情况下不会存在堆,但crate支持no_std
环境。通过禁用“std”功能(通过禁用默认功能)将生成不依赖于标准库的crate版本。这可以用来为类型推导DataSize
trait,即使它们的堆大小通常是0。
数组和const泛型
默认情况下,此crate需要至少Rust版本1.51.0,以便泛型实现[T; N]数组的DataSize。此实现由"const-generics"功能标志提供,该标志默认启用。要使用较旧的Rust版本,您可以在Cargo.toml中指定default-features = false
和features = ["std"]
用于datasize
。
当禁用const-generics
功能标志时,将为小尺寸的数组以及与2的幂相关的一些较大尺寸的数组提供DataSize实现。
已知问题
当前derive宏不支持具有内联类型边界的泛型结构体,例如。
struct Foo<T: Copy> { ... }
可以通过使用等效的where
子句来解决此问题。
struct Foo<T>
where T: Copy
{ ... }
依赖关系
~1.2–2.5MB
~51K SLoC