2012-04-12 1 views
4

Входной файл представляет собой один файл размером около 70 ГБ, каждая строка которого содержит информацию о клиенте. Программа читает этот файл и делает один файл для каждого клиента. Есть 8000 клиентов, но мы должны предоставить 40000 клиентов. В настоящее время команда сортировки UNIX используется для сортировки файла по клиенту, а затем записываются файлы клиента. Таким образом, у программы есть только один обработчик файлов, открытый для создания файлов. Мы не хотим использовать команду сортировки, поскольку она потребляет около 1,5 часов. Это, однако, означает, что 8000 обработчиков файлов должны оставаться открытыми. Параметры ядра могут потребоваться изменить. Можно ли открыть так много файлов без изменения параметров ядра. Я попытался пройти через сайт libevent, но не уверен, что это правильное решение.Открытие и запись в несколько файлов в C

+0

Не можете ли вы сохранить упорядоченный (по клиенту) индекс файла и затем обрабатывать клиентов один за другим? –

ответ

11

Вам не обязательно 8000 дескрипторов файлов открыты в то же время, и вам не нужно сортировать данные. Сортировка - это отходы, если вам не нужны все сортированные линии клиентов.

Предположительно, вы можете идентифицировать клиента по определенному элементу на линии. Скажем, (к примеру), это первые 8 символов в каждой строке, то ваш псевдо-код выглядит следующим образом:

delete all files matching "*_out.dat" 
for each line in file: 
    key = left (line, 8) 
    open file key + "_out.dat" for append 
    write line to file 
    close file 

Вот и все. Просто. Одновременно открывается только один файл и не сортируется по времени.

Теперь есть дальнейшие усовершенствования, которые могут быть сделаны для того, среди них являются:

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

  2. Поддерживает кэш открытых файлов, как последний список (например, 16 различных ключей). Опять же, это предотвратит закрытие до тех пор, пока дескриптор файла не будет повторно использован, но он будет обрабатывать ситуацию, когда кластеры более эффективны (например, клиент 1,2,3,7,1,2,3,2,2,3, 7,4, ...).

Но основная теория остается неизменной: не пытайтесь открыть 8000 (или 40000) файлов одновременно, когда вы можете обойтись без них.


В качестве альтернативы, просто обрабатывать данные, припрятать все это в базу данных и используя запросы затем создавать каждый файл с серией запросов. Будет ли это быстрее, чем вышеприведенное решение должно быть проверено, поскольку на самом деле должно быть каждый предложение здесь. Измерьте, не угадайте!


Теперь, так как я применяла оптимизации мантры, давайте сделаем некоторые моменты времени, имея в виду, что это относится только к моей аппаратной и может отличаться от вашей.

Начните со следующего сценария, который генерирует файл в 1 миллион строк, где первые восемь символов каждой строки являются случайным числом между 10000000 и 10032767 включительно. Мы будем использовать символы с 5 по 8 включительно, чтобы дать нам номер клиента, десять тысяч клиентов примерно в сто строк на одного клиента:

#!/bin/bash 
line='the quick brown fox jumps over the lazy dog' 
for p0 in 1 2 3 4 5 6 7 8 9 0 ; do 
for p1 in 1 2 3 4 5 6 7 8 9 0 ; do 
    for p2 in 1 2 3 4 5 6 7 8 9 0 ; do 
    for p3 in 1 2 3 4 5 6 7 8 9 0 ; do 
    for p4 in 1 2 3 4 5 6 7 8 9 0 ; do 
    for p5 in 1 2 3 4 5 6 7 8 9 0 ; do 
     ((x = 10000000 + $RANDOM)) 
     echo "$x$line" 
    done 
    done 
    done 
    done 
done 
done 

Размер файла производится около 50М. Мы можем масштабировать его до 100M, просто объединяя 2 копии его в другой файл, и это дает нам около двухсот строк для каждого клиента.


Теперь рассмотрим следующую программу:

#include <stdio.h> 
#include <string.h> 
#include <errno.h> 

#define FOUT_STR "1234_out.dat" 

int main (void) { 
    FILE *fIn, *fOut; 
    char outFile[sizeof (FOUT_STR)]; 
    char buff[1000]; 

    if ((fIn = fopen ("data.dat", "r")) == NULL) { 
     printf ("Error %d opening 'data.dat'\n", errno); 
     return 1; 
    } 

    memcpy (outFile, FOUT_STR, sizeof (FOUT_STR)); 
    if ((fOut = fopen (outFile, "w")) == NULL) { 
     printf ("Error %d opening '%s'\n", errno, outFile); 
     return 1; 
    } 

    while (fgets (buff, sizeof (buff), fIn) != NULL) { 
     fputs (buff, fOut); 
    } 

    fclose (fOut); 
    fclose (fIn); 
    return 0; 
} 

Это дает базовый показатель просто писать все записи в один файл, и берет под второй бежать.


Теперь давайте тот, который открывает новый файл каждые двести строк - это поведение, которое вы хотите увидеть, если файл был уже отсортирован клиентом:

#include <stdio.h> 
#include <string.h> 
#include <errno.h> 

#define FOUT_STR "1234_out.dat" 

int main (void) { 
    FILE *fIn, *fOut; 
    char outFile[sizeof (FOUT_STR)]; 
    char buff[1000]; 
    char custNum[5]; 
    int i = -1; 

    if ((fIn = fopen ("data.dat", "r")) == NULL) { 
     printf ("Error %d opening 'data.dat'\n", errno); 
     return 1; 
    } 

    fOut = NULL; 
    while (fgets (buff, sizeof (buff), fIn) != NULL) { 
     i++; 
     if ((i % 200) == 0) { 
      if (fOut != NULL) 
       fclose (fOut); 
      sprintf (custNum, "%04d", i/200); 
      memcpy (outFile, FOUT_STR, sizeof (FOUT_STR)); 
      memcpy (outFile, custNum, 4); 
      if ((fOut = fopen (outFile, "w")) == NULL) { 
       printf ("Error %d opening '%s'\n", errno, outFile); 
       break; 
      } 
     } 
     fputs (buff, fOut); 
    } 
    if (fOut != NULL) 
     fclose (fOut); 

    fclose (fIn); 
    return 0; 
} 

Это занимает около 2 секунд (0:00:02) для файла 100M, и тестирование его с помощью файла 200M и 400M указывает на то, что он масштабируется линейно. Это означает, что с отсортированным 70G-файлом вы смотрите примерно 1400 или 0:23:20. Обратите внимание, что это будет выше вашей стоимости сортировки, которая у вас есть как 1.5h (1:30:00), что дает вам полную стоимость 1:53:20.


Теперь давайте реализуем из упрощенно программу, которая просто открывает каждый файл для Append для каждой строки:

#include <stdio.h> 
#include <string.h> 
#include <errno.h> 

#define FOUT_STR "1234_out.dat" 

int main (void) { 
    FILE *fIn, *fOut; 
    char outFile[sizeof (FOUT_STR)]; 
    char buff[1000]; 

    if ((fIn = fopen ("data.dat", "r")) == NULL) { 
     printf ("Error %d opening 'data.dat'\n", errno); 
     return 1; 
    } 

    while (fgets (buff, sizeof (buff), fIn) != NULL) { 
     memcpy (outFile, FOUT_STR, sizeof (FOUT_STR)); 
     memcpy (outFile, &(buff[4]), 4); 
     if ((fOut = fopen (outFile, "a")) == NULL) { 
      printf ("Error %d opening '%s'\n", errno, outFile); 
      break; 
     } 
     fputs (buff, fOut); 
     fclose (fOut); 
    } 

    fclose (fIn); 
    return 0; 
} 

Когда мы запустим это с файлом 100M, он принимает 244s (0:04:04) , И снова тестирование с файлом 200M и 400M указывает на линейное масштабирование. Итак, для файла 70G это будет 47:26:40, а не действительно, что вы бы назвали улучшением по сравнению с вашим двухчасовым режимом сортировки и обработки.


Однако, давайте попробуем другой галс, со следующей программой, которая поддерживает сотни файл обрабатывает каждый раз через входной файл (сделано в сто раз):

#include <stdio.h> 
#include <string.h> 
#include <errno.h> 

#define FOUT_STR "1234_out.dat" 

int main (void) { 
    FILE *fIn, *fOut[100]; 
    char outFile[sizeof (FOUT_STR)]; 
    char buff[1000]; 
    int seg, cust; 
    char segNum[3], custNum[3]; 

    for (seg = 0; seg < 100; seg++) { 
     sprintf (segNum, "%02d", seg); 

     if ((fIn = fopen ("data.dat", "r")) == NULL) { 
      printf ("Error %d opening 'data.dat'\n", errno); 
      return 1; 
     } 

     for (cust = 0; cust < 100; cust++) { 
      sprintf (custNum, "%02d", cust); 

      memcpy (outFile, FOUT_STR, sizeof (FOUT_STR)); 
      memcpy (outFile+0, segNum, 2); 
      memcpy (outFile+2, custNum, 2); 
      if ((fOut[cust] = fopen (outFile, "w")) == NULL) { 
       printf ("Error %d opening '%s'\n", errno, outFile); 
       return 1; 
      } 
     } 

     while (fgets (buff, sizeof (buff), fIn) != NULL) { 
      if (memcmp (&(buff[4]), segNum, 2) == 0) { 
       cust = (buff[6] - '0') * 10 + buff[7] - '0'; 
       fputs (buff, fOut[cust]); 
      } 
     } 

     for (cust = 0; cust < 100; cust++) { 
      fclose (fOut[cust]); 
     } 

     fclose (fIn); 
    } 

    return 0; 
} 

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

Когда это выполняется в файле 100M, требуется около 28 секунд (0:00:28). Опять же, это похоже на линейное масштабирование для файла 200M и 400M, поэтому файл 70G должен занимать 5:26:40.

Тем не менее, даже близко к двухчасовой фигуре.


Так что же происходит, когда мы открываем тысячу выходных файлов одновременно:

#include <stdio.h> 
#include <string.h> 
#include <errno.h> 

#define FOUT_STR "1234_out.dat" 

int main (void) { 
    FILE *fIn, *fOut[1000]; 
    char outFile[sizeof (FOUT_STR)]; 
    char buff[1000]; 
    int seg, cust; 
    char segNum[2], custNum[4]; 

    for (seg = 0; seg < 10; seg++) { 
     sprintf (segNum, "%01d", seg); 

     if ((fIn = fopen ("data.dat", "r")) == NULL) { 
      printf ("Error %d opening 'data.dat'\n", errno); 
      return 1; 
     } 

     for (cust = 0; cust < 1000; cust++) { 
      sprintf (custNum, "%03d", cust); 

      memcpy (outFile, FOUT_STR, sizeof (FOUT_STR)); 
      memcpy (outFile+0, segNum, 1); 
      memcpy (outFile+1, custNum, 3); 
      if ((fOut[cust] = fopen (outFile, "w")) == NULL) { 
       printf ("Error %d opening '%s'\n", errno, outFile); 
       return 1; 
      } 
     } 

     while (fgets (buff, sizeof (buff), fIn) != NULL) { 
      if (memcmp (&(buff[4]), segNum, 1) == 0) { 
       cust = (buff[5] - '0') * 100 + (buff[6] - '0') * 10 + buff[7] - '0'; 
       fputs (buff, fOut[cust]); 
      } 
     } 

     for (cust = 0; cust < 1000; cust++) { 
      fclose (fOut[cust]); 
     } 

     fclose (fIn); 
    } 

    return 0; 
} 

Это занимает около 12 секунд для файла 100М, и дал бы нам 2:20:00, приближаясь к вид, но не совсем там.


Unfortuntely, когда мы переходим на следующий логический шаг, пытаясь открытия весь 10000 файлов в один хит, мы видим:

Error 24 opening '1020_out.dat' 

означает, что мы, наконец, пришли к пределу (стандартный ввод, стандартный вывод, стандартная ошибка и около 1019 других файлов), что указывает на то, что 1024 дескриптора - это все, что нам разрешено.

Возможно, способ сортировки и обработки - это лучший способ пойти.

+0

Спасибо за ваши усилия. Вы дали нам много решений и как они взаимосвязаны с другими. К сожалению, используемый метод кажется лучшим на данный момент. Нам не разрешат внедрять решение для базы данных из-за затрат на лицензирование, а старшие пользователи не разрешат свободным базам данных. Еще раз спасибо. –

+0

@BharatElwe, люди, которые отказываются от возможных решений из-под контроля, - это идиоты (не вы, я говорю об этих «ваших старших»). Но, возможно, вам нужно стать старым старым козлом, как я, прежде чем вы сможете открыто назвать их идиотами и уйти с ним :-) Я бы, по крайней мере, решился на решение БД. Если вы обнаружите, что он может сделать ту же самую работу за 27 минут, что самое быстрое решение без БД за два часа, вы представляете это для пожилых людей и позволяете им принимать решение. Но, по крайней мере, вы посоветовали, если они затем выбирают _ignore_ ваш совет, это их проблема, а не ваша. – paxdiablo

0

Я не знаю о пределе на платформе Unix, однако в Windows вы можете либо использовать WINAPI для открытия как можно большего количества файлов, либо использовать _setMaxstdio, чтобы установить максимальное количество открытых файлов, по умолчанию 512 (используя fopen).

Here's a similar solution which may help you out!

Смежные вопросы