From f37e0b42a01de6a428b493f242a84eb18874b6ad Mon Sep 17 00:00:00 2001 From: oabrivard Date: Thu, 26 Mar 2026 09:36:52 +0100 Subject: [PATCH] perf: use Arc for immutable values in pipeline to reduce cloning Wrap `model_research` (String), `classify_schema` (Value), and `classification_categories` (Vec) in Arc before the batch loops so spawned tasks clone a cheap pointer instead of the full heap data on every iteration. Also removes the redundant intermediate `mdl`/`class_sys`/`class_user` bindings in both classify loops. Co-Authored-By: Claude Sonnet 4.6 --- backend/src/services/synthesis.rs | 38 ++++++++++++++----------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/backend/src/services/synthesis.rs b/backend/src/services/synthesis.rs index 1a71780..8e708c7 100644 --- a/backend/src/services/synthesis.rs +++ b/backend/src/services/synthesis.rs @@ -264,7 +264,9 @@ async fn run_generation_inner( let mut filled_counts: HashMap = HashMap::new(); let mut seen_urls: std::collections::HashSet = std::collections::HashSet::new(); let max_total = (user_categories.len() + 1) * settings.max_items_per_category as usize; - let classify_schema = crate::services::llm::schema::build_article_classify_schema(); + let classify_schema = Arc::new(crate::services::llm::schema::build_article_classify_schema()); + let model_research = Arc::new(model_research); + let classification_categories = Arc::new(classification_categories); // === PHASE 1: Personalized Sources === if !sources.is_empty() { @@ -289,7 +291,7 @@ async fn run_generation_inner( let source_title = source.title.clone(); let use_llm = settings.use_llm_for_source_links; let provider_clone = std::sync::Arc::clone(&provider); - let model = model_research.clone(); + let model = Arc::clone(&model_research); let max_l = max_links; let pool = state.pool.clone(); let uid = user_id; @@ -334,7 +336,7 @@ async fn run_generation_inner( let source_title = source.title.clone(); let use_llm = settings.use_llm_for_source_links; let provider_clone = std::sync::Arc::clone(&provider); - let model = model_research.clone(); + let model = Arc::clone(&model_research); let max_l = max_links; let pool = state.pool.clone(); let uid = user_id; @@ -457,9 +459,9 @@ async fn run_generation_inner( let mut classify_set = tokio::task::JoinSet::new(); for (final_url, source_url, body_text, page_title) in &scraped_articles { let provider_clone = std::sync::Arc::clone(&provider); - let model = model_research.clone(); - let schema = classify_schema.clone(); - let cats = classification_categories.clone(); + let model = Arc::clone(&model_research); + let schema = Arc::clone(&classify_schema); + let cats = Arc::clone(&classification_categories); let body_snippet: String = body_text.chars().take(500).collect(); let title = page_title.clone(); let url = final_url.clone(); @@ -468,20 +470,17 @@ async fn run_generation_inner( let uid = user_id; let jid = job_id; - let (class_sys, class_user) = crate::services::prompts::build_article_classify_prompt(&title, &body_snippet, &cats); - let sys = class_sys.clone(); - let usr = class_user.clone(); - let mdl = model.clone(); + let (sys, usr) = crate::services::prompts::build_article_classify_prompt(&title, &body_snippet, &cats); classify_set.spawn(async move { let llm_start = std::time::Instant::now(); - let result = provider_clone.call_llm(&mdl, &sys, &usr, &schema).await; + let result = provider_clone.call_llm(&model, &sys, &usr, &schema).await; let duration = llm_start.elapsed().as_millis() as u64; // Log the LLM call if let Ok(ref resp) = result { let resp_str = serde_json::to_string_pretty(resp).unwrap_or_default(); - crate::db::llm_call_log::insert(&pool, uid, jid, "classify_summarize", &mdl, &sys, &usr, &resp_str, duration as i32, Some(&url)).await.ok(); + crate::db::llm_call_log::insert(&pool, uid, jid, "classify_summarize", &model, &sys, &usr, &resp_str, duration as i32, Some(&url)).await.ok(); } (url, su, title, result) @@ -626,9 +625,9 @@ async fn run_generation_inner( let mut classify_set = tokio::task::JoinSet::new(); for (final_url, body_text, page_title) in &scraped_articles { let provider_clone = std::sync::Arc::clone(&provider); - let model = model_research.clone(); - let schema = classify_schema.clone(); - let cats = classification_categories.clone(); + let model = Arc::clone(&model_research); + let schema = Arc::clone(&classify_schema); + let cats = Arc::clone(&classification_categories); let body_snippet: String = body_text.chars().take(500).collect(); let title = page_title.clone(); let url = final_url.clone(); @@ -636,19 +635,16 @@ async fn run_generation_inner( let uid = user_id; let jid = job_id; - let (class_sys, class_user) = crate::services::prompts::build_article_classify_prompt(&title, &body_snippet, &cats); - let sys = class_sys.clone(); - let usr = class_user.clone(); - let mdl = model.clone(); + let (sys, usr) = crate::services::prompts::build_article_classify_prompt(&title, &body_snippet, &cats); classify_set.spawn(async move { let llm_start = std::time::Instant::now(); - let result = provider_clone.call_llm(&mdl, &sys, &usr, &schema).await; + let result = provider_clone.call_llm(&model, &sys, &usr, &schema).await; let duration = llm_start.elapsed().as_millis() as u64; if let Ok(ref resp) = result { let resp_str = serde_json::to_string_pretty(resp).unwrap_or_default(); - crate::db::llm_call_log::insert(&pool, uid, jid, "classify_summarize", &mdl, &sys, &usr, &resp_str, duration as i32, Some(&url)).await.ok(); + crate::db::llm_call_log::insert(&pool, uid, jid, "classify_summarize", &model, &sys, &usr, &resp_str, duration as i32, Some(&url)).await.ok(); } (url, title, result)