2

巨大な XML ファイル (>10 GB) を処理して CSV に変換する必要があります。を使用してXML::Twigいます。

このファイルには、約 260 万人の顧客のデータが含まれており、それぞれに約 100 から 150 のフィールドがあります (顧客のプロファイルによって異なります)。

1 つのサブスクライバーのすべての値を hash に格納し%customer、処理が完了したら、ハッシュの値を CSV 形式のテキスト ファイルに出力します。

問題はパフォーマンスです。処理には約6~8時間かかります。どのように減らすことができますか?

my $t = XML::Twig->new(
  twig_handlers => {
    'objects/simple'   => \&simpleProcess ,
    'objects/detailed' => \&detailedProcess ,
  },
  twig_roots => { objects => 1}
);

sub simpleProcess {
  my ($t, $simple) = @_;

  %customer= (); #reset the hash
  $customer{id}  = $simple->first_child_text('id');
  $customer{Key} = $simple->first_child_text('Key');
}

詳細タグには、ネストされたフィールドを含むいくつかのフィールドが含まれています。そのため、さまざまな種類のフィールドを収集するために毎回関数を呼び出します。

sub detailedProcess {
  my ($t, $detailed1) = @_;

  $detailed = $detailed1;
  if ($detailed->has_children('profile11')){ &profile11();}
  if ($detailed->has_children('profile12')){ &profile12();}
  if ($detailed->has_children('profile13')){ &profile13();}
}
sub profile11 {
  foreach $comcb ($detailed->children('profile11')) {
    $customer{COMCBcontrol} = $comcb->first_child_text('ValueID');
  }

他の関数 *(value2, value3) についても同様です。シンプルにするための他の機能については言及していません。

<objecProfile>
    <simple>
        <id>12345</id>
        <Key>N894FE</Key>
    </simple>
    <detailed>
        <ntype>single</ntype>
        <SubscriberType>genericSubscriber</SubscriberType>
        <odbssm>0</odbssm>
        <osb1>true</osb1>
        <natcrw>true</natcrw>
        <sr>2</sr>
        <Profile11>
            <ValueID>098765</ValueID>
        </Profile11>
        <Profile21>
        <ValueID>098765</ValueID>
        </Profile21>
        <Profile22>
        <ValueID>098765</ValueID>
        </Profile22>
        <Profile61>
            <ValueID>098765</ValueID>
        </Profile61>
    </detailed>
</objectProfile>

foreach問題は次のとおりです。ほとんどの場合、子インスタンスは顧客プロファイル全体で 1 回しか発生しませんが、すべての子に使用します。遅延が発生する可能性がありますか、またはパフォーマンスを改善するための他の提案はありますか? 糸通しなど? (ググったところ、スレッド化はあまり役に立たないことがわかりました。)

4

2 に答える 2

2

を使用することをお勧めしXML::LibXML::Readerます。これは、要求されない限りメモリ内に XML ツリーを構築せず、優れた LibXML ライブラリに基づいているため、非常に効率的です。

とは別の API に慣れる必要がありますがXML::Twig、それでもかなり単純です。

このコードは、独自のコードとまったく同じように機能します。私のタイミングでは、表示されているような 1,000 万件のレコードが 30 分で処理されることが示唆されました。

次の要素を繰り返しスキャンし<object>(質問に一貫性がないため、これが必要かどうかはわかりませんでした<objecProfile>)、ノードとその子孫をXML::LibXML::Elementオブジェクトにコピー$copyしてサブツリーにアクセスできるようにし、必要な情報を%customer.

use strict;
use warnings;

use XML::LibXML::Reader;

my $filename = 'objects.xml';

my $reader = XML::LibXML::Reader->new(location => $filename)
        or die qq(cannot read "$filename": $!);

while ($reader->nextElement('object')) {

    my %customer;

    my $copy = $reader->copyCurrentNode(1);

    my ($simple) = $copy->findnodes('simple');
    $customer{id}  = $simple->findvalue('id');
    $customer{Key} = $simple->findvalue('Key');

    my ($detailed) = $copy->findnodes('detailed');
    $customer{COMCBcontrol} = $detailed->findvalue('(Profile11 | Profile12 | Profile13)/ValueID');

    # Do something with %customer
}
于 2013-03-02T18:35:49.527 に答える
1

まず、DProf または NYTProf を使用して、コードの速度を低下させている原因を突き止めます。しかし、主な作業は XML パーサー内で行われると思いますので、これでは大幅に速度を上げることはできないと思います。

別のバリアントとして、この XML だけを分割 (解析ではなく) し (xml 形式の一貫性を保つ必要があります)、ncpuフォークを実行してそれぞれを個別に処理し、集計値を含むファイルを生成してから処理することをお勧めします。

または、この XML を XML パーサーなしで解析可能なものに変換できます。例: id、Key、ValueIDフィールドが必要なようです。そのため、入力ファイルの "\n" を削除して、1 行に 1 つのobjectProfileを持つ別のファイルを作成できます。次に、各行をパーサーに渡します。これにより、1 つのファイルのマルチスレッド処理を使用できるようになるため、すべての CPU を使用することになります。おそらく文字列</objectProfile>はレコード区切りとして機能します。決定を下すには、xml の形式を調べる必要があります。

PS誰かが「XMLを自分で解析するのは悪い」またはこのようなリンクで私に反対票を投じたいと思うでしょう。しかし、大きな高負荷または非常に大きな入力データがある場合、選択肢がありました。「合法的な」スタイルで行います。または、指定された時間内に指定された精度で実行します。ユーザー/顧客は、あなたがどのようにそれを行うかは気にせず、結果を求めています。

于 2013-03-02T07:53:18.277 に答える