Как декодировать XML с помощью goroutines - PullRequest
0 голосов
/ 09 февраля 2019

Я работаю над проверкой концепции для исследования времени, необходимого для анализа 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))
}

Я используюпростая команда «Сон», чтобы убедиться, что горутины закончены.Я знаю, что это должно быть реализовано с каналами и рабочей очередью.

Согласно выводу на моей консоли декодируются только 3 элемента.Так что же случилось с другими элементами?Может быть, что-то связано с тем, что я использую потоки?

Можно ли как-то сделать так, чтобы это было параллельным, чтобы уменьшить необходимое время для декодирования всех элементов?

1 Ответ

0 голосов
/ 09 февраля 2019

У вас есть только один xml.Decoder объект.Каждый раз, когда что-то вызывает xmlDocDecoder.Token(), оно будет читать следующий токен из (единственного) входного потока.В вашем примере и основной цикл, и каждая запускаемая вами программа пытаются прочитать один и тот же входной поток в одно и то же время, поэтому поток токенов распределяется по всем видам операций случайным образом.Возможно, если вы запустите это снова, вы получите другие результаты;и я немного удивлен, что это работает без паники каким-то странным образом.

Несколько вещей в XML затрудняют распараллеливание.Последовательность, которую вам действительно нужно выполнить, такова:

  1. Обратите внимание на событие <Node> start-element.
  2. Читайте вперед до соответствующего события </Node> end-element нас той же глубиной, вспоминая каждое событие, которое вы прошли за это время.
  3. Запустите программу, чтобы распаковать все события, которые вы запомнили, в структуру.

На практике это, вероятно, "запомнить каждоеСобытие «step» так же дорого, как просто выполнение демаршала, и что вся эта последовательность будет намного быстрее, чем дисковый или сетевой ввод-вывод, чтобы сначала прочитать файл.Это не похоже на то, что будет хорошо распараллеливаться.

На моей машине это занимает около 11 мс.

На самом деле вы не выполняете достаточно работы, чтобы получить хороший результат.почувствуйте, если это "быстро" или "медленно".Посмотрите на поддержку бенчмаркинга в тестовом пакете для лучшего подхода, плюс встроенные инструменты профилирования .Это скажет вам, куда на самом деле уходит время, и подскажет, что вы можете улучшить.

...