2

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

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循环中的 s ——为简洁起见,我省略了它。 google-pprof表示大部分时间都花在strings.Fields、和strings.FieldsFunc上。unicode.IsSpaceruntime.stringiter2

我怎样才能让它运行得更快?

4

1 回答 1

7

更换

split_line := strings.Fields(line)

split_line := strings.SplitN(line, " ", 11)

在模仿您上面提供的格式的 1M 行随机生成的文件上产生了约 4 倍的速度提升:

strings.Fields 版本:4.232525975s 完成

strings.SplitN 版本:1.111450755s 完成

一些效率来自于能够避免在分割持续时间后解析和分割输入行,但大部分来自于 SplitN 中更简单的分割逻辑。即使拆分所有字符串也不会比在持续时间后停止花费更长的时间。使用:

split_line := strings.SplitN(line, " ", -1)

1.554971313s 完成

SplitN 和 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
于 2012-10-06T03:25:17.577 回答