Make Your Go Code Work 1.5x Faster or Even More
This article will explain how to make your GO code run faster. Performance is key in everything; we want to save time.
Join the DZone community and get the full member experience.
Join For FreePerformance is a key thing in everything; we (humans) don't like to wait and waste our time. Therefore, sometimes quick solutions, as most of the managers suppose, are better than slow ones but with good engineering and design. But today, we are not speaking about management but about code performance. We have a small text formatting library that allows us to format text using a template in a convenient on our view way. These not only formatting functions do what fmt.Sprintf
does this in a more convenient way but also provides additional features. Previous versions of our module were loose in performance to fmt.Sprintf
, but since 1.0.1, we are better. And today, we are going to tell you how to make any golang code work faster.
Parameters and Return Values
There are two options to pass either argument and/or get function result - by pointer and by value; consider the following example:
func getItemAsStr(item *interface{}) string // 1st variant
func getItemAsStr(item interface{}) string // 2nd variant
func getItemAsStr(item *interface{}) *string // 3rd variant
func getItemAsStr(item interface{}) *string // 4th variant
According to our performance tests, we could conclude the following:
- We've got a small performance rise when we pass arguments by pointer because we've got rid of arguments copying.
- We've got a performance decrease when we return a pointer to the function local variable.
Therefore, the most optimal variant is the first variant.
Strings
Strings are immutable like in many other programming languages; therefore, string concatenation using the + operator is a bad idea, i.e. code like this is very slow:
var result string = ""
for _, arg := range args { result += getItemAsStr(&arg)
}
Every "+" creates a new string object; as a result, memory usage rise, and performance significantly decreases due to spending sufficient time on allocations to new variables.
There is a better solution for string Concat — use strings.Builder, but for better performance, you should initially enlarge the buffer (using Grow
function) to prevent it from some / all re-allocs, but if the buffer size will be very large, there will be a penalty to performance due to initial memory allocation will be slow; therefore you should choose initial buffer size wisely, i.e.:
var formattedStr = &strings.Builder{}
formattedStr.Grow(templateLen + 22*len(args))
Cycles
There are two ways to iterate over the collection:
- Using
range
expression:
for _, arg := range args { // do some staff here }
- Using traditional
for
with three expressions:
for i := start; i < templateLen; i++ { // do some staff here }
The second variant is better in performance. But there are additional advantages of usage for
three expressions:
- Reduce the number of iterations; more iteration code is slower; therefore, if you could affect your loop value, do it. This can't be achieved with
range
; - Set the initial value for iteration much higher if it is possible, i.e. in our case:
start := strings.Index(template, "{") if start < 0 { return template }
formattedStr.WriteString(template[:start]) for i := start; i < templateLen; i++ { // iterate over i }
Conclusion
Using such simple techniques, we made our code run 1.5 times faster than it was, and now it works faster even than fmt.Sprintf, see our performance measurements:
Published at DZone with permission of Michael Ushakov. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments