Compare commits

...

5 commits

Author SHA1 Message Date
Jarrett Ye
ae54ab2226
Merge 147da28a1f into 3890e12c9e 2025-09-17 09:36:24 +00:00
Jarrett Ye
147da28a1f
When not training, only create the final FSRS item 2025-09-17 17:36:17 +08:00
Jarrett Ye
1afc7e35ca
Merge branch 'main' into Refactor-reviews_for_fsrs-function-for-improved-performance 2025-09-17 13:43:13 +08:00
Jarrett Ye
38990fe68d
collapse if statement 2025-09-17 13:38:05 +08:00
Jarrett Ye
ecd1cf45e9
Refactor reviews_for_fsrs function for improved performance
Replaced the previous implementation with a more efficient approach using a single loop and pre-allocated vectors. This change reduces the complexity of creating FSRSItems and enhances overall performance, especially for larger datasets.
2025-09-17 12:28:24 +08:00

View file

@ -478,27 +478,42 @@ pub(crate) fn reviews_for_fsrs(
})) }))
.collect_vec(); .collect_vec();
let skip = if training { 1 } else { 0 }; let items = if training {
// Convert the remaining entries into separate FSRSItems, where each item // Convert the remaining entries into separate FSRSItems, where each item
// contains all reviews done until then. // contains all reviews done until then.
let items: Vec<(RevlogId, FSRSItem)> = entries let mut items = Vec::with_capacity(entries.len());
.iter() let mut current_reviews = Vec::with_capacity(entries.len());
.enumerate() for (idx, (entry, &delta_t)) in entries.iter().zip(delta_ts.iter()).enumerate() {
.skip(skip) current_reviews.push(FSRSReview {
.map(|(outer_idx, entry)| { rating: entry.button_chosen as u32,
delta_t,
});
if idx >= 1 && delta_t > 0 {
items.push((
entry.id,
FSRSItem {
reviews: current_reviews.clone(),
},
));
}
}
items
} else {
// When not training, we only need the final FSRS item, which represents
// the complete history of the card. This avoids expensive clones in a loop.
let reviews = entries let reviews = entries
.iter() .iter()
.take(outer_idx + 1) .zip(delta_ts.iter())
.enumerate() .map(|(entry, &delta_t)| FSRSReview {
.map(|(inner_idx, r)| FSRSReview { rating: entry.button_chosen as u32,
rating: r.button_chosen as u32, delta_t,
delta_t: delta_ts[inner_idx],
}) })
.collect(); .collect();
(entry.id, FSRSItem { reviews }) let last_entry = entries.last().unwrap();
})
.filter(|(_, item)| !training || item.reviews.last().unwrap().delta_t > 0) vec![(last_entry.id, FSRSItem { reviews })]
.collect_vec(); };
if items.is_empty() { if items.is_empty() {
None None
} else { } else {
@ -738,7 +753,7 @@ pub(crate) mod tests {
], ],
false, false,
), ),
fsrs_items!([review(0)], [review(0), review(1)]) fsrs_items!([review(0), review(1)])
); );
} }
@ -809,7 +824,7 @@ pub(crate) mod tests {
// R | A X R // R | A X R
assert_eq!( assert_eq!(
convert_ignore_before(revlogs, false, days_ago_ms(9)), convert_ignore_before(revlogs, false, days_ago_ms(9)),
fsrs_items!([review(0)], [review(0), review(2)]) fsrs_items!([review(0), review(2)])
); );
} }
@ -828,6 +843,9 @@ pub(crate) mod tests {
assert_eq!( assert_eq!(
convert_ignore_before(revlogs, false, days_ago_ms(9)) convert_ignore_before(revlogs, false, days_ago_ms(9))
.unwrap() .unwrap()
.last()
.unwrap()
.reviews
.len(), .len(),
2 2
); );
@ -849,6 +867,9 @@ pub(crate) mod tests {
assert_eq!( assert_eq!(
convert_ignore_before(revlogs, false, days_ago_ms(9)) convert_ignore_before(revlogs, false, days_ago_ms(9))
.unwrap() .unwrap()
.last()
.unwrap()
.reviews
.len(), .len(),
2 2
); );