Skip to content

Commit 5ff4fad

Browse files
Optimize CMAllTitle string allocations
Introduced `upperCaseFirstLower` helper function to perform title-casing (first char upper, rest lower) in a single pass. This avoids the double allocation caused by `UpperCaseFirst(strings.ToLower(w))` which created an intermediate lower-cased string. The optimization reduces allocations by ~50% (from ~204 to ~104 allocs/op in benchmarks) and improves memory usage by ~18% for CMAllTitle operations. Applied the optimization to `SingleCaseWord`, `AcronymWord`, `UpperCaseWord` handling in `WordsToFormattedCase`, and `FirstUpperCaseWord.String()`. Verified with existing tests and `optimization_test.go` (which targets this specific case). Co-authored-by: arran4 <111667+arran4@users.noreply.github.com>
1 parent d6c51f4 commit 5ff4fad

1 file changed

Lines changed: 40 additions & 4 deletions

File tree

types.go

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ type SeparatorWord string
3636

3737
// String implementations
3838
func (w SingleCaseWord) String() string { return strings.ToLower(string(w)) }
39-
func (w FirstUpperCaseWord) String() string { return UpperCaseFirst(strings.ToLower(string(w))) }
39+
func (w FirstUpperCaseWord) String() string { return upperCaseFirstLower(string(w)) }
4040
func (w AcronymWord) String() string { return string(w) }
4141
func (w UpperCaseWord) String() string { return strings.ToUpper(string(w)) }
4242
func (w SeparatorWord) String() string { return string(w) }
@@ -114,6 +114,42 @@ func MustLowerCaseFirst(s string) string {
114114
return res
115115
}
116116

117+
// upperCaseFirstLower capitalizes the first character and lowercases the rest.
118+
func upperCaseFirstLower(s string) string {
119+
if s == "" {
120+
return ""
121+
}
122+
r, size := utf8.DecodeRuneInString(s)
123+
if r == utf8.RuneError {
124+
return s
125+
}
126+
127+
u := unicode.ToUpper(r)
128+
129+
// Check if changes are needed
130+
needChange := (r != u)
131+
if !needChange {
132+
for _, rc := range s[size:] {
133+
if unicode.ToLower(rc) != rc {
134+
needChange = true
135+
break
136+
}
137+
}
138+
}
139+
140+
if !needChange {
141+
return s
142+
}
143+
144+
var b strings.Builder
145+
b.Grow(len(s))
146+
b.WriteRune(u)
147+
for _, rc := range s[size:] {
148+
b.WriteRune(unicode.ToLower(rc))
149+
}
150+
return b.String()
151+
}
152+
117153
func (w ExactCaseWord) String() string { return string(w) }
118154

119155
// Options
@@ -227,7 +263,7 @@ func WordsToFormattedCase(words []Word, opts ...any) (string, error) {
227263
} else if cfg.allLower || cfg.whispering {
228264
w = strings.ToLower(w)
229265
} else if cfg.caseMode == CMAllTitle {
230-
w = UpperCaseFirst(strings.ToLower(w))
266+
w = upperCaseFirstLower(w)
231267
} else {
232268
w = strings.ToLower(w)
233269
}
@@ -258,7 +294,7 @@ func WordsToFormattedCase(words []Word, opts ...any) (string, error) {
258294
} else if cfg.whispering {
259295
w = strings.ToLower(w)
260296
} else if cfg.caseMode == CMAllTitle {
261-
w = UpperCaseFirst(strings.ToLower(w))
297+
w = upperCaseFirstLower(w)
262298
}
263299
case UpperCaseWord:
264300
w = word.String()
@@ -267,7 +303,7 @@ func WordsToFormattedCase(words []Word, opts ...any) (string, error) {
267303
} else if cfg.allLower || cfg.whispering {
268304
w = strings.ToLower(w)
269305
} else if cfg.caseMode == CMAllTitle {
270-
w = UpperCaseFirst(strings.ToLower(w))
306+
w = upperCaseFirstLower(w)
271307
} else {
272308
w = strings.ToLower(w)
273309
}

0 commit comments

Comments
 (0)