Skip to content

Commit 43738c6

Browse files
committed
test(string): bench scan/cut family — IndexAny/ContainsAny/ContainsRune/CutPrefix/CutSuffix (AX-11)
Hot string scan wrappers that lacked a benchmark while their siblings (Index/Contains/Cut/TrimPrefix) were covered. IndexAny/ContainsAny are O(n×m) charset scans; the _EarlyHit/_NoMatch pair documents the cost shape. All zero-alloc on Apple M3 Ultra (NoMatch ~3.5x EarlyHit). Co-Authored-By: Virgil <virgil@lethean.io>
1 parent 29800b7 commit 43738c6

1 file changed

Lines changed: 92 additions & 0 deletions

File tree

string_bench_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,3 +333,95 @@ func BenchmarkCut(b *B) {
333333
_, _, _ = Cut(s, ": ")
334334
}
335335
}
336+
337+
// --- IndexAny / ContainsAny / ContainsRune (charset scans) ---
338+
//
339+
// IndexAny and ContainsAny scan s against a set of separators (O(n×m)
340+
// in the worst case), so their cost varies with where in s the first
341+
// match lands — _EarlyHit finds it near the front, _NoMatch walks the
342+
// whole string. ContainsRune is the single-rune scan variant.
343+
344+
func BenchmarkIndexAny_EarlyHit(b *B) {
345+
s := "user@host:port/path"
346+
b.ReportAllocs()
347+
for i := 0; i < b.N; i++ {
348+
_ = IndexAny(s, "@:/")
349+
}
350+
}
351+
352+
func BenchmarkIndexAny_NoMatch(b *B) {
353+
s := "the quick brown fox jumps over the lazy dog"
354+
b.ReportAllocs()
355+
for i := 0; i < b.N; i++ {
356+
_ = IndexAny(s, "@:/")
357+
}
358+
}
359+
360+
func BenchmarkContainsAny_Hit(b *B) {
361+
s := "user@host"
362+
b.ReportAllocs()
363+
for i := 0; i < b.N; i++ {
364+
_ = ContainsAny(s, "@:")
365+
}
366+
}
367+
368+
func BenchmarkContainsAny_NoMatch(b *B) {
369+
s := "the quick brown fox jumps over the lazy dog"
370+
b.ReportAllocs()
371+
for i := 0; i < b.N; i++ {
372+
_ = ContainsAny(s, "@:")
373+
}
374+
}
375+
376+
func BenchmarkContainsRune_ASCII(b *B) {
377+
s := "the quick brown fox jumps over the lazy dog"
378+
b.ReportAllocs()
379+
for i := 0; i < b.N; i++ {
380+
_ = ContainsRune(s, 'z')
381+
}
382+
}
383+
384+
func BenchmarkContainsRune_NonASCII(b *B) {
385+
s := "le renard brun rapide saute par-dessus le chien paresseux café"
386+
b.ReportAllocs()
387+
for i := 0; i < b.N; i++ {
388+
_ = ContainsRune(s, 'é')
389+
}
390+
}
391+
392+
// --- CutPrefix / CutSuffix (prefix/suffix cuts) ---
393+
//
394+
// The reporting siblings of TrimPrefix/TrimSuffix — same scan, but they
395+
// also return whether the cut fired. _Hit lands the cut, _Miss does not.
396+
397+
func BenchmarkCutPrefix_Hit(b *B) {
398+
s := "--verbose"
399+
b.ReportAllocs()
400+
for i := 0; i < b.N; i++ {
401+
_, _ = CutPrefix(s, "--")
402+
}
403+
}
404+
405+
func BenchmarkCutPrefix_Miss(b *B) {
406+
s := "verbose"
407+
b.ReportAllocs()
408+
for i := 0; i < b.N; i++ {
409+
_, _ = CutPrefix(s, "--")
410+
}
411+
}
412+
413+
func BenchmarkCutSuffix_Hit(b *B) {
414+
s := "main.go"
415+
b.ReportAllocs()
416+
for i := 0; i < b.N; i++ {
417+
_, _ = CutSuffix(s, ".go")
418+
}
419+
}
420+
421+
func BenchmarkCutSuffix_Miss(b *B) {
422+
s := "main.rs"
423+
b.ReportAllocs()
424+
for i := 0; i < b.N; i++ {
425+
_, _ = CutSuffix(s, ".go")
426+
}
427+
}

0 commit comments

Comments
 (0)