-
Notifications
You must be signed in to change notification settings - Fork 2
perf: optimize Linker to avoid redundant AGENTS.md compression #171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c9bb571
e1d0205
e3cbd79
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,9 +6,10 @@ | |
| use anyhow::{Context, Result}; | ||
| use colored::Colorize; | ||
| use std::cell::RefCell; | ||
| use std::collections::HashMap; | ||
| use std::collections::{HashMap, HashSet}; | ||
| use std::fs; | ||
| use std::path::{Path, PathBuf}; | ||
| use std::rc::Rc; | ||
| use walkdir::WalkDir; | ||
|
|
||
| use crate::config::{Config, SyncType, TargetConfig}; | ||
|
|
@@ -52,6 +53,11 @@ pub struct Linker { | |
| project_root: PathBuf, | ||
| source_dir: PathBuf, | ||
| path_cache: RefCell<HashMap<PathBuf, PathBuf>>, | ||
| /// Cache of compressed content for AGENTS.md files to avoid redundant processing. | ||
| /// Uses Rc to share the same heap-allocated string across multiple agents. | ||
| compression_cache: RefCell<HashMap<PathBuf, Rc<String>>>, | ||
| /// Tracks which compressed files have already been ensured to be up-to-date. | ||
| ensured_outputs: RefCell<HashSet<PathBuf>>, | ||
| } | ||
|
|
||
| impl Linker { | ||
|
|
@@ -66,6 +72,8 @@ impl Linker { | |
| project_root, | ||
| source_dir, | ||
| path_cache: RefCell::new(HashMap::new()), | ||
| compression_cache: RefCell::new(HashMap::new()), | ||
| ensured_outputs: RefCell::new(HashSet::new()), | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -242,13 +250,32 @@ impl Linker { | |
| } | ||
|
|
||
| fn write_compressed_agents_md(&self, source: &Path, dest: &Path) -> Result<()> { | ||
| let content = fs::read_to_string(source) | ||
| .with_context(|| format!("Failed to read AGENTS.md: {}", source.display()))?; | ||
| let compressed = compress_agents_md_content(&content); | ||
| // 1. Skip if this specific destination has already been ensured to be up-to-date. | ||
| if self.ensured_outputs.borrow().contains(dest) { | ||
| return Ok(()); | ||
| } | ||
|
Comment on lines
252
to
+256
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stale Neither cache is cleared between The intended single-run lifetime should be either documented on the struct or enforced by clearing the caches at the top of π‘οΈ Option A β Clear caches at the start of each `sync()` call pub fn sync(&self, options: &SyncOptions) -> Result<SyncResult> {
+ self.compression_cache.borrow_mut().clear();
+ self.ensured_outputs.borrow_mut().clear();
let mut result = SyncResult::default();π Option B β Document the single-run contract on the struct+/// **Note:** The internal `compression_cache` and `ensured_outputs` caches are
+/// populated during a single `sync()` run and are never cleared. Reusing a
+/// `Linker` instance after source files change will produce stale results.
pub struct Linker {π€ Prompt for AI Agents |
||
|
|
||
| // 2. Get or compute the compressed content for this source. | ||
| // This avoids redundant re-reading and re-compressing of the same AGENTS.md. | ||
| let compressed = { | ||
| let mut cache = self.compression_cache.borrow_mut(); | ||
| if let Some(cached) = cache.get(source) { | ||
| cached.clone() | ||
| } else { | ||
| let content = fs::read_to_string(source) | ||
| .with_context(|| format!("Failed to read AGENTS.md: {}", source.display()))?; | ||
| let compressed = compress_agents_md_content(&content); | ||
| let rc = Rc::new(compressed); | ||
| cache.insert(source.to_path_buf(), rc.clone()); | ||
| rc | ||
| } | ||
| }; | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // 3. Check if existing file already matches the compressed content to avoid unnecessary writes. | ||
| if let Ok(existing) = fs::read_to_string(dest) | ||
| && existing == compressed | ||
| && existing == *compressed | ||
| { | ||
| self.ensured_outputs.borrow_mut().insert(dest.to_path_buf()); | ||
| return Ok(()); | ||
| } | ||
|
|
||
|
|
@@ -259,8 +286,12 @@ impl Linker { | |
| .with_context(|| format!("Failed to create directory: {}", parent.display()))?; | ||
| } | ||
|
|
||
| fs::write(dest, compressed) | ||
| .with_context(|| format!("Failed to write compressed AGENTS.md: {}", dest.display())) | ||
| fs::write(dest, &*compressed) | ||
| .with_context(|| format!("Failed to write compressed AGENTS.md: {}", dest.display()))?; | ||
|
|
||
| // 4. Mark this destination as ensured. | ||
| self.ensured_outputs.borrow_mut().insert(dest.to_path_buf()); | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Create a single symlink | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π§Ή Nitpick | π΅ Trivial
π§© Analysis chain
π Script executed:
Repository: dallay/agentsync
Length of output: 210
π Script executed:
Repository: dallay/agentsync
Length of output: 42
π Script executed:
Repository: dallay/agentsync
Length of output: 878
π Script executed:
Repository: dallay/agentsync
Length of output: 42
π Script executed:
Repository: dallay/agentsync
Length of output: 564
π Script executed:
Repository: dallay/agentsync
Length of output: 2812
π Script executed:
Repository: dallay/agentsync
Length of output: 1145
π Script executed:
Repository: dallay/agentsync
Length of output: 42
π Script executed:
Repository: dallay/agentsync
Length of output: 82
π Script executed:
Repository: dallay/agentsync
Length of output: 6782
π Script executed:
Repository: dallay/agentsync
Length of output: 209
π Script executed:
Repository: dallay/agentsync
Length of output: 93
π Script executed:
Repository: dallay/agentsync
Length of output: 278
π Script executed:
Repository: dallay/agentsync
Length of output: 42
π Script executed:
Repository: dallay/agentsync
Length of output: 766
π Script executed:
Repository: dallay/agentsync
Length of output: 1560
π Script executed:
Repository: dallay/agentsync
Length of output: 2577
π Script executed:
Repository: dallay/agentsync
Length of output: 42
Document the intentional single-threaded design of
LinkerThe
compression_cache: RefCell<HashMap<PathBuf, Rc<String>>>field usesRc, which does not implementSend, makingLinker: !Send. While this is technically a narrowing of the struct's trait implementations, it reflects a deliberate performance choice documented in the commit message: to reduce memory usage and CPU time during compression by avoiding deep string clones across agents.Since this is a single-threaded CLI tool with no async or threading patterns, and
Linkeris never moved across thread boundaries in practice, the practical impact is minimal. However, to prevent confusion for future library users, add a comment documenting the intentional single-threaded design:Using
Arcwould add unnecessary atomic overhead without benefit in this context.π€ Prompt for AI Agents