我有一个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解析性能?
答案 0 :(得分:1)
使用encoding/xml
库,这5件事可以帮助提高速度:
(针对具有75,000个条目,20MB的XMB进行了测试,%s应用于前一个项目符号)
xml.Unmarshaller
d.DecodeElement(&foo, &token)
替换为foo.UnmarshallXML(d, &token)
d.RawToken()
代替d.Token()
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实现的普遍问题,并且需要重写/重新思考标准库的这一部分。