Skip to content

Commit 2b5b738

Browse files
committed
history: speed up load with ReadFile and strconv.Unquote
Replace json.NewDecoder streaming decode with os.ReadFile + string splitting + strconv.Unquote. The history file stores one JSON-quoted string per line; Unquote handles the same escapes without the full JSON state machine and reflection overhead. Pre-size slices by counting newlines upfront and use map[string]struct{} for deduplication to reduce allocation count. Benchmarked on a 658KB / 7348-line history file: Before: 3.05ms, 15703 allocs, 2.32MB After: 1.41ms, 2606 allocs, 2.96MB Assisted-By: docker-agent
1 parent 644522e commit 2b5b738

1 file changed

Lines changed: 36 additions & 15 deletions

File tree

pkg/history/history.go

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ package history
22

33
import (
44
"encoding/json"
5-
"io"
65
"os"
76
"path/filepath"
87
"slices"
8+
"strconv"
99
"strings"
1010
)
1111

@@ -208,32 +208,53 @@ func (h *History) append(message string) error {
208208
}
209209

210210
func (h *History) load() error {
211-
f, err := os.Open(h.path)
211+
data, err := os.ReadFile(h.path)
212212
if err != nil {
213213
return err
214214
}
215-
defer f.Close()
216215

217-
var all []string
218-
dec := json.NewDecoder(f)
219-
for {
220-
var message string
221-
if err := dec.Decode(&message); err != nil {
222-
if err == io.EOF {
223-
break
224-
}
216+
// Count lines to pre-size the slice.
217+
n := 0
218+
for _, b := range data {
219+
if b == '\n' {
220+
n++
221+
}
222+
}
223+
224+
// Parse all lines. Each line is a JSON-encoded string (e.g. "hello").
225+
// strconv.Unquote handles the same escape sequences as JSON and is
226+
// much faster than json.Unmarshal for quoted strings.
227+
all := make([]string, 0, n)
228+
s := string(data)
229+
for s != "" {
230+
i := strings.IndexByte(s, '\n')
231+
var line string
232+
if i < 0 {
233+
line = s
234+
s = ""
235+
} else {
236+
line = s[:i]
237+
s = s[i+1:]
238+
}
239+
if line == "" {
240+
continue
241+
}
242+
243+
message, err := strconv.Unquote(line)
244+
if err != nil {
225245
continue
226246
}
227247
all = append(all, message)
228248
}
229249

230-
// Deduplicate keeping the latest occurrence of each message
231-
seen := make(map[string]bool)
250+
// Deduplicate keeping the latest occurrence of each message.
251+
seen := make(map[string]struct{}, len(all))
252+
h.Messages = make([]string, 0, len(all))
232253
for i := len(all) - 1; i >= 0; i-- {
233-
if seen[all[i]] {
254+
if _, dup := seen[all[i]]; dup {
234255
continue
235256
}
236-
seen[all[i]] = true
257+
seen[all[i]] = struct{}{}
237258
h.Messages = append(h.Messages, all[i])
238259
}
239260
slices.Reverse(h.Messages)

0 commit comments

Comments
 (0)