Do not use Logrus
This is actually related to generics. Because Go is a strongly typed static language, it is impossible for you to bypass data types like NodeJS or PHP. So what if we still need to use a common type? For example, Loger or ORM, because only when a general type is used can you write common code, otherwise each one has to be written.
In the end, we can only use reflection. Logrus uses reflection heavily, which results in a large allocation count. While not usually a big problem (depending on the code), performance is important, especially in large-scale, highly concurrent projects. While this may sound like a very small optimization, it is important to avoid reflections. If you see some code that can use structures without considering types, it uses reflection and has an impact on performance.
For example, Logrus doesn't care about the type, but obviously Go needs to know (eventually). What should I do with Logrus? Use reflection to detect types, which is the overhead.
({<!--{C}%3C!%2D%2D%20%2D%2D%3E--> “animal”: myWhatever, }).Info(“A walrus appears”)
So I would prefer zerolog, and of course zap is also good. Both claim zero allocation, which is what we hope for, as they have minimal performance impact.
Don't use encoding/json
When we need a function or function, many people recommend using the standard library. But the encoding/json module in the standard library is an exception. In fact, like the example above, encoding/json uses reflection, which will cause low performance and will cause losses when writing APIs that return json responses or microservices.
For example, you can use Easyjson, which is simple and efficient, it uses a code generator to create the code you need to convert the structure to json to minimize allocation. This is a manual build step and it's annoying. Interestingly, json-iterator also uses reflection, but is significantly faster, which I suspect is black magic.
Don't use closures in goroutine as much as possible
For example, the following example code:
for i:=0;i<10;i++ { go func() { (i) }() }
Most people may expect this to print numbers 0 to 9, just like when delegating a task to a goroutine.
But the actual result: According to the system, you will get one or two numbers and many 10.
Why is this happening? Closures can access the parent scope, so variables can be used directly. Although updated linters may warn you of "variable closure capture", it will not require you to redeclare the variable.
Go's performance reputation is largely due to runtime optimizations for execution, which attempts to "guess" what you want to do and optimize some execution paths. During this time, it "captures" the variables and passes them to where they are needed in the most theoretically efficient way (for example, after some non-concurrency operations are done to free allocations on some CPUs). The result in this case is that the loop may start goroutines, which may receive the value of i from the parent scope very late. There is no guarantee which one you will see when you execute this code multiple times, it may be the number 10 or other numbers.
If you do use closures for some reason, be sure to pass the variable i, treat closures just like every function.
Summarize
This is the end of this article about 3 tips for efficient Go programming. For more relevant content on efficient Go programming, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!