Skip to content

Commit 0fb4ae1

Browse files
committed
compute: decode a LetRec binding's value once
The rendered value was decoded twice, by two independent operators over the same stream: once where the binding is fed back, and again where it is extracted into the outer scope. Each decode allocates an owned row per record per iteration. Keep the first decode and reuse it. Also correct the comment above, which said the recursive value flows as `Vec` through the loop. It does not: the read edge is encoded for the readers and the value is decoded again where it is fed back, so each iteration crosses the container boundary twice.
1 parent 8f26b0a commit 0fb4ae1

1 file changed

Lines changed: 14 additions & 7 deletions

File tree

‎src/compute/src/render.rs‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -953,10 +953,11 @@ impl<'scope> Context<'scope, Product<mz_repr::Timestamp, PointStamp<u64>>> {
953953
// Re-encode the read-edge to columnar so `Get`s on this rec
954954
// binding (e.g. as a Union input) see a columnar edge. The
955955
// feedback `Variable` itself stays `Vec` (set at `oks_v.set`
956-
// below). The recursive value flows `Vec` through the loop, and
957-
// only the externally-visible collection is re-containered. The
958-
// re-encode is a stateless, timestamp-agnostic pass-through, so
959-
// it does not alter the iterative frontier or fixpoint behavior.
956+
// below), so each iteration crosses the container boundary
957+
// twice: encoded here for the readers, decoded once per binding
958+
// where the value is fed back. The re-encode is a stateless,
959+
// timestamp-agnostic pass-through, so it does not alter the
960+
// iterative frontier or fixpoint behavior.
960961
self.insert_id(
961962
Id::Local(*id),
962963
CollectionBundle::from_edge(
@@ -966,7 +967,10 @@ impl<'scope> Context<'scope, Product<mz_repr::Timestamp, PointStamp<u64>>> {
966967
);
967968
variables.insert(Id::Local(*id), (oks_v, err_v));
968969
}
969-
// Now render each of the rec bindings.
970+
// Now render each of the rec bindings. The decoded value is kept so
971+
// the extraction below reuses it instead of decoding the same stream
972+
// a second time.
973+
let mut decoded_oks = BTreeMap::new();
970974
let mut rec_iter = recs.into_iter().peekable();
971975
while let Some(RecBind { id, value, limit }) = rec_iter.next() {
972976
let last = rec_iter.peek().is_none();
@@ -976,6 +980,7 @@ impl<'scope> Context<'scope, Product<mz_repr::Timestamp, PointStamp<u64>>> {
976980
// here to cause that to happen.
977981
let (oks, mut err) = bundle.collection.clone().unwrap();
978982
let oks = oks.into_vec();
983+
decoded_oks.insert(id, oks.clone());
979984
// Collapses what forward reads see. `err_v` below feeds reads rendered before this
980985
// binding and is collapsed separately; without this, a `Get` in a later rec binding
981986
// or in the body resolves to the bundle stored here and compounds level over level,
@@ -1035,8 +1040,10 @@ impl<'scope> Context<'scope, Product<mz_repr::Timestamp, PointStamp<u64>>> {
10351040
// Now extract each of the rec bindings into the outer scope.
10361041
for id in rec_ids.into_iter() {
10371042
let bundle = self.remove_id(Id::Local(id)).unwrap();
1038-
let (oks, err) = bundle.collection.unwrap();
1039-
let oks = oks.into_vec();
1043+
let (_, err) = bundle.collection.unwrap();
1044+
let oks = decoded_oks
1045+
.remove(&id)
1046+
.expect("rec binding decoded while rendering above");
10401047
// Extract into the outer scope and re-encode the read-edge to
10411048
// columnar, so `Get`s on the extracted binding see a columnar
10421049
// edge. `leave_dynamic` has already stripped the iteration

0 commit comments

Comments
 (0)