优化一个已有的 Go 程序,提高其性能并减少资源占用 | 青训营

53 阅读4分钟

性能优化的方向

延迟

常见解决方法:

  1. 减少程序中的循环和递归,使用更高效的算法。
  2. 减少程序中的内存访问,使用缓存和预读取技术。
  3. 减少程序中的锁和同步操作,使用并发编程技术。
  4. 使用更快的硬件,如更快的CPU、更快的硬盘等。
  5. 使用更快的网络连接,如更快的网卡、更快的路由器等。

资源效率

常见解决方法:

  1. 减少程序中的内存访问,使用缓存和预读取技术。
  2. 减少程序中的锁和同步操作,使用并发编程技术。
  3. 使用更快的硬件,如更快的CPU、更快的硬盘等。
  4. 使用更快的网络连接,如更快的网卡、更快的路由器等。
  5. 优化算法和数据结构,减少程序中的循环和递归。

算法效率

常见解决方法

  1. 选择合适的数据结构和算法,减少时间复杂度和空间复杂度。
  2. 对算法进行优化,如使用动态规划、贪心算法等。
  3. 利用并行计算技术,如GPU、FPGA等。
  4. 利用缓存技术,如LRU缓存、LFU缓存等。

提高 Golang 程序性能的最佳实践

 并行化 CPU 工作

同步操作在计算机中通常需要花费大量时间,因此当您使用可用内核并行工作时,它肯定会优化Golang应用程序的性能。这是线性加速应用程序执行的重要一步。这也属于Golang相对于其他语言的天然优势(自带弹药库)。另外,尽管并行不一定总是最优的解决方案,但对于可以并行处理的场景,使用并行化技术肯定对我们有利,因为Golang具有天生的优势。

下面我们以计算目录下所有文件的总大小为例。通过对比非并行和并行两种方式来展示其差异。

首先,我们定义一个函数来计算单个文件的大小:

func getFileSizeInBytes(filePath string) (int64, error) {
    fileInfo, err := os.Stat(filePath)
    if err != nil {
        return 0, err
    }
    return fileInfo.Size(), nil
}

接下来,我们分别使用非并行和并行的方式来计算目录下所有文件的总大小:

非并行方式:

package main

import (
 "fmt"
 "io/ioutil"
 "os"
 "path/filepath"
)

func getTotalSizeOfDirectory(dirPath string) (int64, error) {
 var totalSize int64
 err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
  if err != nil {
   return err
  }
  if !info.IsDir() {
   size, err := getFileSizeInBytes(path)
   if err != nil {
    return err
   }
   totalSize += size
  }
  return nil
 })
 if err != nil {
  return totalSize, err
 }
 return totalSize, nil
}




    

在非并行方式中,我们使用filepath.Walk函数遍历目录下的所有文件,并对每个文件调用getFileSizeInBytes函数获取其大小。然后将所有文件的大小累加起来得到总大小。

并行方式:

package main

import (
 "fmt"
 "io/ioutil"
 "os"
 "path/filepath"
 "sync"
)

type FileInfo struct {
 Path         string `json:"path"` // 文件路径
 Size          int64      `json:"size"` // 文件大小
 IsDir         bool     `json:"is_dir"` // 是否为目录
}
var fileInfos []FileInfo = make([]FileInfo, 0) // 存储文件信息的切片
var fileSizes []int64 = make([]int64, 0) // 存储文件大小的切片
var fileMutex sync.Mutex // 用于线程安全访问共享数据 'fileSizes' 的互斥锁
var filesCount int           // 已处理的文件数量。用于计算进度百分比。
var totalSize int64         // 已处理的所有文件的总大小。用于计算进度百分比。
var lock sync.Mutex // 用于线程安全访问共享数据 'lock' 的互斥锁
var doneChan = make(chan bool) // 用于信号操作完成的通道。当这个通道被一个goroutine接收时,表示操作已完成,我们可以关闭doneChan通道。我们使用这个通道来控制程序的流程。当doneChan通道关闭时,不会再启动新的goroutine。主goroutine等待此通道关闭后再退出。当doneChan通道未关闭时,不会启动新的goroutine,现有的goroutine将继续运行,直到它们完成任务或在doneChan通道上接收到值。通过使用这种机制,我们可以防止多个goroutine同时访问和修改共享数据时的竞争条件。在这种情况下,我们使用锁来确保只有一个goroutine可以同时访问和修改'filesCount'和'totalSize'变量。一旦这些变量被更新,其他goroutine必须等待锁被释放后才能再次访问和修改它们。