- vừa được xem lúc

Golang: Top 5 Cách Để Tăng Performance

0 0 4

Người đăng: Phuong Le

Theo Viblo Asia

1. Tránh sử dụng reflection

Reflection là một tính năng mạnh mẽ của Go và các ngôn ngữ lập trình khác, cho phép chúng ta phân tích và sửa đổi cấu trúc của kiểu (type) cũng như hành vi của nó tại runtime.

Bạn có thể sử dụng reflection để xác định kiểu của một giá trị, truy cập các trường của nó và gọi các phương thức của nó như sau:

package main import ( "fmt" "reflect" ) func main() { x := 100 v := reflect.ValueOf(x) t := v.Type() fmt.Println("Type:", t) // "Type: int" 
}

Tuy nhiên, khi sử dụng reflection, nó liên quan đến việc phân tích và thao tác với các giá trị trong thời gian chạy (runtime), thay vì tại thời điểm biên dịch (compile time).

Hệ thống runtime của Go phải thực hiện thêm công việc để xác định kiểu và cấu trúc của giá trị được reflect thay vì compile, điều này có thể tăng thêm workloads cho go-runtime và làm chậm chương trình.

"Tại sao runtime lại thêm việc?"

Đơn cử cách chúng ta sử dụng biến, nếu các biến đã được define trước như hằng số (const - compile time) chẳng hạn, so với các biến động (dynamic - runtime), thì tất nhiên hằng số luôn có performance tốt hơn.

Reflection cũng có thể khiến code khó đọc và hiểu hơn, điều này có thể ảnh hưởng đến hiệu suất làm việc của team.

2. Tránh nối chuỗi bằng +

Thường hiệu quả hơn nếu sử dụng kiểu bytes.Buffer để xây dựng chuỗi thay vì nối chuỗi bằng toán tử +.

Trước tiên, chúng ta hãy xem xét đoạn code sau:

s := "" for i := 0; i < 100000; i++ { s += "x" } fmt.Println(s)

Đoạn code này sẽ tạo ra 3 chuỗi mới trên mỗi vòng lặp bao gồm s, s+xx bởi string là mutable type. Điều này có thể không hiệu quả và có thể dẫn đến hiệu suất kém.

Thay vào đó, bạn có thể sử dụng bytes.Buffer để xây dựng chuỗi một cách hiệu quả hơn:

var buffer bytes.Buffer for i := 0; i < 100000; i++ { buffer.WriteString("x") } s := buffer.String() fmt.Println(s)

Dưới đây là một giải pháp khác: sử dụng strings.Builder. Cách sử dụng của nó tương tự như bytes.Buffer, nhưng nó cung cấp hiệu suất tốt hơn:

var builder strings.Builder for i := 0; i < 100000; i++ { builder.WriteString("x") } s := builder.String() fmt.Println(s)

“Có benchmark hay gì không?”

Mình đã so sánh 2 cách này và kết quả:

  • Sử dụng bytes.Buffer nhanh hơn đáng kể so với việc sử dụng nối chuỗi bằng +, cải thiện performance lên đến > 250x trong một số trường hợp.
  • Sử dụng strings.Builder nhanh hơn khoảng 1.5 lần so với bytes.Buffer

Và cần lưu ý rằng mức độ tăng performance chính xác có thể thay đổi tùy thuộc vào các yếu tố như CPU gì? ngữ cảnh chạy của code? máy của bạn đang xử lý tác vụ nào...

“Tại sao strings.Builder nhanh hơn bytes.Buffer?”

Điều này là bởi vì strings.Builder được tối ưu hóa cho việc xây dựng chuỗi. Ngược lại, bytes.Buffer là một bộ đệm (buffer) được design cho mục đích chung có thể được sử dụng để xây dựng bất kỳ loại dữ liệu nào, nhưng có thể không được tối ưu hóa cho việc xây dựng chuỗi như strings.Builder.

3. Cấp phát trước cho slice, map

Cấp phát một slice với dung lượng phù hợp với số lượng phần tử mà nó dự kiến sẽ tăng performance đáng kể cho phần mềm, đặc biệt là trong Go. Đồng thời thì đây là một trong những tips cải thiện hiệu suất khá tốt mà mình ít thấy nhiều người sử dụng, bởi khó trong việc ước tính số lượng phần tử cho slice.

Performance tăng bởi việc cấp phát một slice với dung lượng lớn hơn có thể giảm số lần thay đổi kích thước của slice khi thêm phần tử.

Dưới đây là benchmark đơn giản:

func main() { // Allocate a slice with a small capacity  start := time.Now() s := make(\[\]int, 0, 10) for i := 0; i < 100000; i++ { s = append(s, i) } elapsed := time.Since(start) fmt.Printf("Allocating slice with small capacity: %v\\n", elapsed) // 1.165208ms  // Allocate a slice with a larger capacity  start = time.Now() s = make(\[\]int, 0, 100000) for i := 0; i < 100000; i++ { s = append(s, i) } elapsed = time.Since(start) fmt.Printf("Allocating slice with larger capacity: %v\\n", elapsed) // 361.333µs 
}

Vâng, chúng ta đã có thể tăng performance x3 với việc cấp phát trước.

Nếu bạn muốn hiểu tại sao việc cấp phát trước nhanh hơn, mình đã viết một bài giải thích chi tiết trong một bài viết về slices: why pre-allocation is faster.

4. Tránh sử dụng interfaces với một kiểu cụ thể duy nhất

Nếu bạn biết rằng một interface chỉ có một loại implement duy nhất, bạn có thể sử dụng loại cụ thể trực tiếp để tránh gánh nặng do interface.

Việc sử dụng trực tiếp kiểu cụ thể có thể hiệu quả hơn việc sử dụng interface vì nó tránh việc lưu trữ kiểu và giá trị trong interface.

Dưới đây là một ví dụ so sánh hiệu suất giữa việc sử dụng interface và sử dụng trực tiếp kiểu cụ thể trong Go:

type Shape interface { Area() float64 } type Circle struct { radius float64 } func (c \*Circle) Area() float64 { return 3.14 \* c.radius \* c.radius } func main() { // Use the Shape interface  start := time.Now() var s Shape = &Circle{radius: 10} for i := 0; i < 100000; i++ { s.Area() } elapsed := time.Since(start) fmt.Printf("Using Shape interface: %s\\n", elapsed) // Using Shape interface: 358µs  // Use the Circle type directly  start = time.Now() c := Circle{radius: 10} for i := 0; i < 100000; i++ { c.Area() } elapsed = time.Since(start) fmt.Printf("Using Circle type directly: %s\\n", elapsed) // Using Circle type directly: 341.917µs 
}

Sử dụng interface mất 358μs, kiểu cụ thể mất 342μs.

Cần lưu ý rằng kỹ thuật này chỉ nên được sử dụng khi bạn chắc chắn rằng một interface chỉ bao giờ có một kiểu cụ thể duy nhất (1 type implement interface)

5. Sử dụng govet

govet là một công cụ phân tích tĩnh mà không cần chạy code, nó vẫn có thể giúp bạn tìm ra những vấn đề tiềm ẩn trong code của bạn.

Để sử dụng govet, bạn có thể chạy lệnh go tool vet và truyền tên của các tệp nguồn Go mà bạn muốn kiểm tra làm đối số:

go tool vet main.go

Bạn cũng có thể truyền flag -all vào go tool vet để kiểm tra tất cả các tệp nguồn Go trong thư mục hiện tại và các thư mục con của nó:

go tool vet -all

““Govet quá spam. Một số lỗi không cần phải report.”

Bạn có thể tùy chỉnh hành vi của govet bằng cách viết "vet comments" trong mã của bạn. vet comment là những comment đặc biệt để báo cho govet biết phải bỏ qua những vấn đề nhất định.

Dưới đây là một ví dụ về một vet comment cho biết govet không chú ý đến một biến không sử dụng:

func main() { var x int //go:noinline _ = x }

Claims

Bài viết được dịch từ Go Performance Boosters: The Top 5 Tips and Tricks You Need to Know

Bình luận

Bài viết tương tự

- vừa được xem lúc

gRPC - Nó là gì và có nên sử dụng hay không?

Nhân một ngày rảnh rỗi, mình ngồi đọc lại RPC cũng như gRPC viết lại để nhớ lâu hơn. Vấn đề là gì và tại sao cần nó .

0 0 112

- vừa được xem lúc

Embedded Template in Go

Getting Start. Part of developing a web application usually revolves around working with HTML as user interface.

0 0 40

- vừa được xem lúc

Tạo Resful API đơn giản với Echo framework và MySQL

1. Giới thiệu.

0 0 44

- vừa được xem lúc

Sử dụng goquery trong golang để crawler thông tin các website Việt Nam bị deface trên mirror-h.org

. Trong bài viết này, mình sẽ cùng mọi người khám phá một package thu thập dữ liệu có tên là goquery của golang. Mục tiêu chính của chương trình crawler này sẽ là lấy thông tin các website Việt Nam bị deface (là tấn công, phá hoại website, làm thay đổi giao diện hiển thị của một trang web, khi người

0 0 218

- vừa được xem lúc

Tạo ứng dụng craw dữ liệu bing với Golang, Mysql driver

Chào mọi người . Lâu lâu ta lại gặp nhau 1 lần, để tiếp tục series chia sẻ kiến thức về tech, hôm nay mình sẽ tìm hiểu và chia sẻ về 1 ngôn ngữ đang khá hot trong cộng đồng IT đó là Golang.

0 0 59

- vừa được xem lúc

Golang: Rest api and routing using MUX

Routing with MUX. Let's create a simple CRUD api for a blog site. # All . GET articles/ .

0 0 39