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

Gre*_*rey 5 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)
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

但是 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;
                    }
                }
            }
Run Code Online (Sandbox Code Playgroud)

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

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

小智 3

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

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

为了进行测试,我使用了https://archive.org/details/stackexchangePosts.xml存档 torrent中的 (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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这将输出如下内容:

...s elapsed, read ... kB/s (last post.Id ...)
Run Code Online (Sandbox Code Playgroud)

唯一需要注意的是,这并不能让您方便地解组到结构中。

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