самый быстрый способ разбора большого файла (~ 2 Гб) с разделителем Tab - PullRequest
0 голосов
/ 01 января 2019

Пожалуйста, сообщите мне: есть ли более эффективный способ выполнения таких задач:

  1. Считывание пар ключ-значение из файла с разделителями первой вкладки в контейнер карты
  2. При чтении изфайл с разделителями во второй вкладке (пары «ключ-значение»), запись в третью

Выполнение этих двух шагов для fname1 и fname2 заняло 7 000 000 строк.

#include <string>
#include <map>
#include <fstream>
string col1;
int x;
map<string, int> m;
ifstream is1(fname1, ios::in | ios::binary);
ifstream is2(fname2, ios::in | ios::binary);
ofstream os3(fname3, ios::out | ios::binary);
if (is1.is_open())
{
    while (is1 >> col1 >> x)
    {
        m[col1] = x;
    }
    is.close();
}
if (is2.is_open() && os3.is_open())
{
    while (is2 >> col1 >> x)
    {
        if (m.count(col1) > 0)
            x += m[col1];
        os3 << col1 << "\t" << x << endl;
    }
    is2.close();
    os3.close();
}

Что я сделал не так?Есть ли более эффективный способ выполнения таких задач?Или файловый ввод / вывод в большинстве случаев является узким местом?

ОБНОВЛЕНО: Здесь я приведу две реализации одного и того же алгоритма.Главный вопрос: почему pythonic версия работает быстрее ?Я решил переключиться на C ++, потому что слышал, что он обеспечивает более быстрый код.Был ли я не прав?

fname1, fname2 - ввод.fname3 - желаемый вывод.

fname1:

col1 col2 col3

1 1 1

2 2 2

fname2:

col1 col2 col3

1 1 2

3 3 3

fname3:

col1 col2 col3

1 13

2 2 2

3 3 3

def merge_two_files(fname1, fname2, fname3):
    fout=open(fname3,'w')
    fin1=open(fname1)
    d1=dict()
    for line in fin1:
        l=line.strip().split('\t')
        key='\t'.join(l[0:2])
        d1[key] = float(l[2])
    fin1.close()
    d2=dict()
    fin2=open(fname2)
    for line in fin2:
        l=line.strip().split('\t')
        key='\t'.join(l[0:2])
        d2[key] = float(l[2])
    fin2.close()
    for e in d1.viewkeys() & d2.viewkeys():
        line_out='\t'.join([e,'{:.2f}'.format(d1[e]+d2[e])])
        fout.write(line_out+'\n')
    for e in d1.viewkeys() - (d1.viewkeys() & d2.viewkeys())
        line_out='\t'.join([e,'{:.2f}'.format(d1[e])])
        fout.write(line_out+'\n')
    for e in d2.viewkeys() - (d1.viewkeys() & d2.viewkeys())
        line_out='\t'.join([e,'{:.2f}'.format(d2[e])])
        fout.write(line_out+'\n')

#include <fstream>
#include <string>
#include <unordered_map>
#include <set>

using namespace std;

int main() {
    unordered_map < string, float > map1, map2 ;
    set < string > s1, s2, both ;
    string col1, col2, key, fname1, fname2, fname3 ;
    float col3 ;
    ifstream f1 ( fname1, ios::in | ios::binary) ;
    ifstream f2 ( fname2, ios::in | ios::binary) ;
    ofstream f3 ( fname3, ios::out | ios::binary) ;

    if ( f1.is_open() ) {
        while ( f1 >> col1 >> col2 >> col3 )
        key= col1 + "\t" + col2 ;
        map1.insert(make_pair(key,col3)) ;
        s1.insert(key) ;
    }
    f1.close()
    if ( f2.is_open() ) {
        while ( f2 >> col1 >> col2 >> col3 ) {
        key= col1 + "\t" + col2 ;
        map2.insert(make_pair(key,col3)) ;
        s2.insert(key) ;
        }
    }
    f2.close() ;

    set_intersection(s1.begin(), s1.end(),
                     s2.begin(), s2.end(),
                     inserter(both, both.begin())) ;
    if ( f3.is_open() ) {
        for ( const auto& e : both ) {
            f3 << e << "\t" << map1.at(e) + map2.at(e) << "\n" ;
        }
        for ( const auto& kv : map1 ) {
            if ( both.count(kv.first) ) continue ;
            f3 << kv.first << "\t" << kv.second << "\n" ;
        }
        for ( const auto& kv : map2 ) {
            if ( both.count(kv.first) ) continue ;
            f3 << kv.first << "\t" << kv.second << "\n" ;
        }
    }
    f3.close() ;
    return 0;
}

Ответы [ 2 ]

0 голосов
/ 02 января 2019

Узкие места

Ваши узкие места - это 1) файловый ввод-вывод и 2) преобразование формата.Файл может потребовать дополнительных затрат, таких как раскрутка (жесткие диски), время поиска и фактическое время чтения.

Потоковая передача данных

Независимо от того, находится ли файл на флэш-диске или на жестком диске, потоковая передача данных наиболее эффективна (то есть обеспечивает непрерывную передачу данных).Например, чтение 1 КБ данных в 1 транзакции более эффективно, чем чтение 1 КБ данных в 1 КБ транзакций.Ваш самый эффективный метод здесь - это читать большие объемы в память.Вы можете проверить свою ОС, чтобы увидеть, поддерживает ли она отображение памяти.

Преобразование данных

Следующим узким местом является анализ и преобразование данных из текстового во внутренний числовой формат.Это требует времени.Чтобы ускорить это, ваши данные должны быть записаны в двоичном (внутреннем) представлении.Это может быть невозможно из-за проблем Endianess или с плавающей запятой.

Хранение данных

Я предполагаю, что следующим узким местом является хранение внутренних отформатированных данных в структуре данных.Для ключей, значений, пар вы можете использовать std::map или std::unordered_map.Если вам не нужно время поиска, вы можете создать struct со значениями и сохранить в массиве (или std::vector, если количество неизвестно во время компиляции).

Профилирование

Лучший способ узнать, где находятся ваши узкие места, - профиль .Запустите ваше приложение 1E6 повторений и возьмите среднее значение (вам может потребоваться запустить 1E9, чтобы получить лучшую точность).Поищите в Интернете «Бенчмаркинг программ на С ++».Бенчмаркинг покажет вам, как получить лучшие результаты.

0 голосов
/ 01 января 2019

По крайней мере в пределе большого количества различных ключей в этих файлах (я полагаю, что вы достаточно близко подошли), ограничивающим фактором будет поиск ключа map, а не IO.Поиск ключа в std::map имеет сложность по времени O(ln n), где n - это количество различных ключей в контейнере.

Используйте std::unordered_map, который в среднем амортизировал O(1) время поиска ключа.

Используйте профилировщик, чтобы сообщить, на что тратится большая часть времени, прежде чем начинать оптимизацию с неверными предположениями.Сделайте то же самое в этом случае, чтобы убедиться, что я прав.

...