Standart xml解析器在Golang中的性能非常低

时间:2017-09-09 21:13:57

标签: c# xml go

我有一个100Gb大小的xml文件,并使用SAX方法解析它,并使用此代码

file, err := os.Open(filename)
handle(err)
defer file.Close()
buffer := bufio.NewReaderSize(file, 1024*1024*256) // 33554432
decoder := xml.NewDecoder(buffer)
for {
        t, _ := decoder.Token()
        if t == nil {
            break
        }
        switch se := t.(type) {
        case xml.StartElement:
            if se.Name.Local == "House" {
                house := House{}
                err := decoder.DecodeElement(&house, &se)
                handle(err)
            }
        }
    }

但golang的工作速度非常慢,似乎是执行时间和磁盘使用情况。我的硬盘能够以100-120 mb / s的速度读取数据,但golang仅使用10-13 mb / s。 对于实验,我在c#中重写了这段代码:

using (XmlReader reader = XmlReader.Create(filename)
            {
                while (reader.Read())
                {
                    switch (reader.NodeType)
                    {
                        case XmlNodeType.Element:
                            if (reader.Name == "House")
                            {
                                //Code
                            }
                            break;
                    }
                }
            }

我加载了完整的硬盘,c#以100-110mb / s的速度读取数据。并且执行时间大约低了10倍。

如何使用golang提高xml解析性能?

2 个答案:

答案 0 :(得分:1)

使用encoding/xml库,这5件事可以帮助提高速度:
(针对具有75,000个条目,20MB的XMB进行了测试,%s应用于前一个项目符号)

  1. 使用定义明确的结构
  2. 在所有结构上实施xml.Unmarshaller
    • 很多代码
    • 节省20%的时间和15%的分配
  3. d.DecodeElement(&foo, &token)替换为foo.UnmarshallXML(d, &token)
    • 几乎100%安全
    • 节省10%的时间和分配
  4. 使用d.RawToken()代替d.Token()
    • 需要手动处理嵌套对象和名称空间
    • 节省10%的时间和20%的分配
  5. 如果使用d.Skip(),请使用d.RawToken()

在特定用例上,我将时间和分配减少了40%,但代价是更多的代码,更易处理的代码以及可能对角落情况的处理更糟,但是我的输入是相当一致的,但是这还不够

benchstat first.bench.txt parseraw.bench.txt 
name          old time/op    new time/op    delta
Unmarshal-16     1.06s ± 6%     0.66s ± 4%  -37.55%  (p=0.008 n=5+5)

name          old alloc/op   new alloc/op   delta
Unmarshal-16     461MB ± 0%     280MB ± 0%  -39.20%  (p=0.029 n=4+4)

name          old allocs/op  new allocs/op  delta
Unmarshal-16     8.42M ± 0%     5.03M ± 0%  -40.26%  (p=0.016 n=4+5)

在我的实验中,lack of memoizing issue是XML分析器上大量时间/分配的原因,它显着减慢了速度,主要原因是Go按值复制。

答案 1 :(得分:0)

回答您的问题“如何使用golang提高xml解析性能?”

使用公用xml.NewDecoder / decoder.Token,我在本地看到50 MB / s。通过使用https://github.com/tamerh/xml-stream-parser,我能够使解析速度提高一倍。

为了测试,我使用了https://archive.org/details/stackexchange存档种子中的Posts.xml(68 GB)。

package main

import (
    "bufio"
    "fmt"
    "github.com/tamerh/xml-stream-parser"
    "os"
    "time"
)

func main() {
    // Using `Posts.xml` (68 GB) from https://archive.org/details/stackexchange (in the torrent)
    f, err := os.Open("Posts.xml")
    if err != nil {
        panic(err)
    }
    defer f.Close()

    br := bufio.NewReaderSize(f, 1024*1024)
    parser := xmlparser.NewXmlParser(br, "row")

    started := time.Now()
    var previous int64 = 0

    for x := range *parser.Stream() {
        elapsed := int64(time.Since(started).Seconds())
        if elapsed > previous {
            kBytesPerSecond := int64(parser.TotalReadSize) / elapsed / 1024
            fmt.Printf("\r%ds elapsed, read %d kB/s (last post.Id %s)", elapsed, kBytesPerSecond, x.Attrs["Id"])
            previous = elapsed
        }
    }
}

这将输出以下内容:

...s elapsed, read ... kB/s (last post.Id ...)

仅需注意的是,这并不能方便地将您编入struct。

正如https://github.com/golang/go/issues/21823中所讨论的那样,速度似乎是Golang中XML实现的普遍问题,并且需要重写/重新思考标准库的这一部分。