2013-01-05 95 views
-3

我正在尋找非常快速的Delphi解析器,用於非常簡單的數據。德爾福 - 快速(er)XML解析器

考慮以下類型的數據:

<node> 
    <datatype1>randomdata</datatype1> 
    <datatype2>randomdata</datatype2> 
    <datatype3>randomdata</datatype3> 
    <datatype4>randomdata</datatype4> 
    <datatype5>randomdata</datatype5> 
    <datatype6>randomdata</datatype6> 
    <datatype7>randomdata</datatype7> 
    <datatype8>randomdata</datatype8> 
    <datatype9>randomdata</datatype9> 
    <datatype10>randomdata</datatype10> 
    <datatype11>randomdata</datatype11> 
    <datatype12>randomdata</datatype12> 
    <datatype13>randomdata</datatype13> 
    <datatype14>randomdata</datatype14> 
    <datatype15>randomdata</datatype15> 
    <datatype16>randomdata</datatype16> 
    <datatype17>randomdata</datatype17> 
    <datatype18>randomdata</datatype18> 
    <datatype19>randomdata</datatype19> 
    <datatype20>randomdata</datatype20> 
</node> 

拷貝10000次(數據類型和數據是在真實的場景中明顯不同)。還要考慮包含Unicode的數據。

這將被解析並加載到一個記錄數組一樣

Type MyData = record 
    d1,d2,d3,d4,d5, 
    d6,d7,d8,d9,d10, 
    d11,d12,d13,d14,d15, 
    d16,d17,d18,d19,d20: string; 
end; 

我寫了這個定製的解析器,這在我的電腦需要約。從裝載文件到填充10,000條記錄,整個過程爲115毫秒。

所以我正在尋找能夠更快完成這件事的東西。

相關問題:

Pos() within utf8 string boundaries

Delphi - Pos() with boundaries

+2

你如何測試這個,你想要多快?我的意思是,即使你只是從磁盤讀取8Mb,對於8Mb沒有任何幫助,115ms是相當不錯的。如果你能夠快速解析硬盤可以讀取的內容,它會對你有什麼好處? –

+0

時間如下:從磁盤加載爲Unicode字符串:26ms,解析節點邊界:7ms,加載節點並填充數據記錄:76ms。這是在一臺i5 4Ghz計算機上,從ramdisk加載XML文件。然而,磁盤加載部分在這裏沒有任何含義,我需要改進分析過程。 – hikari

+0

「加載節點」(c)你需要_load_節點嗎?似乎你應該使用SAX模型解析器,而不需要像DOM模型解析器那樣的「真實加載」節點。 無論如何,至於我,155ms是一個非常好的速度。您可以嘗試使用OmniXML或NativeXML解析器,但我不知道它們是否實現了SAX-model – teran

回答

9

首先讓我告訴你,你在這裏優化錯誤的東西:除非你是爲了娛樂的目的而這樣做,那麼你的方法是錯的。 XML並不是一個困難的格式,但它確實有它的怪癖,它需要它自由。這是一種爲外國應用程序之間的數據交換而設計的格式,因此需要將重點放在兼容性上,而不是速度上!一個非標準的超快速解析器在遇到輕微改變的XML文件時會給出錯誤的結果有什麼好處?

如果您可以找到一個XML解析庫,保證與任何可以解析您的數據的硬件解析數據的速度相同,那麼只需實現一個生產者 - 消費者多線程應用程序,其中一個線程不斷從磁盤讀取數據,而另外兩個只是解析。最後,您只能在保持兼容性的同時受到HDD速度的限制。如果你只是在尋找速度,你可能會犯錯,跳過XML功能,取決於你正在處理的示例XML文件的某些特殊性。您的應用程序可能因多種原因而中斷。

請記住,應用程序最昂貴的週期是維護,而不是生產。當計算機速度提高50%(使競爭對手無能爲力)時,如果將速度提高50%,難度維持在200%以上,那麼今後可能會失去一年左右的時間。此外,對於這樣的流程,例如HDD的速度,超出自然限制沒有意義。與RAM驅動器中的文件進行測試無關 - 當應用程序投入使用時,它將與HDD中的文件一起使用,並且應用程序的性能將受到HDD速度的限制。


無論如何,我偶爾會喜歡挑戰,我真的很喜歡解析器。接下來是一個非常簡單的解析器實現,它只查看輸入字符串中的每個字符一次,並且只複製需要的東西:複製節點的名稱以決定下一步該做什麼,並在節點的「有效負載」適當的,以便將其推入陣列。在我的「適度」i7 @ 3.4 Ghz解析通過複製您的示例數據10,000次構建的字符串需要63毫秒。它清楚地打敗你的時間,但一個警告的話,這個代碼是脆弱的:它取決於有一個XML文件是某種形式。沒辦法。

program Project28; 

{$APPTYPE CONSOLE} 

uses SysUtils, DateUtils, Windows; 

const SampleData = 
    '<node>'#13#10+ 
    ' <datatype1>randomdata</datatype1>'#13#10+ 
    ' <datatype2>randomdata</datatype2>'#13#10+ 
    ' <datatype3>randomdata</datatype3>'#13#10+ 
    ' <datatype4>randomdata</datatype4>'#13#10+ 
    ' <datatype5>randomdata</datatype5>'#13#10+ 
    ' <datatype6>randomdata</datatype6>'#13#10+ 
    ' <datatype7>randomdata</datatype7>'#13#10+ 
    ' <datatype8>randomdata</datatype8>'#13#10+ 
    ' <datatype9>randomdata</datatype9>'#13#10+ 
    ' <datatype10>randomdata</datatype10>'#13#10+ 
    ' <datatype11>randomdata</datatype11>'#13#10+ 
    ' <datatype12>randomdata</datatype12>'#13#10+ 
    ' <datatype13>randomdata</datatype13>'#13#10+ 
    ' <datatype14>randomdata</datatype14>'#13#10+ 
    ' <datatype15>randomdata</datatype15>'#13#10+ 
    ' <datatype16>randomdata</datatype16>'#13#10+ 
    ' <datatype17>randomdata</datatype17>'#13#10+ 
    ' <datatype18>randomdata</datatype18>'#13#10+ 
    ' <datatype19>randomdata</datatype19>'#13#10+ 
    ' <datatype20>randomdata</datatype20>'#13#10+ 
    '</node>'#13#10; 
const NodeIterations = 10000; 

type 
    TDummyRecord = record 
    D1, D2, D3, D4, D5, D6, D7, D8, D9, D10, D11, D12, D13, 
     D14, D15, D16, D17, D18, D19, D20: string; 
    end; 
    TDummyRecordArray = array[1..NodeIterations] of TDummyRecord; 

procedure ParseDummyXMLToRecordArray(const InputText:string; var A: TDummyRecordArray); 
var PInputText: PChar; 
    cPos, TextLen: Integer; 
    C: Char; 
    State: Integer; 

    tag_starts_at: Integer; 
    last_payload_starts_at: Integer; 
    FlagEndTag: Boolean; 

    NodeName, Payload: string; 

    cNode: Integer; 

const st_not_in_node = 1; 
     st_in_node = 2; 
begin 
    cPos := 1; 
    TextLen := Length(InputText); 
    PInputText := @InputText[1]; 
    State := st_not_in_node; 
    last_payload_starts_at := 1; 
    cNode := 0; 

    // This is the lexer/parser loop. It's a finite-state machine with only 
    // two states: st_not_in_node and st_in_node 
    while cPos < TextLen do 
    begin 
    C := PInputText[cPos-1]; 
    case State of 

     // What happens when we're NOT currently inside a node? 
     // Not much. We only jump to st_in_node if we see a "<" 
     st_not_in_node: 
     case C of 
      '<': 
      begin 
       // A node starts here. Switch state and set up some simple 
       // flags. 
       state := st_in_node; 
       tag_starts_at := cPos + 1; 
       FlagEndTag := False; 
      end; 
     end; 

     // What happens while inside a node? Again, not much. We only care about 
     // the "/" - as it signals an closing tag, and we only care about the 
     // ">" because that means the end of the ndoe. 
     st_in_node: 
     case C of 
      '/': FlagEndTag := True; 
      '>': 
      begin 
       // This is where the magic haepens. We're in one of possibly two states: 
       // We're ither seeing the first <name> of a pair, or the second </name> 
       // 
       if FlagEndTag then 
       begin 
        // This is the closing pair of a tag pair, ie, it's the </NodeName> What we'll do 
        // depends on what node is closing, so we retreive the NodeName: 
        NodeName := System.Copy(InputText, tag_starts_at+1, cPos - tag_starts_at-1); 
        if NodeName <> 'node' then // SAMPLE-DATA-SPECIFIC: I know I don't care about "node" tags. 
        begin 
        // SAMPLE-DATA-SPECIFIC: I know there are only two kinds of nodes: 
        // "node" and "datatypeN". I retreive the PAYLOAD for the node because 
        // I know it's not "ndoe" and I know I'll need it. 
        Payload := System.Copy(InputText,last_payload_starts_at, tag_starts_at - last_payload_starts_at -1); 
        // Make sure we're dealing with a valid node 
        if (cNode > 0) and (cNode <= High(A)) then 
         begin 
         // Based on NodeName, copy the Payload into the appropriate field. 
         if NodeName = 'datatype1' then A[cNode].D1 := Payload 
         else if NodeName = 'datatype2' then A[cNode].D2 := Payload 
         else if NodeName = 'datatype3' then A[cNode].D3 := Payload 
         else if NodeName = 'datatype4' then A[cNode].D4 := Payload 
         else if NodeName = 'datatype5' then A[cNode].D5 := Payload 
         else if NodeName = 'datatype6' then A[cNode].D6 := Payload 
         else if NodeName = 'datatype7' then A[cNode].D7 := Payload 
         else if NodeName = 'datatype8' then A[cNode].D8 := Payload 
         else if NodeName = 'datatype9' then A[cNode].D9 := Payload 
         else if NodeName = 'datatype10' then A[cNode].D10 := Payload 
         else if NodeName = 'datatype11' then A[cNode].D11 := Payload 
         else if NodeName = 'datatype12' then A[cNode].D12 := Payload 
         else if NodeName = 'datatype13' then A[cNode].D13 := Payload 
         else if NodeName = 'datatype14' then A[cNode].D14 := Payload 
         else if NodeName = 'datatype15' then A[cNode].D15 := Payload 
         else if NodeName = 'datatype16' then A[cNode].D16 := Payload 
         else if NodeName = 'datatype17' then A[cNode].D17 := Payload 
         else if NodeName = 'datatype18' then A[cNode].D18 := Payload 
         else if NodeName = 'datatype19' then A[cNode].D19 := Payload 
         else if NodeName = 'datatype20' then A[cNode].D20 := Payload 
         else 
          raise Exception.Create('Unknown node: ' + NodeName); 
         end 
        else 
         raise Exception.Create('cNode out of bounds.'); 
        end; 
        // Repeat :-) 
        state := st_not_in_node; 
       end 
       else 
       begin 
        // Node start. Retreive node name. I only care about the start of the "NODE" - if I see that 
        // I'll increment the current node counter so I'll go on filling the next position in the array 
        // with whatever I need. 
        NodeName := System.Copy(InputText, tag_starts_at, cPos - tag_starts_at); 
        last_payload_starts_at := cPos+1; 
        if NodeName = 'node' then Inc(cNode); 
        state := st_not_in_node; 
       end; 
      end; 
     end; 
    end; 
    Inc(cPos); 
    end; 
end; 

var DataString: string; 
    SB: TStringBuilder; 
    i: Integer; 
    DummyArray: TDummyRecordArray; 
    T1, T2, F: Int64; 

begin 
    try 
    try 
     // Prepare the sample string; 10.000 iterations of the sample data. 
     SB := TStringBuilder.Create; 
     try 
     for i:=1 to NodeIterations do 
      SB.Append(SampleData); 
     DataString := SB.ToString; 
     finally SB.Free; 
     end; 

     // Invoke the simple parser using the string constant. 
     QueryPerformanceCounter(T1); 

     ParseDummyXMLToRecordArray(DataString, DummyArray); 

     QueryPerformanceCounter(T2); 
     QueryPerformanceFrequency(F); 
     WriteLn(((T2-T1) * 1000) div F); 

     // Test parse validity. 
     for i:=1 to NodeIterations do 
     begin 
     if DummyArray[i].D1 <> 'randomdata' then raise Exception.Create('Bug. D1 doesn''t have the proper value, i=' + IntToStr(i)); 
     if DummyArray[i].D2 <> 'randomdata' then raise Exception.Create('Bug. D2 doesn''t have the proper value, i=' + IntToStr(i)); 
     if DummyArray[i].D3 <> 'randomdata' then raise Exception.Create('Bug. D3 doesn''t have the proper value, i=' + IntToStr(i)); 
     if DummyArray[i].D4 <> 'randomdata' then raise Exception.Create('Bug. D4 doesn''t have the proper value, i=' + IntToStr(i)); 
     if DummyArray[i].D5 <> 'randomdata' then raise Exception.Create('Bug. D5 doesn''t have the proper value, i=' + IntToStr(i)); 
     if DummyArray[i].D6 <> 'randomdata' then raise Exception.Create('Bug. D6 doesn''t have the proper value, i=' + IntToStr(i)); 
     if DummyArray[i].D7 <> 'randomdata' then raise Exception.Create('Bug. D7 doesn''t have the proper value, i=' + IntToStr(i)); 
     if DummyArray[i].D8 <> 'randomdata' then raise Exception.Create('Bug. D8 doesn''t have the proper value, i=' + IntToStr(i)); 
     if DummyArray[i].D9 <> 'randomdata' then raise Exception.Create('Bug. D9 doesn''t have the proper value, i=' + IntToStr(i)); 
     if DummyArray[i].D10 <> 'randomdata' then raise Exception.Create('Bug. D10 doesn''t have the proper value, i=' + IntToStr(i)); 
     if DummyArray[i].D11 <> 'randomdata' then raise Exception.Create('Bug. D11 doesn''t have the proper value, i=' + IntToStr(i)); 
     if DummyArray[i].D12 <> 'randomdata' then raise Exception.Create('Bug. D12 doesn''t have the proper value, i=' + IntToStr(i)); 
     if DummyArray[i].D13 <> 'randomdata' then raise Exception.Create('Bug. D13 doesn''t have the proper value, i=' + IntToStr(i)); 
     if DummyArray[i].D14 <> 'randomdata' then raise Exception.Create('Bug. D14 doesn''t have the proper value, i=' + IntToStr(i)); 
     if DummyArray[i].D15 <> 'randomdata' then raise Exception.Create('Bug. D15 doesn''t have the proper value, i=' + IntToStr(i)); 
     if DummyArray[i].D16 <> 'randomdata' then raise Exception.Create('Bug. D16 doesn''t have the proper value, i=' + IntToStr(i)); 
     if DummyArray[i].D17 <> 'randomdata' then raise Exception.Create('Bug. D17 doesn''t have the proper value, i=' + IntToStr(i)); 
     if DummyArray[i].D18 <> 'randomdata' then raise Exception.Create('Bug. D18 doesn''t have the proper value, i=' + IntToStr(i)); 
     if DummyArray[i].D19 <> 'randomdata' then raise Exception.Create('Bug. D19 doesn''t have the proper value, i=' + IntToStr(i)); 
     if DummyArray[i].D20 <> 'randomdata' then raise Exception.Create('Bug. D20 doesn''t have the proper value, i=' + IntToStr(i)); 
     end; 

    except on E: Exception do Writeln(E.ClassName, ': ', E.Message); 
    end; 
    finally 
    WriteLn('ENTER to Exit'); 
    ReadLn; 
    end; 
end. 
+0

我的目標是實現兩者:)我的計算機中的代碼在~40ms內執行解析塊(大約在我的80中),你更像是一個串行解析器。它不適合適用於不同的/通用的場景,因爲在這裏我們確切知道子節點名稱是什麼,但它可以適應觸發解析器外的事件,爲應用程序提供節點號,子節點名稱和它的數據,然後應用程序將其分配給它擁有的任何記錄。我的解析器中的方法更通用一些,對文件進行採樣以收集節點邊界/指針,然後在選定節點上執行 – hikari

+0

子節點數據請求,示例解析代碼:http://pastebin.com/kgkCU6Yx它還支持CDATA考慮到有效載荷中的標記。 – hikari

+0

這看起來有點過於具體的數據,但是這種格式對於很多使用RESTful API,RSS提要等的Web服務來說很常見。 – hikari

1

如果你的XML是容易的,格式是固定的,而文件是大的,你真的需要快速處理,我會建議通過自己實現解析,使用簡單的while(長度爲(我的<)(unputStr))循環。在那裏你可以搜索'<'符號,提取節點名稱等,等等。

+0

我已經在使用我自己的解析器,但我想要更快的東西。但是,節點元素並不固定,根節點和它下面的子節點在不同的XML文件中具有不同的值。 – hikari

+1

您可以在解析器中提取名稱。無論如何,每個XML解析器都會比自己的解析器慢,這個解析器預先知道文件結構。 –

+0

+1。最快的方法是將其視爲字符串,而不是嘗試加載到XML文檔中。如果速度不夠快,請使用指向緩衝區的指針。如果速度不夠快,轉換爲彙編程序。 –