2013-03-02 3 views
2

Мне нужно обработать огромный XML-файл (> 10 ГБ), чтобы преобразовать его в CSV. Я использую XML::Twig.Проблема с производительностью с использованием XML :: Twig для обработки гигантских файлов (> 10 ГБ)

Файл содержит данные около 2,6 млн. Клиентов, каждый из которых будет иметь от 100 до 150 полей (в зависимости от профиля клиентов).

Я сохраняю все значения одного абонента в хеш-файле %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'); 
    } 

То же самое касается других функций * (значение2, значение3). Я не упомянул о других функциях, обеспечивающих его простоту.

<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 для каждого ребенка, даже если почти каждый раз, когда экземпляр ребенка происходит только один раз по всему профилю клиента. Может ли это вызвать задержку или какие-либо другие предложения по улучшению производительности? Threading и т. Д.? (Я искал googled и обнаружил, что резьба не очень помогает.)

+0

Является ли содержащим элемент '' в соответствии с вашим кодом или '', как показывает ваш XML? – Borodin

ответ

1

Сначала используйте DProf или NYTProf, чтобы понять, что замедляет ваш код. Но, я думаю, основная работа будет внутри парсера XML, поэтому мое мнение об этом не сможет значительно увеличить скорость.

В качестве другого варианта я предлагаю вам разделить (не разобрать), только этот XML на часть (необходимо сохранить консистенцию формата XML) и запустить ncpu вил для обработки независимо друг от друга, производят некоторый файл с agregate значений, а затем процесс Это.

Или вы можете преобразовать этот XML в нечто, что можно разборчиво без синтаксического анализатора XML. Например: вам нужны поля id, Key, ValueID, поэтому вы можете удалить «\ n» во входном файле и создать другой файл с одним objectProfile в строке. Затем подайте каждую строку в синтаксический анализатор. Это позволит вам использовать многопоточную обработку одного файла, поэтому вы будете использовать все процессоры. Вероятно, строка </objectProfile> может работать как разделитель записей. Вам нужно изучить формат вашего xml, чтобы принять решение.

P.S. Кто-то захочет сфокусировать меня на «синтаксическом анализе XML сам по себе плохо» или на некоторых ссылках, например this. Но иногда, когда у вас большие или большие входные данные, у вас был выбор: делать это в «законном» стиле; или делать это в заданное время с заданной точностью. Пользователям/клиентам все равно, как вы это делаете, они хотят результата.

+0

На самом деле вот почему я тоже. Разбор XML, отличный от обычного парсера, не меньше, чем грех. :) .. но я думаю, чтобы отделить и обработать его больше как текст. *скрещенные пальцы*. На самом деле расщепление было бы немного сложным, из-за размера. То же самое касается удаления \ n. :( На стороне примечания, я полностью знаком с XML-форматом. Я могу выделить его (как одна функция продолжает читать файл, кормить весь блок XML-парсером как строку), и мы можем развить несколько парсеров (скажем, 4, у меня 4 cpus)? – Muzammil

+0

У меня когда-то была программа Perl, которая запускалась в 100 раз быстрее, конвертируя ее, чтобы использовать реальный синтаксический анализатор XML, вместо того, чтобы пытаться разобрать «вручную». –

+0

@MichaelKay, любое предложение для реальный синтаксический анализатор ?? был бы очень оценен.Я использую xml :: twig. и он должен быть довольно хорошим. – Muzammil

2

Предлагаю использовать XML::LibXML::Reader. Он очень эффективен, потому что он не создает дерево XML в памяти, если вы его не попросите, и основан на отличной библиотеке LibXML.

Вам нужно будет привыкнуть к другому API от XML::Twig, но IMO все еще довольно прост.

Этот код делает именно то, что делает ваш собственный код, и мои тайминги предположили, что 10 миллионов записей, подобных тому, который вы показываете, будут обработаны за 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 
}