All Benchmarks

Go Benchmark

String Matching

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 atCPUs

1 CPU

Fastest

Strings Contains

16.83 ns/op

Slowest

Regexp Match String

1.08 µs/op · 64x slower

32 CPUs

Fastest

Strings Contains

16.81 ns/op

Slowest

Regexp Match String

962.58 ns/op · 57x slower

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 (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.4xfaster(238%)thanPrecompiled Regexp
64.3xfaster(6327%)thanRegexp Match String
32 CPUs
3.5xfaster(246%)thanPrecompiled Regexp
57.3xfaster(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 (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
19xfaster(1804%)thanRegexp Match String
3.4xslower(238%)thanStrings Contains
32 CPUs
16.6xfaster(1557%)thanRegexp Match String
3.5xslower(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 (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
19xslower(1804%)thanPrecompiled Regexp
64.3xslower(6327%)thanStrings Contains
32 CPUs
16.6xslower(1557%)thanPrecompiled Regexp
57.3xslower(5625%)thanStrings Contains

Contributors