-3

非常に単純なデータ用の、Delphi用の非常に高速な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;

私はこのためのカスタムパーサーを作成しました。これは私のコンピューターでは約1時間かかります。ファイルのロードから10,000レコードの入力まで、プロセス全体で115ミリ秒。

だから私はこれをより速く達成できる何かを探しています。

関連する質問:

utf8文字列境界内のPos()

Delphi-境界のあるPos()

4

2 に答える 2

9

最初に、ここで間違ったことを最適化していることを伝えさせてください。娯楽目的でこれを行っていない限り、あなたのアプローチは間違っています。XML は難しい形式ではありませんが、癖があり、自由が必要です。これは、外国のアプリケーション間のデータ交換用に設計されたフォーマットであるため、速度ではなく互換性を重視する必要があります。わずかに変更された XML ファイルに直面したときに間違った結果を返す非標準の超高速パーサーは何の役に立つのでしょうか?

HDDが読み取れる速度の半分でデータを解析できるものと互換性があることが保証されているXML解析LIBRARYを見つけることができれば、1つのスレッドが常にデータを読み取るプロデューサー/コンシューマーマルチスレッドアプリケーションを実装するだけです。他の2つは単に解析を行いますが、ディスクから。最終的には、互換性を維持しながら、HDD の速度によってのみ制限されます。間違いを犯しやすい速度だけを探している場合は、XML 機能をスキップし、扱っているサンプル XML ファイルの特定の特殊性に依存してください。アプリケーションは、さまざまな理由で壊れる可能性があります。

アプリケーションにとって最もコストのかかるサイクルは、運用ではなくメンテナンスであることに注意してください。50% 高速で保守が 200% 難しいものを作成することで現在得られるものは、コンピューターが 50% 高速化する 1 年ほどで失われます (競合に対する優位性が失われます)。その上、HDD の速度など、そのようなプロセスの自然な限界を超えても意味がありません。RAM ドライブのファイルを使用してテストすることは重要ではありません。アプリケーションが本番環境に入ると、HDD のファイルが使用され、アプリケーションのパフォーマンスは HDD の速度によって制限されます。


とにかく、私はたまに挑戦するのが好きで、パーサーが本当に好きです。以下は、入力文字列の各文字を1 回だけ見て、必要な部分だけをコピーする非常に単純なパーサーの実装です。次に何をすべきかを決定するためにノードの名前をコピーし、必要に応じてノードの「ペイロード」をコピーします。それを配列にプッシュするため。私の「適度な」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.
于 2013-01-05T20:23:11.883 に答える
1

XMLが非常に簡単で、形式が固定されていて、ファイルが非常に大きく、非常に高速な処理が必要な場合は、単純なwhile(i <length(unputStr))docycleを使用して自分で解析を実装することをお勧めします。そこで、「<」記号の検索、ノード名の抽出などを行うことができます。

于 2013-01-05T19:18:14.180 に答える