Синтаксический анализ XML (проблемы с производительностью) - PullRequest
0 голосов
/ 01 июня 2019

Прежде всего, позвольте мне заявить, что я застрял с текущим стеком, который я использую, и в настоящее время я не могу перейти к чему-то другому. Вместо того, чтобы дать мне знать, что я должен использовать другие вещи, я не могу ... поэтому давайте пропустим это.

Мои пользователи в основном следующие:

  • Excel 16.0, 64 бит.
  • Windows 7, 10
  • Оперативная память: Мин. (4 ГБ) - Макс. (8 ГБ)
  • Процессор: от i5 до i7 (у меня работает i7-3770 @ 3,4 ГГц)

Основы:

  • Приложение Excel, которое я разработал для внутреннего использования в компании, использует ADO для общения с SQLServer.
  • В настоящее время вся система запросов построена так, чтобы работать только с одним набором записей, возвращаемым за раз (если единственное решение состоит в том, чтобы вернуть несколько наборов записей и работать с ними, потребуется перезапись большого размера в ядро система)

Неосновы:

  • В настоящее время система может импортировать файлы 500 МБ в SQLServer за одну или две минуты, разбивая запросы на фрагменты по 100 тыс. Строк, и это работает довольно хорошо.
  • При получении данных из SQLServer у меня есть два разных стиля:
    • Стандартный 2-D набор записей (чрезвычайно производительный)
    • Набор записей, содержащий одно строковое значение XML (без разбора) со следующим форматом std:
<root>
    <*page*>
        <row>
            <*column*></*column*>
        </row>
    </*page*>
</root>

Пример вывода:

<root>
    <tab_awesome>
        <row>
            <col1>Value 1</col1>
            <col2>Value 2</col2>
        </row>
    </tab_awesome>
</root>

Затем я беру этот XML и в настоящее время загружаю значение String из набора записей, используя метод MSXML «Load». После загрузки и некоторой проверки я преобразую XML в эту стандартную структуру словаря, которая экспортирует на вкладки [N] и еще много чего.

{
    "page_count": 1,
    "page_names": ["tab_awesome"],
    "pages": {
        "tab_awesome": {
            "page_name": "tab_awesome",
            "row_count": 1,
            "column_count": 2,
            "column_names": ["col1", "col2"]
            "data": [["Value 1", "Value 2"]]
        }
    }
}

Проблема:

  • У нас есть несколько Sprocs, которые возвращают свои результаты, используя String XML ... и при анализе содержат несколько вкладок по + 100 тыс. Строк на вкладку. Некоторые Sprocs вернут +1 000 000 записей с +20 полями (все вкладки включены в счет).
  • Моя текущая проблема - Sproc for Amortizing. Он возвращает строку размером примерно 208 МБ.
    • Получение данных по сети занимает несколько секунд
    • Разбор строки в XML с использованием MSXML занимает 3m35s , и босс хочет, чтобы она работала быстрее.

Мои мысли:

  • Можно ли использовать Sax?
  • Можно ли перенести XML-документ из SQLServer через ADO, поэтому мне не нужно загружать его из строковой формы?
  • xmllite.dll? (Я вижу, что Rubberduck использует это. Мэтт, как производительность и случайно, есть привязки для VBA?)
  • В крайнем случае, я перепишу базовый механизм запросов моего приложения, чтобы обрабатывать несколько наборов записей ADO для работы с несколькими вкладками данных. Если я сделаю это, как мне получить один Sproc для возврата нескольких наборов записей?

Я застрял, используя вызовы API для Windows и VBA.

хмм ... единственное, что возможно, - это возвращать каждую вкладку как другую строку в наборе записей (все еще строковый XML). Для каждой строки создайте новый процесс Excel, проанализируйте / загрузите информацию этой вкладки, отправьте XML обратно в основной процесс, закройте эти вспомогательные экземпляры Excel ...

1 Ответ

0 голосов
/ 18 июня 2019

Ну ... К счастью, я смог решить это.На моей машине мне удалось сократить время обработки до 18 секунд.


    Option Explicit

    Implements IVBSAXContentHandler

    ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    ' Defined Events
    ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Public Event NoRecordsReturned()
    Public Event returnedRecordset(ByRef records As Dictionary)
    ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    ' Enumerations & Types
    ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Private Enum enumLevel
        eRoot = 0
        ePage = 1
        eRow = 2
        eColumn = 3
    End Enum

    ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    ' Class Variables
    ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Private saxReader As New SAXXMLReader60
    Private Container As Dictionary

    Private DEPTH As Long
    Private page_name As String
    Private page_idx As Long
    Private row_idx As Long
    Private column_idx As Long
    Private column_value As String

    Private page_data() As String
    Private page_data_len As Long
    Private lst_first_row_headers As Object
    Private lst_first_row_values As Object
    Private lst_page_names As Object

    ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    ' Class: Initialization & Termination
    ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Private Sub Class_Initialize()
        Call Build_Main_Container

        Set lst_first_row_values = CreateObject("System.Collections.ArrayList")
        Set lst_first_row_headers = CreateObject("System.Collections.ArrayList")
        Set lst_page_names = CreateObject("System.Collections.ArrayList")
    End Sub
    ' ------------------
    Private Sub Class_Terminate()
        Set lst_first_row_values = Nothing
        Set lst_first_row_headers = Nothing
        Set lst_page_names = Nothing
        Set Container = Nothing
    End Sub

    ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    ' Class Properties
    ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Public Property Let parse(ByRef strRecordset As String)
        Set saxReader.contentHandler = Me
        saxReader.parse strRecordset
    End Property

    Private Property Set IVBSAXContentHandler_documentLocator(ByVal RHS As MSXML2.IVBSAXLocator)
    End Property

    ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    ' Private Subroutines
    ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Private Sub IVBSAXContentHandler_endPrefixMapping(strPrefix As String)
    End Sub
    Private Sub IVBSAXContentHandler_ignorableWhitespace(strChars As String)
    End Sub
    Private Sub IVBSAXContentHandler_processingInstruction(strTarget As String, strData As String)
    End Sub
    Private Sub IVBSAXContentHandler_skippedEntity(strName As String)
    End Sub
    Private Sub IVBSAXContentHandler_startPrefixMapping(strPrefix As String, strURI As String)
    End Sub

    ' // Start of useful Subs & Funcs
    Private Sub IVBSAXContentHandler_startDocument()
        DEPTH = 0
        page_idx = 0
        row_idx = 0
        column_idx = 0
    End Sub

    Private Sub IVBSAXContentHandler_endDocument()
        If Container("page_count") = 0 Then
            RaiseEvent NoRecordsReturned
        Else
            ' // Finished and sending data back to consumer
            RaiseEvent returnedRecordset(Container)
        End If
    End Sub

    ' //////////////////////////////////////////////
    Private Sub IVBSAXContentHandler_startElement(strNamespaceURI As String, strLocalName As String, strQName As String, ByVal oAttributes As MSXML2.IVBSAXAttributes)
        Select Case DEPTH
            'Case eRoot
            ' // Not in use
            Case ePage
                page_name = strLocalName
            'Case eRow
            ' // Not in use
            Case eColumn
                If Not oAttributes.Length = 0 Then
                    If IsNil(oAttributes) Then
                        Call IVBSAXContentHandler_characters(vbNullString)
                    End If
                End If
        End Select
        ' // This goes @ the bottom
        DEPTH = DEPTH + 1
    End Sub

    ' //////////////////////////////////////////////
    Private Sub IVBSAXContentHandler_endElement(strNamespaceURI As String, strLocalName As String, strQName As String)
        ' // This goes @ the top \\ '
        DEPTH = DEPTH - 1
        ' // ------------------- \\ '
        Select Case DEPTH
            'Case eRoot
            ' // Not in use
            Case ePage
                Call pageEnd(page_name, row_idx - 1)
                page_idx = page_idx + 1
                ' // Reset: New Page
                row_idx = 0
                page_name = Empty
            Case eRow
                Call rowEnd(row_idx, column_idx - 1)
                row_idx = row_idx + 1
                ' // Reset: New Row
                column_idx = 0
                column_value = Empty
            Case eColumn
                Call columnData(row_idx, column_idx, strLocalName, column_value)
                column_idx = column_idx + 1
        End Select
    End Sub
    ' //////////////////////////////////////////////
    Private Sub IVBSAXContentHandler_characters(strChars As String)
        column_value = strChars
    End Sub

    Private Sub pageEnd(ByRef name As String, ByRef last_row_idx As Long)
        lst_page_names.Add name
        Call ProcessPage(name, last_row_idx)
    End Sub

    Private Sub rowEnd(ByRef row_idx As Long, ByRef last_column_idx As Long)
        If row_idx = 0 Then
            Call CreatePageArray(last_column_idx)
        End If
    End Sub

    Private Sub columnData(ByRef row_idx As Long, ByRef column_idx As Long, ByRef tag As String, ByRef value As String)
        If row_idx  0 Then
            Call ArrayAppend(row_idx, column_idx, value)
            Exit Sub
        Else
        ' // First row: need to add column values to ArrayList
            lst_first_row_headers.Add tag
            lst_first_row_values.Add value
            Exit Sub
        End If
    End Sub

    Private Sub ArrayAppend(ByRef row_idx As Long, ByRef col_idx As Long, ByRef val As String)
        If row_idx 
...