From da182cf1ee929603540529daf6557d0c306fe9e5 Mon Sep 17 00:00:00 2001 From: wuyangfan <1102042793@qq.com> Date: Sun, 31 May 2026 15:40:59 +0800 Subject: [PATCH] perf: precompute find/replace as []byte once per run Convert FIND and REPLACE to []byte when building the walk context so each file rewrite does not re-allocate the same byte slices. Fixes #22 --- find_replace.go | 22 +++++++++++++++++----- find_replace_test.go | 28 ++++++++++++++-------------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/find_replace.go b/find_replace.go index 9bdbe79..1499545 100644 --- a/find_replace.go +++ b/find_replace.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "errors" "fmt" "io" @@ -13,9 +14,20 @@ import ( // findReplace is a struct used to provide context to all find & replace // operations, including the strings to search for & replace. +func newFindReplace(find, replace string) findReplace { + return findReplace{ + find: find, + replace: replace, + findB: []byte(find), + replaceB: []byte(replace), + } +} + type findReplace struct { - find string - replace string + find string + replace string + findB []byte + replaceB []byte // errs accumulates non-fatal errors that occurred during a walk. The // walker logs each error at the point of failure (preserving the @@ -82,7 +94,7 @@ func run(args []string, stderr io.Writer) int { return 1 } - fr := findReplace{find: args[1], replace: args[2]} + fr := newFindReplace(args[1], args[2]) // Recursively explore the hierarchy depth first, rewrite files as needed, // and rename files last (after we don't have to revisit them). @@ -204,9 +216,9 @@ func (fr *findReplace) ReplaceContents(f *File) error { if err != nil { return err } - if !strings.Contains(content, fr.find) { + if !bytes.Contains([]byte(content), fr.findB) { return nil } - newContent := strings.ReplaceAll(content, fr.find, fr.replace) + newContent := string(bytes.ReplaceAll([]byte(content), fr.findB, fr.replaceB)) return f.Write(newContent) } diff --git a/find_replace_test.go b/find_replace_test.go index 9a54564..a4201d7 100644 --- a/find_replace_test.go +++ b/find_replace_test.go @@ -169,7 +169,7 @@ func TestWalkDir(t *testing.T) { f1 := newTestFile(t, d.Path, "why", f1Contents) defer os.Remove(f1.Path) - fr := findReplace{find: find, replace: replace} + fr := newFindReplace(find, replace) fr.WalkDir(d) if err := fr.errs.err(); err != nil { t.Fatalf("WalkDir reported errors: %v", err) @@ -228,7 +228,7 @@ func TestHandleFileWithDir(t *testing.T) { defer os.Remove(f.Path) expectedPath := filepath.Join(f.Dir(), strings.ReplaceAll(f.Base(), find, replace)) defer os.Remove(expectedPath) - fr := findReplace{find: find, replace: replace} + fr := newFindReplace(find, replace) assertFileExists(t, f) if err := fr.HandleFile(f); err != nil { @@ -252,7 +252,7 @@ func TestHandleFileWithIgnoredDir(t *testing.T) { unexpectedName := strings.ReplaceAll(f.Base(), find, replace) unexpectedPath := filepath.Join(f.Dir(), unexpectedName) defer os.Remove(unexpectedPath) - fr := findReplace{find: find, replace: replace} + fr := newFindReplace(find, replace) assertFileExists(t, f) if err := fr.HandleFile(f); err != nil { @@ -272,7 +272,7 @@ func TestHandleFileWithFile(t *testing.T) { expectedName := strings.ReplaceAll(f.Base(), find, replace) expectedPath := filepath.Join(f.Dir(), expectedName) defer os.Remove(expectedPath) - fr := findReplace{find: find, replace: replace} + fr := newFindReplace(find, replace) assertFileExists(t, f) if err := fr.HandleFile(f); err != nil { @@ -296,7 +296,7 @@ func TestRenameFile(t *testing.T) { expectedName := strings.ReplaceAll(f.Base(), find, replace) expectedPath := filepath.Join(f.Dir(), expectedName) defer os.Remove(expectedPath) - fr := findReplace{find: find, replace: replace} + fr := newFindReplace(find, replace) assertFileExists(t, f) if err := fr.RenameFile(f); err != nil { @@ -323,7 +323,7 @@ func TestReplaceContents(t *testing.T) { f := newTestFile(t, "", "*", initial) defer os.Remove(f.Path) - fr := findReplace{find: find, replace: replace} + fr := newFindReplace(find, replace) if err := fr.ReplaceContents(f); err != nil { t.Fatalf("ReplaceContents(%q): %v", f.Path, err) } @@ -338,7 +338,7 @@ func TestReplaceContentsEntireFile(t *testing.T) { f := newTestFile(t, "", "*", initial) defer os.Remove(f.Path) - fr := findReplace{find: find, replace: replace} + fr := newFindReplace(find, replace) if err := fr.ReplaceContents(f); err != nil { t.Fatalf("ReplaceContents(%q): %v", f.Path, err) } @@ -353,7 +353,7 @@ func TestReplaceContentsMultipleMatchesSingleLine(t *testing.T) { f := newTestFile(t, "", "*", initial) defer os.Remove(f.Path) - fr := findReplace{find: find, replace: replace} + fr := newFindReplace(find, replace) if err := fr.ReplaceContents(f); err != nil { t.Fatalf("ReplaceContents(%q): %v", f.Path, err) } @@ -368,7 +368,7 @@ func TestReplaceContentsMultipleMatchesMultipleLines(t *testing.T) { f := newTestFile(t, "", "*", initial) defer os.Remove(f.Path) - fr := findReplace{find: find, replace: replace} + fr := newFindReplace(find, replace) if err := fr.ReplaceContents(f); err != nil { t.Fatalf("ReplaceContents(%q): %v", f.Path, err) } @@ -383,7 +383,7 @@ func TestReplaceContentsNoMatches(t *testing.T) { f := newTestFile(t, "", "*", initial) defer os.Remove(f.Path) - fr := findReplace{find: find, replace: replace} + fr := newFindReplace(find, replace) if err := fr.ReplaceContents(f); err != nil { t.Fatalf("ReplaceContents(%q): %v", f.Path, err) } @@ -433,7 +433,7 @@ func TestWalkDir_PermissionDeniedSubdirContinues(t *testing.T) { t.Cleanup(func() { _ = os.Chmod(denied, 0700) }) rootFile := newFileOrFatal(t, root) - fr := findReplace{find: "alpha", replace: "beta"} + fr := newFindReplace("alpha", "beta") fr.WalkDir(rootFile) // The sibling file should have been rewritten despite the denied subtree. @@ -473,7 +473,7 @@ func TestRenameFile_ReturnsErrorOnExistingDestination(t *testing.T) { } f := newFileOrFatal(t, src) - fr := findReplace{find: "alpha", replace: "beta"} + fr := newFindReplace("alpha", "beta") err := fr.RenameFile(f) if err == nil { t.Fatalf("RenameFile(%q): err = nil; want an error referencing the occupied destination", src) @@ -516,7 +516,7 @@ func TestWalkDir_BadRenameTargetDoesNotAbortSiblings(t *testing.T) { } rootFile := newFileOrFatal(t, root) - fr := findReplace{find: "alpha", replace: "beta"} + fr := newFindReplace("alpha", "beta") fr.WalkDir(rootFile) // The free file should have been renamed. @@ -679,7 +679,7 @@ func BenchmarkNova(b *testing.B) { for n := 0; n < b.N; n++ { b.StopTimer() d := CloneRepoToTestDir(b, "git@github.com:openstack/nova.git") - fr := findReplace{find: RandomString(2), replace: RandomString(2)} + fr := newFindReplace(RandomString(2), RandomString(2)) b.StartTimer() fr.WalkDir(d) }