如何使用goroutines解码XML

时间:2019-02-09 12:27:32

标签: xml go decode

我正在研究概念证明,以研究解析具有一定数量实体的XML文档所需的时间。

首先,我的结构确实包含XML文档中的条目:

type Node struct {
    ID             int    `xml:"id,attr"`
    Position       int    `xml:"position,attr"`
    Depth          int    `xml:"depth,attr"`
    Parent         string `xml:"parent,attr"`
    Name           string `xml:"Name"`
    Description    string `xml:"Description"`
    OwnInformation struct {
        Title       string `xml:"Title"`
        Description string `xml:"Description"`
    } `xml:"OwnInformation"`
    Assets []struct {
        ID           string `xml:"id,attr"`
        Position     int    `xml:"position,attr"`
        Type         string `xml:"type,attr"`
        Category     int    `xml:"category,attr"`
        OriginalFile string `xml:"OriginalFile"`
        Description  string `xml:"Description"`
        URI          string `xml:"Uri"`
    } `xml:"Assets>Asset"`
    Synonyms []string `xml:"Synonyms>Synonym"`
}

接下来,我有一个工厂可以生成任何给定数量的元素:

func CreateNodeXMLDocumentBytes(
    nodeElementCount int) []byte {

    xmlContents := new(bytes.Buffer)

    xmlContents.WriteString("<ROOT>\n")

    for iterationCounter := 0; iterationCounter < nodeElementCount; iterationCounter++ {
        appendNodeXMLElement(iterationCounter, xmlContents)
    }

    xmlContents.WriteString("</ROOT>")

    return xmlContents.Bytes()
}

// PRIVATE: appendNodeXMLElement appends a '<Node />' elements to an existing bytes.Buffer instance.
func appendNodeXMLElement(
    counter int,
    xmlDocument *bytes.Buffer) {

    xmlDocument.WriteString("<Node id=\"" + strconv.Itoa(counter) + "\" position=\"0\" depth=\"0\" parent=\"0\">\n")
    xmlDocument.WriteString("    <Name>Name</Name>\n")
    xmlDocument.WriteString("    <Description>Description</Description>\n")
    xmlDocument.WriteString("    <OwnInformation>\n")
    xmlDocument.WriteString("        <Title>Title</Title>\n")
    xmlDocument.WriteString("        <Description>Description</Description>\n")
    xmlDocument.WriteString("    </OwnInformation>\n")
    xmlDocument.WriteString("    <Assets>\n")
    xmlDocument.WriteString("        <Asset id=\"0\" position=\"0\" type=\"0\" category=\"0\">\n")
    xmlDocument.WriteString("            <OriginalFile>OriginalFile</OriginalFile>\n")
    xmlDocument.WriteString("            <Description>Description</Description>\n")
    xmlDocument.WriteString("            <Uri>Uri</Uri>\n")
    xmlDocument.WriteString("        </Asset>\n")
    xmlDocument.WriteString("        <Asset id=\"1\" position=\"1\" type=\"1\" category=\"1\">\n")
    xmlDocument.WriteString("            <OriginalFile>OriginalFile</OriginalFile>\n")
    xmlDocument.WriteString("            <Description>Description</Description>\n")
    xmlDocument.WriteString("            <Uri>Uri</Uri>\n")
    xmlDocument.WriteString("        </Asset>\n")
    xmlDocument.WriteString("        <Asset id=\"2\" position=\"2\" type=\"2\" category=\"2\">\n")
    xmlDocument.WriteString("            <OriginalFile>OriginalFile</OriginalFile>\n")
    xmlDocument.WriteString("            <Description>Description</Description>\n")
    xmlDocument.WriteString("            <Uri>Uri</Uri>\n")
    xmlDocument.WriteString("        </Asset>\n")
    xmlDocument.WriteString("        <Asset id=\"3\" position=\"3\" type=\"3\" category=\"3\">\n")
    xmlDocument.WriteString("            <OriginalFile>OriginalFile</OriginalFile>\n")
    xmlDocument.WriteString("            <Description>Description</Description>\n")
    xmlDocument.WriteString("            <Uri>Uri</Uri>\n")
    xmlDocument.WriteString("        </Asset>\n")
    xmlDocument.WriteString("        <Asset id=\"4\" position=\"4\" type=\"4\" category=\"4\">\n")
    xmlDocument.WriteString("            <OriginalFile>OriginalFile</OriginalFile>\n")
    xmlDocument.WriteString("            <Description>Description</Description>\n")
    xmlDocument.WriteString("            <Uri>Uri</Uri>\n")
    xmlDocument.WriteString("        </Asset>\n")
    xmlDocument.WriteString("    </Assets>\n")
    xmlDocument.WriteString("    <Synonyms>\n")
    xmlDocument.WriteString("        <Synonym>Synonym 0</Synonym>\n")
    xmlDocument.WriteString("        <Synonym>Synonym 1</Synonym>\n")
    xmlDocument.WriteString("        <Synonym>Synonym 2</Synonym>\n")
    xmlDocument.WriteString("        <Synonym>Synonym 3</Synonym>\n")
    xmlDocument.WriteString("        <Synonym>Synonym 4</Synonym>\n")
    xmlDocument.WriteString("    </Synonyms>\n")
    xmlDocument.WriteString("</Node>\n")
}

接下来,我有一个创建示例文档并解码每个''元素的应用程序:

func main() {
    nodeXMLDocumentBytes := factories.CreateNodeXMLDocumentBytes(100)

    xmlDocReader := bytes.NewReader(nodeXMLDocumentBytes)
    xmlDocDecoder := xml.NewDecoder(xmlDocReader)

    xmlDocNodeElementCounter := 0

    start := time.Now()

    for {
        token, _ := xmlDocDecoder.Token()
        if token == nil {
            break
        }

        switch element := token.(type) {
        case xml.StartElement:
            if element.Name.Local == "Node" {
                xmlDocNodeElementCounter++

                xmlDocDecoder.DecodeElement(new(entities.Node), &element)
            }
        }
    }

    fmt.Println("Total '<Node />' elements in the XML document: ", xmlDocNodeElementCounter)
    fmt.Printf("Total elapsed time: %v\n", time.Since(start))
}

这在我的计算机上大约需要11毫秒。

接下来,我使用goroutines解码XML元素:

func main() {
    nodeXMLDocumentBytes := factories.CreateNodeXMLDocumentBytes(100)

    xmlDocReader := bytes.NewReader(nodeXMLDocumentBytes)
    xmlDocDecoder := xml.NewDecoder(xmlDocReader)

    xmlDocNodeElementCounter := 0

    start := time.Now()

    for {
        token, _ := xmlDocDecoder.Token()
        if token == nil {
            break
        }

        switch element := token.(type) {
        case xml.StartElement:
            if element.Name.Local == "Node" {
                xmlDocNodeElementCounter++

                go xmlDocDecoder.DecodeElement(new(entities.Node), &element)
            }
        }
    }

    time.Sleep(time.Second * 5)

    fmt.Println("Total '<Node />' elements in the XML document: ", xmlDocNodeElementCounter)
    fmt.Printf("Total elapsed time: %v\n", time.Since(start))
}

我使用一个简单的“ Sleep”命令来确保goroutine完成。 我知道应该通过通道和工作队列来实现。

根据控制台上的输出,只有3个元素被解码。 那么其他元素发生了什么?也许与我正在使用流有关的事实?

有什么办法可以使其并发,从而减少解码所有元素所需的时间?

1 个答案:

答案 0 :(得分:0)

您只有一个xml.Decoder对象。每当有人调用xmlDocDecoder.Token()时,它将从(单个)输入流中读取下一个标记。在您的示例中,主循环和您启动的每个goroutine都试图同时读取同一输入流,因此令牌流将随机地分配到所有goroutine中。如果再次执行此操作,可能会得到不同的结果。令我感到惊讶的是,这种方法不会以某种奇怪的方式惊慌。

关于XML的几件事使得这很难并行化。您实际上需要在此处实现的顺序是:

  1. 通知<Node>开始元素事件。
  2. 向前阅读直到匹配的</Node>元素结束事件在同一深度进行,并记住您在此期间通过的每个事件。
  3. 启动一个goroutine来解组所有您记忆中的事件到一个结构中。

在实践中,“记住每个事件”步骤很可能与取消编组一样昂贵,并且整个过程比首先读取文件的磁盘或网络I / O要快得多。这似乎并不能很好地并行化。

  

这在我的计算机上大约需要11毫秒。

对于“快速”还是“慢速”,您并没有真正做得很好。请查看testing package中的基准测试支持,以寻求更好的方法以及built-in profiling tools。这样可以告诉您实际的时间在哪里,并建议您可以改善的地方。