我正在尝试处理一个日志文件,其每一行看起来像这样:
flow_stats: 0.30062869162666672 gid 0 fid 1 pkts 5.0 fldur 0.30001386666666674 avgfldur 0.30001386666666674 actfl 3142 avgpps 16.665896331902879 finfl 1
我对pkts
字段和fldur
字段感兴趣。我有一个Python脚本,可以读取百万行日志文件,为所有不同持续时间的每个数据包创建一个列表,对这些列表进行排序,并在大约3秒内计算中位数。
我正在玩Go编程语言,并认为我会重写它,希望它会更快运行。
到目前为止,我一直很失望。只需将文件读入数据结构大约需要5.5秒。所以我想知道你们中的一些人是否可以帮助我更快地完成这项工作(呵呵)。
这是我的循环:
data := make(map[int][]float32)
infile, err := os.Open("tmp/flow.tr")
defer infile.Close()
if err != nil {
panic(err)
}
reader := bufio.NewReader(infile)
line, err := reader.ReadString('\n')
for {
if len(line) == 0 {
break
}
if err != nil && err != io.EOF {
panic(err)
}
split_line := strings.Fields(line)
num_packets, err := strconv.ParseFloat(split_line[7], 32)
duration, err := strconv.ParseFloat(split_line[9], 32)
data[int(num_packets)] = append(data[int(num_packets)], float32(duration))
line, err = reader.ReadString('\n')
}
请注意,我确实检查了循环中的err
- 为简洁起见,我省略了它。 google-pprof
表示大部分时间都花费在strings.Fields
strings.FieldsFunc
,unicode.IsSpace
和runtime.stringiter2
上。
如何让这次跑得更快?
答案 0 :(得分:7)
更换
split_line := strings.Fields(line)
带
split_line := strings.SplitN(line, " ", 11)
在1M线随机生成的文件上提高了4倍的速度,该文件模仿了您在上面提供的格式:
strings.Fields version:在4.232525975s
中完成strings.SplitN version:在1.111450755s中完成
一些效率来自于能够在分割持续时间之后避免解析和分割输入行,但大多数来自SplitN中更简单的分裂逻辑。即使拆分所有字符串也不会比持续时间后停止更长的时间。使用:
split_line := strings.SplitN(line, " ", -1)
于1.554971313s完成
SplitN和Fields不一样。 Fields假定标记由1个或多个空格字符限定,其中SplitN将标记视为由分隔符字符串限定的任何内容。如果您的输入在标记之间有多个空格,则split_line将为每对空格包含空标记。
排序和计算中位数不会增加太多时间。在排序时,为了方便起见,我将代码更改为使用float64而不是float32。这是完整的程序:
package main
import (
"bufio"
"fmt"
"os"
"sort"
"strconv"
"strings"
"time"
)
// SortKeys returns a sorted list of key values from a map[int][]float64.
func sortKeys(items map[int][]float64) []int {
keys := make([]int, len(items))
i := 0
for k, _ := range items {
keys[i] = k
i++
}
sort.Ints(keys)
return keys
}
// Median calculates the median value of an unsorted slice of float64.
func median(d []float64) (m float64) {
sort.Float64s(d)
length := len(d)
if length%2 == 1 {
m = d[length/2]
} else {
m = (d[length/2] + d[length/2-1]) / 2
}
return m
}
func main() {
data := make(map[int][]float64)
infile, err := os.Open("sample.log")
defer infile.Close()
if err != nil {
panic(err)
}
reader := bufio.NewReaderSize(infile, 256*1024)
s := time.Now()
for {
line, err := reader.ReadString('\n')
if len(line) == 0 {
break
}
if err != nil {
panic(err)
}
split_line := strings.SplitN(line, " ", 11)
num_packets, err := strconv.ParseFloat(split_line[7], 32)
if err != nil {
panic(err)
}
duration, err := strconv.ParseFloat(split_line[9], 32)
if err != nil {
panic(err)
}
pkts := int(num_packets)
data[pkts] = append(data[pkts], duration)
}
for _, k := range sortKeys(data) {
fmt.Printf("pkts: %d, median: %f\n", k, median(data[k]))
}
fmt.Println("\nCompleted in ", time.Since(s))
}
输出:
pkts: 0, median: 0.498146
pkts: 1, median: 0.511023
pkts: 2, median: 0.501408
...
pkts: 99, median: 0.501517
pkts: 100, median: 0.491499
Completed in 1.497052072s