优化Go文件读取程序

时间:2012-10-06 00:16:12

标签: optimization go

我正在尝试处理一个日志文件,其每一行看起来像这样:

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.FieldsFuncunicode.IsSpaceruntime.stringiter2上。

如何让这次跑得更快?

1 个答案:

答案 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