编程笔记

lifelong learning & practice makes perfect

go-pprof|excel(xlsx) vs csv数据读取内存占用

一次线上报表导出的功能异常无法使用,看监控日志,服务器内存有段时间占用高
监控

除内存异常,其他组件都没发现问题,怀疑OOM导致服务挂了,查询服务运行时间,发现确实是重启了.

1
2
3
4
5
# 查看程序pid
ps -aux | grep "xxxx"

# 通过pid查看启动时间
ps -o lstart,etime -p xxx

go pprof 分析内存占用

分析异常api的功能主要是excel的读写(用的excelize这个库),便用prof测试下内存占用,这里在测试用例读取一个几万行的excel文件(.xlsx),使用go的工具pprof记录内存占用

从.xlsx文件与.csv文件读取对比看,excelizexlsx读取xlsx文件,比使用encoding/csv包读取csv文件更占内存,相差在2至3倍,前者平均需要4,5GB内存,后者仅占1.7GB左右.

综上,没有特殊需求,简单的表格文件保存可以优先选择csv

文件大小

file size

以下测试使用同样内容的文件,8w行,csv:30.1MB,xlsx:18.7MB

go pprof使用

使用命令go进行benchmark测试并保存mempprof

1
go test -benchmem -run=^$ -bench ^BenchmarkExcelizeLoadXlsx$ github.com/xxxxx -memprofile mem_xlsx.pprof

使用命令go查看pprof文件,会启动一个server支持在web页面查看

1
go tool pprof -http=:9999 mem_xlsx.pprof

xlsx读取.xlsx文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func LoadXlsx(filepath string) []*xlsx.Row {
f, err := xlsx.OpenFile(filepath)
if err != nil {
return nil
}
fmt.Println("sheets:", len(f.Sheets))
if len(f.Sheets) == 0 {
return nil
}
s := f.Sheets[0]
return s.Rows
}

func BenchmarkLoadXlsx(b *testing.B) {
filePath := "./80000.xlsx"
for i := 0; i < b.N; i++ {
all := LoadXlsx(filePath)

if all == nil {
return
}
}
}

读取xlsx

在Ubuntu上测试读取xlsx

excelize读取.xlsx文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import (
"testing"

"github.com/xuri/excelize/v2"
)

func ExcelizeLoadXlsx(filepath string) [][]string {

f, err := excelize.OpenFile(filepath)
if err != nil {
return nil
}
defer f.Close()

rs, err := f.GetRows("Sheet1")
if err != nil {
return nil
}
return rs
}

func BenchmarkExcelizeLoadXlsx(b *testing.B) {
for i := 0; i < b.N; i++ {
filePath := "./80000.xlsx"
all := ExcelizeLoadXlsx(filePath)

if all == nil {
return
}
}
}

excelize读取

encoding/csv读取csv文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import (
"encoding/csv"
"log"
"os"
"testing"
"time"

"github.com/google/uuid"
)

// csv文件读取
func ReadCsv(filepath string) [][]string {
//打开文件(只读模式),创建io.read接口实例
opencast, err := os.Open(filepath)
if err != nil {
log.Println("csv文件打开失败!")
return nil
}
defer opencast.Close()

//创建csv读取接口实例
ReadCsv := csv.NewReader(opencast)

//读取所有内容
readAll, err := ReadCsv.ReadAll() //返回切片类型:[[s s ds] [a a a]]
if err != nil {
return nil
}
return readAll
}

func BenchmarkLoad(t *testing.B) {
filePath := "./80000.csv"

for i := 0; i < b.N; i++ {
all := ReadCsv(filePath)

if all == nil {
return
}
}
}

csv读取

csv读取

欢迎关注我的其它发布渠道