性能优化的方向
延迟
常见解决方法:
- 减少程序中的循环和递归,使用更高效的算法。
- 减少程序中的内存访问,使用缓存和预读取技术。
- 减少程序中的锁和同步操作,使用并发编程技术。
- 使用更快的硬件,如更快的CPU、更快的硬盘等。
- 使用更快的网络连接,如更快的网卡、更快的路由器等。
资源效率
常见解决方法:
- 减少程序中的内存访问,使用缓存和预读取技术。
- 减少程序中的锁和同步操作,使用并发编程技术。
- 使用更快的硬件,如更快的CPU、更快的硬盘等。
- 使用更快的网络连接,如更快的网卡、更快的路由器等。
- 优化算法和数据结构,减少程序中的循环和递归。
算法效率
常见解决方法
- 选择合适的数据结构和算法,减少时间复杂度和空间复杂度。
- 对算法进行优化,如使用动态规划、贪心算法等。
- 利用并行计算技术,如GPU、FPGA等。
- 利用缓存技术,如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必须等待锁被释放后才能再次访问和修改它们。