All Benchmarks

String Matching — Go Benchmark

Compare string matching approaches in Go.

stringsmatchingregexpregexcontains

Finding a substring within a larger string is a common task in software development. Go's standard library provides multiple ways to accomplish this, from strings.Contains to regular expressions. This benchmark demonstrates why simple functions like strings.Contains are universally better for exact substring matching compared to regexp, which has its own compilation and evaluation overhead.

linux/amd64AMD Ryzen 9 9950X3D 16-Core Processorbenchmarks/string-matching
Compare at
CPUs
Performance Comparison (lower is better)
CPU:
1

Strings Contains

Fastest

Uses strings.Contains(haystack, needle). Under the hood, this implementation often maps directly to architecture-specific assembly (like SIMD instructions), making it incredibly fast and allocation-free for exact literal string matching.

CPU Scaling — Strings Contains (lower is better)
const (
	haystack = "The ultimate measure of a man is not where he stands in moments of comfort and convenience, but where he stands at times of challenge and controversy."
	needle   = "challenge"
)

func BenchmarkStringsContains_run(b *testing.B) {
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		sink = strings.Contains(haystack, needle)
	}
}
1 CPU
3.4×faster(238%)thanPrecompiled Regexp
64.3×faster(6327%)thanRegexp Match String
32 CPUs
3.5×faster(246%)thanPrecompiled Regexp
57.3×faster(5625%)thanRegexp Match String
2

Precompiled Regexp

Compiles the regular expression regexp.MustCompile("challenge") once upfront, and evaluates it in the hot loop using re.MatchString(haystack). This skips the heavy regex compilation cost per operation, but still runs through Go's regular expression engine, making it slower than strings.Contains.

CPU Scaling — Precompiled Regexp (lower is better)
const (
	haystack = "The ultimate measure of a man is not where he stands in moments of comfort and convenience, but where he stands at times of challenge and controversy."
	needle   = "challenge"
)

func BenchmarkPrecompiledRegexp_run(b *testing.B) {
	// Compile the regex pattern once before entering the loop.
	re := regexp.MustCompile("challenge")
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		// Evaluate the precompiled regexp.
		sink = re.MatchString(haystack)
	}
}
1 CPU
19×faster(1804%)thanRegexp Match String
3.4×slower(238%)thanStrings Contains
32 CPUs
16.6×faster(1557%)thanRegexp Match String
3.5×slower(246%)thanStrings Contains
3

Regexp Match String

Slowest

Calls regexp.MatchString("challenge", haystack) inside the loop. This is a classic anti-pattern for hot loops because the regex engine must parse and compile the string into a Regexp object on every single iteration, making it the slowest and most allocation-heavy approach.

CPU Scaling — Regexp Match String (lower is better)
const (
	haystack = "The ultimate measure of a man is not where he stands in moments of comfort and convenience, but where he stands at times of challenge and controversy."
	needle   = "challenge"
)

func BenchmarkRegexpMatchString_run(b *testing.B) {
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		// Compiles the regex format to a match format on the fly for every single iteration.
		sink, _ = regexp.MatchString("challenge", haystack)
	}
}
1 CPU
19×slower(1804%)thanPrecompiled Regexp
64.3×slower(6327%)thanStrings Contains
32 CPUs
16.6×slower(1557%)thanPrecompiled Regexp
57.3×slower(5625%)thanStrings Contains

Contributors