mtchuyen / Golang-Tips

Tips in Golang programming
3 stars 2 forks source link

Optimize A Golang Application #17

Open mtchuyen opened 2 years ago

mtchuyen commented 2 years ago

How to Optimize A Golang Application Which Has More Than 100 Million Objects at Peak?

https://groups.google.com/g/golang-nuts/c/ni1mRfenItM

Implementing The Fieldalignment Bundle in Go

https://medium0.com/@switchupcb/implementing-the-fieldalignment-bundle-in-go-1f725302a6dc

The fieldalignment bundle is a technique that you can use to minimize the amount of memory your application uses during runtime. This can result in a performance improvement due to the semantics of the Go Garbage Collector.

Struct Padding

A system’s architecture (32-bit, 64-bit) defines

  1. the size (in bits) of each word (Call: first factor)
  2. and how the memory of the system is aligned. (Call: second factor)

The first factor serves as the basis for the size of primitive types (i.e string, int, uint, etc) in the Go programming language. As an example, the sizes of basic types can be found in go/types/sizes.go:131.

The second factor serves as the basis for struct padding, which aligns the fields of structs to addresses in memory.

For more information on memory alignment, read Memory Layouts (Go) and Dealing With Maligned Structs.

Field-Alignment

Fieldalignment is the process of aligning the fields in Go structs in order to minimize the size of the struct (in memory).

As an example, reorganizing the order of a struct’s fields can reduce its size from 24 bytes to 16 bytes.

In the Go programming language, issues with fieldalignment can be fixed using the fieldalignment tool which is installed using:

go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest.

Using the fieldalignment command will tell you where mis-aligned fields are in your program.

As a WARNING, running fieldalignment -fix ./... may remove comments from your fields due to the underlying difficulty in manipulating free-floating comments within the Go Abstract Syntax Tree.

What are the benefits?

The benefits of fieldalignment are observed in bulk.

If your application saves 8 bytes per struct through fieldalignment, this will save you 8 MB of memory for every struct that is used within 1 MILLION requests. That allows you to save money on memory resources, and may even reduce the amount of peaks within the Go Garbage Collector (which improves performance over time).

To be specific, Go uses a Stop The World Garbage Collector which triggers at a target heap size. Garbage Collector latency can be reduced by maintaining the smallest heap possible at a consistent pace.

This is also the rationale behind using zero-allocation libraries.

Caveat

Fieldalignment can result in performance degradation as a compact field order can cause two variables (in separate goroutines) that are updated at the same time to occupy the same CPU cache line: This results in a form of memory contention called false sharing. When false sharing occurs, the first variable that is being updated is forced to reload a CPU cache block even though it’s not required. Thus, both goroutines updating each variable are slowed down.

Due to this caveat, it’s recommended to benchmark your fieldaligned code.

Bundle

In the context of software development, a bundle represents a collection of files or sources.

Using the bundle command bundles the application into a single file.

However, this can generate a file that contains collisions in its imports.

Implementing the Fieldalignment Bundle

The fieldalignment bundle technique involves bundling your source code, then fieldaligning it.

mtchuyen commented 2 years ago

Small number change will affect benchmark results

https://groups.google.com/g/golang-nuts/c/WtkYMKz46es

The benchmark code: https://play.golang.org/p/IqVnVa5x9qp

When N == 16384, the benchmark result:

Benchmark_Insert-4 134622 8032 ns/op 32768 B/op 1 allocs/op Benchmark_Insert2-4 132049 8201 ns/op 32768 B/op 1 allocs/op

When N == 16385, the benchmark result:

Benchmark_Insert-4 118677 9374 ns/op 40960 B/op 1 allocs/op Benchmark_Insert2-4 136845 7744 ns/op 40960 B/op 1 allocs/op

=== 16384+16384= 32768 and 16385+16384= 32769. 32768 bytes will be allocated on a memory block in the 32768 size class, which is the largest size class. 32769 bytes will be allocated on 5 pages (each page is 8192 bytes).

But the implementations of Insert and Insert2 are very similar, the allocation algorithms for them should be the same (for a specified number). So it is weird the number change causes positive impact for one implementation,effect but negative impact for the other.

mtchuyen commented 2 years ago

Optimizing a Golang Service to Reduce Over 40% CPU

https://coralogix.com/blog/optimizing-a-golang-service-to-reduce-over-40-cpu/

Optimize

mtchuyen commented 2 years ago

Profiling and optimizing Go web applications

https://artem.krylysov.com/blog/2017/03/13/profiling-and-optimizing-go-web-applications/

mtchuyen commented 2 years ago

[Golang] Writing memory efficient and CPU optimized Go Structs

https://towardsdev.com/golang-writing-memory-efficient-and-cpu-optimized-go-structs-62fcef4dbfd0

Trước khi đọc bài này, cần đọc phần cơ bản:

Golang Field Alignment: https://medium.com/@didi12468/golang-field-alignment-2e657e87668a

The number of bytes occupied by each embedded type of Go (64-bit machine)

[nob.1]: Number of byte of String: 16 bytes

type stringStruct struct {
    str unsafe.Pointer
    len int
}

[nob.2]: Number of byte of String: 24 bytes

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

Bài viết được viết lại:

https://viblo.asia/p/golang-memory-cac-loai-type-Qbq5Q7rRZD8

Không chỉ gây ảnh hưởng tới memory, còn ảnh hưởng tới cả CPU, và thời gian tính toán 1 phép tính: Xem ở When IO-bound Hides Inside CPU

mtchuyen commented 2 years ago

Golang: simple optimization notes

https://medium.com/scum-gazeta/golang-simple-optimization-notes-70bc64673980

8 coding hacks for Go that I wish I’d known when I started

https://levelup.gitconnected.com/8-code-hacks-for-go-that-i-wish-id-known-when-i-started-56a6f4399acf

3. Specify the size of the array when using append.

If you need to add items to an array, the go-to option is “append”. For example:

for _, v := range inputArray {
  myArray = append(myArray, v)
}

However, this can be slow on large arrays, because append will keep needing to increase the size of “myArray” to accommodate the new values. A better way to it is to specify the length of the array first, then direct assign each value:

myArray := make([]int, len(inputArray))
 for i, v := range inputArray {
  myArray[i] = v
 }
}

There is a third option which I like even better, and that is to combine the two! To me, it is slightly more readable but also doesn't have the speed penalty as you are assigning the size at the start.

myArray := make([]int, 0, len(inputArray))
for _, v := range inputArray {
  myArray = append(myArray, v)
}

Here you set the size of the array as 0, but the max size as the length of the input array. So append doesn't need to resize as it goes. If you compare the times on an array of 100 million integers you can see the speed difference:

normal array append took        3782.1423ms
presized array took                  549.8333ms
presized array append took     685.9402ms
mtchuyen commented 2 years ago

Working on High-Performance Golang Client Library — Remove the Bad Busy Loops With the Sync.Cond

https://betterprogramming.pub/working-on-high-performance-golang-client-library-remove-the-bad-busy-loops-with-the-sync-cond-e262b3fcb458

mtchuyen commented 9 months ago

optimizing server