3 个版本 (破坏性)
0.3.0 | 2021 年 8 月 2 日 |
---|---|
0.2.0 | 2021 年 8 月 2 日 |
0.1.0 | 2021 年 8 月 2 日 |
#127 在 缓存
28KB
177 代码行
刺穿
避免嵌套智能指针中的双重间接引用。
Pierce
结构允许您缓存双重嵌套智能指针的解引用结果。
快速示例
use std::sync::Arc;
use pierce::Pierce;
let vec: Vec<i32> = vec![1, 2, 3];
let arc_vec = Arc::new(vec);
let pierce = Pierce::new(arc_vec);
// Here, the execution jumps directly to the slice to call `.get(...)`.
// Without Pierce it would have to jump to the Vec first,
// than from the Vec to the slice.
pierce.get(0).unwrap();
嵌套智能指针
智能指针可以嵌套以组合其功能。例如,使用 Arc<Vec<i32>>
,一个 i32 的切片由外部的 Vec
管理,该 Vec
又被 Arc
包装。
然而,嵌套会带来 双重间接引用 的代价:当我们想要访问底层数据时,我们必须首先跟随外部指针到内部指针所在的位置,然后跟随内部指针到底层数据所在的位置。两次 deref
。两次跳跃。
use std::sync::Arc;
let vec: Vec<i32> = vec![1, 2, 3];
let arc_vec = Arc::new(vec);
// Here, the `Arc<Vec<i32>>` is first dereferenced to the `Vec<i32>`,
// then the Vec is dereferenced to the underlying i32 slice,
// on which `.get(...)` is called.
arc_vec.get(0).unwrap();
刺穿
此 crate 提供的 Pierce
结构可以减少嵌套智能指针的性能成本,通过 缓存解引用结果。我们最初对嵌套智能指针进行双重解引用,存储内部指针指向的地址。然后我们可以通过仅跳转到存储的地址来访问底层数据。一次跳跃。
以下是可能看起来是这样的图示。
┌───────────────────────────┬───────────────────────────────┬──────────────────────────────────────────┐
│ Stack │ Heap │ Heap │
┌────────────┼───────────────────────────┼───────────────────────────────┼──────────────────────────────────────────┤
│ T │ │ │ │
│ │ ┌──────────────────┐ │ ┌───────────────────┐ │ ┌──────────────────────────────────┐ │
│ │ │Outer Pointer │ │ │Inner Pointer │ │ │Target │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ T ────────────────────────► T::Target ─────────────────► <T::Target as Deref>::Target │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ └──────────────────┘ │ └───────────────────┘ │ └──────────────────────────────────┘ │
│ │ │ │ │
├────────────┼───────────────────────────┼───────────────────────────────┼──────────────────────────────────────────┤
│ Pierce<T> │ │ │ │
│ │ ┌──────────────────┐ │ ┌───────────────────┐ │ ┌──────────────────────────────────┐ │
│ │ │Outer Pointer │ │ │Inner Pointer │ │ │Target │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ T ────────────────────────► T::Target ─────────────────► <T::Target as Deref>::Target │ │
│ │ │ │ │ │ │ │ │ ▲ │ │
│ │ ├──────────────────┤ │ └───────────────────┘ │ └────────────────│─────────────────┘ │
│ │ │Cache │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ ptr ───────────────────────────────────────────────────────────────────┘ │
│ │ │ │ │ │ │
│ │ └──────────────────┘ │ │ │
│ │ │ │ │
└────────────┴───────────────────────────┴───────────────────────────────┴──────────────────────────────────────────┘
用法
Pierce<T>
可以通过 Pierce::new(...)
创建。 T
应该是一个双重指针(例如 Arc<Vec<_>>
,Box<Box<_>>
)。
对 deref
的 Pierce<T> 进行解引用返回 &T::Target<Deref>::Target
,即 T 的解引用目标的解引用目标(由 Pierce 包裹的外部指针),即内部指针的解引用目标。
您还可以使用 borrow_inner
获取 T(外部指针)的借用。
有关详细信息,请参阅 Pierce
的文档。
更深的嵌套
Pierce
将两个跳跃减少到一个。如果您有更深的嵌套,可以多次包裹它。
use pierce::Pierce;
let triply_nested: Box<Box<Box<i32>>> = Box::new(Box::new(Box::new(42)));
assert_eq!(***triply_nested, 42); // <- Three jumps!
let pierce_twice = Pierce::new(Pierce::new(triply_nested));
assert_eq!(*pierce_twice, 42); // <- Just one jump!
基准测试
这些基准测试可能根本不能代表您的用例,因为
- 它们被设计得让 Pierce 看起来很棒。
- 编译器优化难以控制。
- CPU 缓存和预测难以控制。(我敢打赌,您的 CPU 上的数字会有很大不同。)
- 无数其他原因,说明您不应该信任合成基准测试。
在实际应用中进行自己的基准测试.
话虽如此,以下是我的结果
基准测试 1:从具有模拟内存碎片的 Box<Vec
中读取项目。
基准测试 2:从 SlowBox<Vec
中读取项目。SlowBox
故意减慢 deref
调用。
基准测试 3:读取几个 Box<Box
。
Pierce<T>
版本与 T
版本的耗时比较。
运行 | 基准测试 1 | 基准测试 2 | 基准测试 3 |
---|---|---|---|
1 | -40.23% | -99.69% | -5.68% |
2 | -40.59% | -99.69% | -5.16% |
3 | -40.70% | -99.68% | +2.69% |
4 | -39.85% | -99.68% | -5.35% |
5 | -38.90% | -99.71% | -5.02% |
6 | -39.12% | -99.69% | -5.53% |
7 | -40.51% | -99.69% | -6.09% |
8 | -26.99% | -99.71% | -6.43% |
请在此处查看基准测试的代码 here.
局限性
仅支持不可变类型
Pierce
仅与不可变数据一起使用。不支持可变性,因为我相当确信这几乎是不可能的。 (如果您有想法,请分享。)
需要 StableDeref
由 Pierce
包装的指针必须是 StableDeref
。如果您的指针类型满足所需条件,您可以在它上实现 unsafe impl StableDeref for T
。该特性在 pierce::StableDeref
中重新导出。
绝大多数指针是 StableDeref
类型,包括 Box
、Vec
、String
、Rc
、Arc
。
依赖项
~12KB