DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • DataWeave: Play With Dates (Part 1)
  • Tired of Messy Code? Master the Art of Writing Clean Codebases
  • The Long Road to Java Virtual Threads
  • Exploring Exciting New Features in Java 17 With Examples

Trending

  • The Modern Data Stack Is Overrated — Here’s What Works
  • How AI Agents Are Transforming Enterprise Automation Architecture
  • Understanding IEEE 802.11(Wi-Fi) Encryption and Authentication: Write Your Own Custom Packet Sniffer
  • Accelerating AI Inference With TensorRT
  1. DZone
  2. Data Engineering
  3. Data
  4. Optimizing String Comparisons in Go

Optimizing String Comparisons in Go

By 
Jeremy Morgan user avatar
Jeremy Morgan
·
Sep. 21, 20 · Analysis
Likes (4)
Comment
Save
Tweet
Share
7.2K Views

Join the DZone community and get the full member experience.

Join For Free

Want your Go programs to run faster? Optimizing string comparisons in Go can improve your application’s response time and help scalability. Comparing two strings to see if they’re equal takes processing power, but not all comparisons are the same. In a previous article, we looked at How to compare strings in Go and did some benchmarking. We’re going to expand on that here.

It may seem like a small thing, but as all great optimizers know, it’s the little things that add up. Let’s dig in.

Measuring Case Sensitive Compares

First, let’s measure two types of string comparisons.

Method 1: Using Comparison Operators

Go
xxxxxxxxxx
1
 
1
if a == b {  return true
2
}else {  return false
3
}

Method 2: Using Strings.Compare

Go
xxxxxxxxxx
1
 
1
if strings.Compare(a, b) == 0 {  return true
2
}
3
return false


So we know the first method is a bit easier. We don’t need to bring in any packages from the standard library, and it’s a bit less code. Fair enough, but which one is faster? Let’s find out.

Initially, we’re going to set up a single app with a test file. We’re going to use the Benchmarking utility from the Go test tools.

compare.go

Go
xxxxxxxxxx
1
23
 
1
package main
2
3
import (
4
    "strings"
5
)
6
7
func main() {
8
}
9
10
// operator compare
11
func compareOperators(a string, b string) bool {  if a == b {
12
       return true
13
    }else {
14
       return false
15
    }
16
}
17
18
// strings compare
19
func compareString(a string, b string) bool {  if strings.Compare(a, b) == 0 {
20
       return true
21
    }
22
    return false
23
}


And we’ll create a set of tests for it:

compare_test.go

Go
xxxxxxxxxx
1
17
 
1
package main
2
3
import (
4
    "testing"
5
)
6
7
func BenchmarkCompareOperators(b *testing.B) {
8
    for n := 0; n < b.N; n++ {
9
        compareOperators("This is a string", "This is a strinG")
10
    }
11
}
12
13
func BenchmarkCompareString(b *testing.B) {
14
    for n := 0; n < b.N; n++ {
15
        compareString("This is a string", "This is a strinG")
16
    }
17
}


For the string samples, I will change the last character to make sure the methods parse the entire string.

Some Notes if you’ve never done this:

  • We are using the Go testing package
  • By naming it compare_test.go Go knows to look for tests here.
  • Instead of tests, we insert benchmarks. Each func must be preceded by Benchmark
  • We will run our tests with bench flag

To run our benchmarks, use this command:

Go
xxxxxxxxxx
1
 
1
go test -bench=.


Here are my results:

Benchmarking in terminal

So using standard comparison operators is faster than using the method from the Strings package. 7.39 nanoseconds vs. 2.92.

Running the test several times shows similar results:

Benchmarking in terminal

So, it’s clearly faster. 5ms can make a big difference at a large enough scale.

Verdict: Basic string comparison is faster than strings package comparison for case sensitive string compares.

Measuring Case Insensitive Compares

Let’s change it up. Generally, when I’m doing a string compare, I want to see if the test in the string matches, no matter which characters are capitalized. This adds some complexity to our operation.

Go
xxxxxxxxxx
1
 
1
sampleString := "This is a sample string"
2
compareString := "this is a sample string"


With a standard compare, these two strings are not equal because the T is capitalized.

However, we are now looking for the text, and don’t care how it’s capitalized. So let’s change our functions to reflect this:

Go
xxxxxxxxxx
1
15
 
1
// operator compare
2
func compareOperators(a string, b string) bool {
3
    if strings.ToLower(a) == strings.ToLower(b) {
4
        return true
5
    }
6
    return false
7
}
8
9
// strings compare
10
func compareString(a string, b string) bool {
11
    if strings.Compare(strings.ToLower(a), strings.ToLower(b)) == 0 {
12
        return true
13
    }
14
    return false
15
}


Now before each comparison, we make both strings lowercase. We’re adding in some extra cycles to be sure. Let’s benchmark them.

Benchmarking in terminal

They appear to be the same. I run it a few times to be sure:

Benchmarking in terminal

Yep, they’re the same. But why?

One reason is, we’ve added the Strings.ToLower action to every execution. This is a performance hit. Remember strings are simple runes, and the ToLower() method loops through the rune, making every character lowercase, then performs a comparison. This extra time washes out any big differences between the actions.

Introducing EqualFold

In our last article, we looked at EqualFold as another way of doing case insensitive compares. We determined Equalfold was the fastest of the three methods. Let’s see if this set of benchmark reflects that.

Add this to compare.go

Go
xxxxxxxxxx
1
 
1
// EqualFold compare
2
func compareEF(a string, b string) bool {
3
    if strings.EqualFold(sampleString, compareString) {
4
        return true
5
    }else {
6
        return false
7
    }
8
}


And add the following test to compare_test.go

Go
xxxxxxxxxx
1
 
1
func BenchmarkEqualFold(b *testing.B) {
2
    for n := 0; n < b.N; n++ {
3
        compareEF("This is a string", "This is a strinG")
4
    }
5
}


So lets now run a benchmark on these three methods:

Benchmarking in terminal

Wow! EqualFold is considerably faster. I run it several times with the same results.

Why is it faster? BecauseEqualfold also parses the rune character by character, but it “drops off early” when it finds a different character.

Verdict: EqualFold (Strings Package) comparison is faster for case sensitive string compares.

Let’s Ramp Up Our testing

Ok, so we know these benchmarks show significant differences between the methods. Let’s add in some more complexity, shall we?

In the last article, we added in this 200,000 line word list to make comparisons. We’ll change our methods to open up this file and run string comparisons till we find a match.

Benchmarking in terminal

In this file, I added the name we’re searching for at the end of the file, so we know the test will loop through 199,000 words before matching.

Change your methods to look like this:

compare.go

Go
xxxxxxxxxx
1
59
 
1
// operator compare
2
func compareOperators(a string) bool {
3
    file, err := os.Open("names.txt")
4
    result := false;
5
     if err != nil {
6
        log.Fatalf("failed opening file: %s", err)
7
    }
8
     scanner := bufio.NewScanner(file)
9
    scanner.Split(bufio.ScanLines)
10
     for scanner.Scan() {
11
        if strings.ToLower(a) == strings.ToLower(scanner.Text()) {
12
            result = true
13
        }else {
14
            result = false
15
        }
16
    }
17
    file.Close()
18
    return result
19
}
20
21
// strings compare
22
func compareString(a string) bool {
23
    file, err := os.Open("names.txt")
24
    result := false;
25
     if err != nil {
26
        log.Fatalf("failed opening file: %s", err)
27
    }
28
     scanner := bufio.NewScanner(file)
29
    scanner.Split(bufio.ScanLines)
30
     for scanner.Scan() {
31
        if strings.Compare(strings.ToLower(a), strings.ToLower(scanner.Text())) == 0  {
32
            result = true
33
        }else {
34
            result = false
35
        }
36
    }
37
    file.Close()
38
    return result
39
}
40
41
// EqualFold compare
42
func compareEF(a string) bool {
43
    file, err := os.Open("names.txt")
44
    result := false;
45
     if err != nil {
46
        log.Fatalf("failed opening file: %s", err)
47
    }
48
     scanner := bufio.NewScanner(file)
49
    scanner.Split(bufio.ScanLines)
50
     for scanner.Scan() {
51
        if strings.EqualFold(a, scanner.Text()) {
52
            result = true
53
        }else {
54
            result = false
55
        }
56
    }
57
    file.Close()
58
    return result
59
}


Yes, each of these are now going to:

  • Open a text file
  • Parse it line by line
  • Look for the search phrase

Let’s change our tests now, so the funcs only take one parameter:

compare_test.go

Go
xxxxxxxxxx
1
17
 
1
func BenchmarkCompareOperators(b *testing.B) {
2
    for n := 0; n < b.N; n++ {
3
        compareOperators("Immanuel1234")
4
    }
5
}
6
7
func BenchmarkCompareString(b *testing.B) {
8
    for n := 0; n < b.N; n++ {
9
        compareString("Immanuel1234")
10
    }
11
}
12
13
func BenchmarkEqualFold(b *testing.B) {
14
    for n := 0; n < b.N; n++ {
15
        compareEF("Immanuel1234")
16
    }
17
}


So now, we can expect the test to take longer, so there will be fewer iterations from the benchmarking tool. Let’s run it:

Benchmarking in terminal

And EqualFold still comes out on top, by quite a bit.

Adding to the complexity of this test is good and bad.

Good: Reading in text and doing sequential tests is more “real life” simulation Good: We can force more diverse testing with different strings Bad: We introduce several factors (file reading, etc.) that could skew our results.

Verdict: EqualFold (Strings Package) comparison is STILL faster for case sensitive string compares.

But Wait, There’s More!!

Is there any way we can make this comparison even faster? Of course. I decided to try counting the characters of the string. If the character count is different, it’s not the same string, so we can “duck out early” on the comparison altogether.

But we still need to include EqualFold in case the strings are equal length but different characters. The added check of the count makes the operation more expensive, so would it be faster? Let’s find out.

compare.go

Go
xxxxxxxxxx
1
18
 
1
func compareByCount(a string) bool {
2
    file, err := os.Open("names.txt")
3
    result := false;
4
     if err != nil {
5
        log.Fatalf("failed opening file: %s", err)
6
    }
7
     scanner := bufio.NewScanner(file)
8
    scanner.Split(bufio.ScanLines)
9
     for scanner.Scan() {
10
        if len(a) == len(scanner.Text()) &&  strings.EqualFold(a, scanner.Text()){
11
            result = true
12
        }else {
13
            result = false
14
        }
15
    }
16
    file.Close()
17
    return result
18
}

compare_test.go

Go
xxxxxxxxxx
1
 
1
func BenchmarkCompareByCount(b *testing.B){
2
    for n := 0; n < b.N; n++ {
3
        compareByCount("Immanuel1234")
4
    }
5
}


Benchmarking in terminal

And it is indeed faster! Every little bit counts.

Verdict: Do a character count with your EqualFold comparison for even more speed

Summary

In this article, we looked at a few different string comparison methods and which one is faster. Bottom line: Use a basic comparison for case sensitive comparisons, and character count + EqualFold for case insensitive comparisons.

I love doing tests like this, and you’ll find small changes add up pretty nice when you’re doing optimization. Stay tuned for more articles where we look at optimizations like this.

What do you think? Let me know!

Strings Comparison (grammar) Data Types

Published at DZone with permission of Jeremy Morgan, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • DataWeave: Play With Dates (Part 1)
  • Tired of Messy Code? Master the Art of Writing Clean Codebases
  • The Long Road to Java Virtual Threads
  • Exploring Exciting New Features in Java 17 With Examples

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!