Используйте fscanf, чтобы прочитать переменные числа целого числа - PullRequest
0 голосов
/ 25 апреля 2018

У меня более 100 000 CSV-файлов в следующем формате:

1,1,5,1,1,1,0,0,6,6,1,1,1,0,1,0,13,4,7,8,18,20,,,,,,,,,,,,,,,,,,,,,,
1,1,5,1,1,1,0,1,6,5,1,1,1,0,1,0,4,7,8,18,20,,,,,,,,,,,,,,,,,,,,,,,
1,1,5,1,1,1,0,2,6,5,1,1,1,0,1,0,4,7,8,18,20,,,,,,,,,,,,,,,,,,,,,,,
1,1,5,1,1,1,0,3,6,5,1,1,1,0,1,0,13,4,7,8,20,,,,,,,,,,,,,,,,,,,,,,,
1,1,5,1,1,1,0,4,6,5,1,1,1,0,1,0,13,4,7,8,20,,,,,,,,,,,,,,,,,,,,,,,
1,1,5,1,1,1,0,5,6,4,1,0,1,0,1,0,4,8,18,20,,,,,,,,,,,,,,,,,,,,,,,,
1,1,5,1,1,1,0,6,6,5,1,1,1,0,1,0,4,7,8,18,20,,,,,,,,,,,,,,,,,,,,,,,
1,1,5,1,1,1,0,7,6,5,1,1,1,0,1,0,13,4,7,8,20,,,,,,,,,,,,,,,,,,,,,,,
1,1,5,1,1,1,0,8,6,5,1,1,1,0,1,0,13,4,7,8,20,,,,,,,,,,,,,,,,,,,,,,,
1,1,5,1,1,2,0,0,12,12,1,2,4,1,1,0,13,4,7,8,18,20,21,25,27,29,31,32,,,,,,,,,,,,,,,,

Все, что мне нужно, это поле 10 и поле 17 и далее, поле 10 - это счетчик, показывающий, сколько сохраненное целое число начинается с поля 17, т.е. мне нужно:

6,13,4,7,8,18,20
5,4,7,8,18,20
5,4,7,8,18,20
5,13,4,7,8,20
5,13,4,7,8,20
4,4,8,18,20
5,4,7,8,18,20
5,13,4,7,8,20
5,13,4,7,8,20
12,13,4,7,8,18,20,21,25,27,29,31,32

Максимальное число целых чисел, которое нужно прочитать, составляет 28. Я легко могу добиться этого с помощью Getline в C ++, однако, исходя из моего предыдущего опыта, так как мне нужно обработать более 100 000 таких файлов, и каждый файл может иметь 300 000 ~ 400 000 таких строк. Поэтому использование Getline для чтения данных и построения вектора> может иметь серьезные проблемы с производительностью для меня. Я попытался использовать fscanf для достижения этой цели:

while (!feof(stream)){
 fscanf(fstream,"%*d,%*d,%*d,%*d,%*d,%*d,%*d,%*d,%*d,%d",&MyCounter);
 fscanf(fstream,"%*d,%*d,%*d,%*d,%*d,%*d"); // skip to column 17
 for (int i=0;i<MyCounter;i++){
  fscanf(fstream,"%d",&MyIntArr[i]);
 }
 fscanf(fstream,"%*s"); // to finish the line
}

Однако, это вызовет fscanf несколько раз, а также может привести к проблемам с производительностью. Есть ли способ прочитать в переменной целое число на 1 вызов с fscanf? Или мне нужно прочитать строку и затем strsep / стои это? Сравните с fscanf, который лучше с точки зрения производительности?

Ответы [ 4 ]

0 голосов
/ 26 апреля 2018

В конечном итоге я использую файл с отображенной памятью для решения своей проблемы (это решение является побочным продуктом моей предыдущей проблемы, проблемы с производительностью при чтении большого файла CSV) чтение больших проблем с производительностью файла CSV в C ++

Поскольку я работаю в MS Windows, поэтому я использую «Портативное отображение памяти C ++ Class» Стефана Брамма http://create.stephan -brumme.com / portable-memory-mapping / Поскольку мне не нужноразобраться с файлом (ами)> 2 Гб, моя реализация проще.Чтобы получить файл размером более 2 ГБ, посетите веб-сайт, чтобы узнать, как с ним работать.

Ниже приведен мой код:

// may tried RandomAccess/SequentialScan
MemoryMapped MemFile(FilterBase.BaseFileName, MemoryMapped::WholeFile, MemoryMapped::RandomAccess);

// point to start of memory file
char* start = (char*)MemFile.getData();
// dummy in my case
char* tmpBuffer = start;

// looping counter
uint64_t i = 0;

// pre-allocate result vector
MyVector.resize(300000);

// Line counter
int LnCnt = 0;

//no. of field
int NumOfField=43;
//delimiter count, num of field + 1 since the leading and trailing delimiter are virtual
int DelimCnt=NoOfField+1;
//Delimiter position. May use new to allocate at run time
// or even use vector of integer
// This is to store the delimiter position in each line
// since the position is relative to start of file. if file is extremely
// large, may need to change from int to unsigner, long or even unsigned long long
static  int DelimPos[DelimCnt];

// Max number of field need to read usually equal to NumOfField, can be smaller, eg in my case, I only need 4 fields
// from first 15 field, in this case, can assign 15 to MaxFieldNeed
int MaxFieldNeed=NumOfField;
// keep track how many comma read each line
int DelimCounter=0;
// define field and line seperator
char FieldDelim=',';
char LineSep='\n';

// 1st field, "virtual Delimiter" position
DelimPos[CommaCounter]=-1
DelimCounter++;

// loop through the whole memory field, 1 and only once
for (i = 0; i < MemFile.size();i++)
{
  // grab all position of delimiter in each line
  if ((MemFile[i] == FieldDelim) && (DelimCounter<=MaxFieldNeed)){
    DelimPos[DelimCounter] = i;
    DelimCounter++;
  };

  // grab all values when end of line hit
  if (MemFile[i] == LineSep) {
    // no need to use if (DelimCounter==NumOfField) just assign anyway, waste a little bit
    // memory in integer array but gain performance 
    DelimPos[DelimCounter] = i;
    // I know exactly what the format is and what field(s) I want
    // a more general approach (as a CSV reader) may put all fields
    // into vector of vector of string
    // With *EFFORT* one may modify this piece of code so that it can parse
    // different format at run time eg similar to:
    // fscanf(fstream,"%d,%f....
    // also, this piece of code cannot handle complex CSV e.g.
    // Peter,28,157CM
    // John,26,167CM
    // "Mary,Brown",25,150CM
    MyVector.StrField = string(strat+DelimPos[0] + 1, strat+DelimPos[1] - 1);
    MyVector.IntField = strtol(strat+DelimPos[3] + 1,&tmpBuffer,10);
    MyVector.IntField2 = strtol(strat+DelimPos[8] + 1,&tmpBuffer,10);
    MyVector.FloatField = strtof(start + DelimPos[14] + 1,&tmpBuffer);
    // reset Delim counter each line
    DelimCounter=0
    // previous line seperator treat as first delimiter of next line
    DelimPos[DelimCounter] = i;
    DelimCounter++
    LnCnt++;
  }
}
MyVector.resize(LnCnt);
MyVector.shrink_to_fit();
MemFile.close();
};

Я могу кодировать все, что захочу:

  if (MemFile[i] == LineSep) {
  }

например, обработать пустое поле, выполнить вычисления и т. Д. С помощью этого куска кода я обрабатываю 2100 файлов (6,3 ГБ) за 57 секунд !!!(Я кодирую формат CSV в нем и только 4 значения в моем предыдущем случае).Позже изменит этот код для решения этой проблемы.Спасибо всем, кто мне помогает в этом вопросе.

0 голосов
/ 26 апреля 2018

Чтобы максимизировать производительность, вы должны сопоставить файлы в памяти с mmap или эквивалентным и проанализировать файл с помощью специального кода, обычно сканируя один символ за раз с указателем, проверяя на '\n' и / или'\r' для завершения записи и преобразования чисел на лету для хранения в ваших массивах.Хитрые части:

  • как вы распределяете или иным образом обрабатываете массивы назначения.
  • все поля являются числовыми?целое?
  • последняя запись заканчивается новой строкой?Вы можете легко проверить это условие после вызова mmap.Преимущество заключается в том, что вам нужно проверять конец файла только при появлении последовательности новой строки.
0 голосов
/ 26 апреля 2018

Вероятно, самый простой способ прочитать определенное во время выполнения число целых чисел - указать на правую часть более длинной строки формата.Другими словами, мы можем иметь строку формата с 28 %d, спецификаторами, но указать на n th перед концом строки и передать этот указатель как строку формата для scanf().

В качестве простого примера рассмотрите возможность принятия 3 целых чисел максимум от 6:

"%d,%d,%d,%d,%d,%d,"
          ^

Стрелка показывает указатель строки для использования в качестве аргумента шаблона.


Вот полный проработанный пример;его время выполнения составляет около 8 секунд на 1 миллион итераций (10 миллионов строк) при сборке с gcc -O3.Механика немного усложняет обновление указателя входной строки, что, очевидно, не нужно при чтении из файлового потока.Я пропустил проверку nfields <= 28, но это легко добавить.

char const *const input =
    "1,1,5,1,1,1,0,0,6,6,1,1,1,0,1,0,13,4,7,8,18,20,,,,,,,,,,,,,,,,,,,,,,\n"
    "1,1,5,1,1,1,0,1,6,5,1,1,1,0,1,0,4,7,8,18,20,,,,,,,,,,,,,,,,,,,,,,,\n"
    "1,1,5,1,1,1,0,2,6,5,1,1,1,0,1,0,4,7,8,18,20,,,,,,,,,,,,,,,,,,,,,,,\n"
    "1,1,5,1,1,1,0,3,6,5,1,1,1,0,1,0,13,4,7,8,20,,,,,,,,,,,,,,,,,,,,,,,\n"
    "1,1,5,1,1,1,0,4,6,5,1,1,1,0,1,0,13,4,7,8,20,,,,,,,,,,,,,,,,,,,,,,,\n"
    "1,1,5,1,1,1,0,5,6,4,1,0,1,0,1,0,4,8,18,20,,,,,,,,,,,,,,,,,,,,,,,,\n"
    "1,1,5,1,1,1,0,6,6,5,1,1,1,0,1,0,4,7,8,18,20,,,,,,,,,,,,,,,,,,,,,,,\n"
    "1,1,5,1,1,1,0,7,6,5,1,1,1,0,1,0,13,4,7,8,20,,,,,,,,,,,,,,,,,,,,,,,\n"
    "1,1,5,1,1,1,0,8,6,5,1,1,1,0,1,0,13,4,7,8,20,,,,,,,,,,,,,,,,,,,,,,,\n"
    "1,1,5,1,1,2,0,0,12,12,1,2,4,1,1,0,13,4,7,8,18,20,21,25,27,29,31,32,,,,,,,,,,,,,,,,\n";

#include <stdio.h>

#define SKIP_FIELD "%*[^,],"
#define DECIMAL_FIELD "%d,"

int read()
{
    int n;             /* bytes read - not needed for file or stdin */
    int sum = 0;       /* just to make sure results are used */

    for (char const *s = input;  *s;  ) {
        int nfields;
        int array[28];
        int m = sscanf(s,
                       /* field 0 is missing */
                       SKIP_FIELD SKIP_FIELD SKIP_FIELD
                       SKIP_FIELD SKIP_FIELD SKIP_FIELD
                       SKIP_FIELD SKIP_FIELD SKIP_FIELD
                       DECIMAL_FIELD /* field 10 */
                       SKIP_FIELD SKIP_FIELD SKIP_FIELD
                       SKIP_FIELD SKIP_FIELD SKIP_FIELD
                       "%n",
                       &nfields,
                       &n);
        if (m != 1) {
            return -1;
        }

        s += n;

        static const char fieldchars[] = DECIMAL_FIELD;
        static const size_t fieldsize = sizeof fieldchars - 1; /* ignore terminating null */

        static const char *const parse_entries =
            DECIMAL_FIELD DECIMAL_FIELD DECIMAL_FIELD DECIMAL_FIELD
            DECIMAL_FIELD DECIMAL_FIELD DECIMAL_FIELD DECIMAL_FIELD
            DECIMAL_FIELD DECIMAL_FIELD DECIMAL_FIELD DECIMAL_FIELD
            DECIMAL_FIELD DECIMAL_FIELD DECIMAL_FIELD DECIMAL_FIELD
            DECIMAL_FIELD DECIMAL_FIELD DECIMAL_FIELD DECIMAL_FIELD
            DECIMAL_FIELD DECIMAL_FIELD DECIMAL_FIELD DECIMAL_FIELD
            DECIMAL_FIELD DECIMAL_FIELD DECIMAL_FIELD DECIMAL_FIELD
            "[^\n] ";
        const char *const line_parse = parse_entries + (28-nfields) * fieldsize;

        /* now read nfields (max 28) */
        m = sscanf(s,
                   line_parse,
                   &array[0], &array[1], &array[2], &array[3],
                   &array[4], &array[5], &array[6], &array[7],
                   &array[8], &array[9], &array[10], &array[11],
                   &array[12], &array[13], &array[14], &array[15],
                   &array[16], &array[17], &array[18], &array[19],
                   &array[20], &array[21], &array[22], &array[23],
                   &array[24], &array[25], &array[26], &array[27]);
        if (m != nfields) {
            return -1;
        }

        /* advance stream position */
        sscanf(s, "%*[^\n] %n", &n);  s += n;

        /* use the results */
        for (int i = 0;  i < nfields;  ++i) {
            sum += array[i];
        }
    }
    return sum;
}

#undef SKIP_FIELD
#undef DECIMAL_FIELD

int main()
{
    int sum = 0;
    for (int i = 0;  i < 1000000;  ++i) {
        sum += read() * (i&1 ? 1 : - 1); /* alternate add and subtract */
    }
    return sum != 0;
}
0 голосов
/ 26 апреля 2018

Итак, в каждой строке максимум 43 номера. Даже в 64 битах каждое число ограничено 21 цифрой, поэтому 1024 байта достаточно для макс. 946 байтов, которыми может быть строка (при условии, что пробел отсутствует).

char line[1024];

while (fgets(line, sizeof(line), stdin) != NULL) {
    //...
}

Вспомогательная функция для перехода к нужному столбцу.

const char *find_nth_comma(const char *s, int n) {
    const char *p = s;
    if (p && n) while (*p) {
        if (*p == ',') {
            if (--n == 0) break;
        }
        ++p;
    }
    return p;
}

Итак, внутри вашего цикла перейдите к столбцу 10, чтобы найти первое интересующее число, а затем перейдите к столбцу 17, чтобы начать чтение остальных чисел. Завершенный цикл выглядит так:

while (fgets(line, sizeof(line), stdin) != NULL) {
    const char *p = find_nth_comma(line, 9);
    char *end;
    assert(p && *p);
    MyCounter = strtol(p+1, &end, 10);
    assert(*end == ',');
    p = find_nth_comma(end+1, 6);
    assert(p && *p);
    for (int i = 0; i < MyCounter; ++i, p = end) {
        MyIntArray[i] = strtol(p+1, &end, 10);
        assert((*end == ',') ||
               (i == MyCounter-1) &&
               (*end == '\0' || isspace(*end & 0xFF)));
    }
}

Этот подход будет работать и с решением mmap. fgets будет заменен функцией, которая указывает на следующую строку для обработки в файле. find_nth_comma потребуется модификация для определения конца строки / конца файла, а не для того, чтобы полагаться на завершенную строку NUL. strtol будет изменено с помощью пользовательской функции, которая снова обнаруживает конец строки или конец файла. (Целью таких изменений является удаление любого кода, который потребовал бы копирования данных, что было бы мотивом для подхода mmap.)

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

...