I want to implement a cached generator. Something like this
struct CachedGen<T: Iterator<Item = u32>> {
generator: T,
cache: Vec<u32>
}
When you are trying to get
the nth element, it will look up the cache, if it is there then return, otherwise call generator until the nth element and push all the elements into cache so that it can be reused later. The problem with that this cached generator will be shared a lot but I cannot duplicate &mut
. One option I tried is to use Rc<RefCell<CachedGen>>
, but it makes my code very ugly and more importantly it puts too much overhead to my code since this generator is very hot. My program is single-threaded and I can guarantee that the computation dependency is a DAG, is it reasonable for me to use unsafe
code here? Or if there is any better approach.
Thank you!
EDIT: details
Let's say we have a graph with u32
node ID.
struct Graph<T> {
nodes: Vec<Node<T>>
}
struct Node<T> {
content: T,
outgoing: Vec<u32>,
incoming: Vec<u32>
}
It is an adjacent list for each node, and the graph is a DAG(I have run time check to make sure it is a DAG). Now that I have a DP algorithm, that will calculate a list of u32
for each node in the graph. WLOG, let's assume that the graph is topologically sorted with respect to the node id. So to calculate the list for node x
, it might need the result from nodes with id less than x
// I want to get this
DP_result: HashMap<u32, Vec<u32>>
// However, since I want to calculate these things lazily, what I really want is
DP_result_lazy: HashMap<u32, CachedGen>
// But wait, the `CachedGen` in this hash map is also referring to other
// `CachedGen` in the HashMap, I cannot have self-reference because
// it will lock everything up so that I cannot add any thing to the hashmap
// Okay, fine, let's do this
DP_result_lazy: Rc<RefCell<HashMap<u32, CachedGen>>>
// Oh damn, if I want to get the mutable reference of CachedGen, I need to
// borrow_mut from DP_result_lazy, but this will panic at runtime as I will have
// more than one coroutine borrowing DP_result_lazy at the same time.
// Okay, let's have more fine-grained share
DP_result_lazy: Rc<RefCell<HashMap<u32, RefCell<CachedGen>>>>
// I should be good, right?
// error[E0626]: borrow may still be in use when coroutine yields
So what I ended up with is
DP_result_lazy: Rc<RefCell<HashMap<u32, Rc<RefCell<CachedGen>>>>>
The super simplified version of the Coroutine
// For each node_id
let DP_result_lazy_clone = DP_result_lazy.clone()
let coroutine = #[coroutine] || move {
let incomings = graph.get_incomings(node_id);
for pred in incomings {
// Super stupid borrowing.
DP_result_lazy_clone.as_ref().borrow().get(&pred).unwrap().as_ref()......
// Some complicated calculation
yield result;
}
}
DP_result_lazy.as_ref().borrow_mut().insert(node_id, Rc::new(RefCell::new(CachedGen::new(coroutine))));