2016-08-09 2 views
6

Я написал функцию, которая объединяет два больших файла (file1,file2) в новый файл (outputFile). Каждый файл представляет собой строковый формат, а записи разделяются байтом \ 0. Оба файла имеют одинаковое количество нулевых байтов.Улучшение производительности ввода-вывода для слияния двух файлов в C

Один пример файла с помощью двух записей может выглядеть следующим образом A\nB\n\0C\nZ\nB\n\0

Input: 
    file1: A\nB\0C\nZ\nB\n\0 
    file2: BBA\nAB\0T\nASDF\nQ\n\0 
    Output 
    outputFile: A\nB\nBBA\nAB\0C\nZ\nB\nT\nASDF\nQ\n\0 

FILE * outputFile = fopen(...); 
setvbuf (outputFile , NULL , _IOFBF , 1024*1024*1024) 
FILE * file1 = fopen(...); 
FILE * file2 = fopen(...); 
int c1, c2; 
while((c1=fgetc(file1)) != EOF) { 
    if(c1 == '\0'){ 
     while((c2=fgetc(file2)) != EOF && c2 != '\0') { 
      fwrite(&c2, sizeof(char), 1, outputFile); 
     } 
     char nullByte = '\0'; 
     fwrite(&nullByte, sizeof(char), 1, outputFile); 
    }else{ 
     fwrite(&c1, sizeof(char), 1, outputFile); 
    } 
} 

Есть ли способ, чтобы улучшить эту производительность ввода-вывода этой функции? Я увеличил размер буфера outputFile до 1 ГБ, используя setvbuf. Помогло бы оно использовать posix_fadvise на file1 и file2?

+2

Я бы сказал две вещи. FIrst, я всегда думал, что 'write' быстрее, чем' fwrite'. Во-вторых, не записывайте каждый байт в файл. Сделайте свой собственный промежуточный буфер и напишите в файл большие куски данных. – GMichael

+0

Сделайте свой код доступным для чтения. – Inline

+0

@GMichael спасибо за ваш совет. fwrite должен быть буферизирован. Считаете ли вы, что функция называется самокритичной? –

ответ

1

Вы делаете IO символ за символом. Это будет бесполезно и болезненно S-L-O-W, даже с буферизированными потоками.

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

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

typedef struct mapdata 
{ 
    const char *ptr; 
    size_t bytes; 
} mapdata_t; 

mapdata_t mapFile(const char *filename) 
{ 
    mapdata_t data; 
    struct stat sb; 

    int fd = open(filename, O_RDONLY); 
    fstat(fd, &sb); 

    data.bytes = sb.st_size; 

    /* assumes we have a NUL byte after the file data 
     If the size of the file is an exact multiple of the 
     page size, we won't have the terminating NUL byte! */ 
    data.ptr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); 
    close(fd); 
    return(data); 
} 

void unmapFile(mapdata_t data) 
{ 
    munmap(data.ptr, data.bytes); 
} 

void mergeFiles(const char *file1, const char *file2, const char *output) 
{ 
    char zeroByte = '\0'; 

    mapdata_t data1 = mapFile(file1); 
    mapdata_t data2 = mapFile(file2); 

    size_t strOffset1 = 0UL; 
    size_t strOffset2 = 0UL; 

    /* get a page-aligned buffer - a 64kB alignment should work */ 
    char *iobuffer = memalign(64UL * 1024UL, 1024UL * 1024UL); 

    /* memset the buffer to ensure the virtual mappings exist */ 
    memset(iobuffer, 0, 1024UL * 1024UL); 

    /* use of direct IO should reduce memory pressure - the 1 MB 
     buffer is already pretty large, and since we're not seeking 
     the page cache is really only slowing things down */ 
    int fd = open(output, O_RDWR | O_TRUNC | O_CREAT | O_DIRECT, 0644); 

    FILE *outputfile = fdopen(fd, "wb"); 
    setvbuf(outputfile, iobuffer, _IOFBF, 1024UL * 1024UL); 

    /* loop until we reach the end of either mapped file */ 
    for (;;) 
    { 
     fputs(data1.ptr + strOffset1, outputfile); 
     fwrite(&zeroByte, 1, 1, outputfile); 

     fputs(data2.ptr + strOffset2, outputfile); 
     fwrite(&zeroByte, 1, 1, outputfile); 

     /* skip over the string, assuming there's one NUL 
      byte in between strings */ 
     strOffset1 += 1 + strlen(data1.ptr + strOffset1); 
     strOffset2 += 1 + strlen(data2.ptr + strOffset2); 

     /* if either offset is too big, end the loop */ 
     if ((strOffset1 >= data1.bytes) || 
      (strOffset2 >= data2.bytes)) 
     { 
      break; 
     } 
    } 

    fclose(outputfile); 

    unmapFile(data1); 
    unmapFile(data2);  
} 

Я положил в отсутствии ошибки проверки на всех. Вам также нужно будет добавить соответствующие файлы заголовков.

Обратите внимание, что данные файла принимаются равным NOT, что является точным кратным размеру системной страницы, тем самым гарантируя, что после содержимого файла отображается NUL-байт. Если размер файла является точным кратным размеру страницы, вам нужно будет добавить mmap() дополнительную страницу после содержимого файла, чтобы убедиться, что для завершения последней строки существует NUL-байт.

Или вы можете положиться на то, что в качестве последнего байта содержимого файла имеется байт NUL. Если это когда-либо окажется неверным, вы, скорее всего, получите либо SEGV, либо поврежденные данные.

+0

- 1 для использования POSIX-специфического 'mmap'. Этот вопрос не требует или не требует. – rubenvb

+1

@ rubenvb Действительно? Вам тяжело заменять 'mmap()' чем-то вроде Windows 'CreateFileMapping()'? Он даже инкапсулирован в отдельный набор функций, чтобы сделать его простым. Вот ссылка, если вам нужна помощь: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366537%28v=vs.85%29.aspx –

+0

@rubenvb: И даже если бы не было альтернатива для систем, отличных от posix ... Системы posix по-прежнему являются основным прецедентом. – Hurkyl

-2

Если вы можете использовать потоки, сделайте одно для file1 и другое для файла2.

Сделайте outputFile столько, сколько вам нужно, затем сделайте thread1 напишите файл1 в outputFile.

Хотя thread2 искать это выход outputFile на длина file1 + 1, и написать file2

Edit:

Это не правильный ответ на этот случай, но предотвратить Беспорядки I Позволю себе это.

Больше я нашел дискурс об этом: improve performance in file IO in C

+0

Threading действительно не улучшает IO. Потому что IO - самая медленная операция, и она последовательна. Вы не можете парализовать IO на том же диске. – bolov

+0

Ну, если у нас есть неблокирующий байт-файл, мы сможем его написать. Это правильно? – Raskayu

+0

Я не эксперт здесь, но я уверен, что доступ к диску последователен, независимо от того, к каким секторам вы обращаетесь. Ускорение производится с использованием буферов ОС, файлов с отображением памяти и т. Д., А не путем чтения/записи в нескольких потоках. – bolov

-1

Незначительное улучшение было бы в том, что если вы собираетесь писать отдельные символы, вы должны использовать fputc, а не fwrite.

Кроме того, так как вы заботитесь о скорости, вы должны попробовать putc и getc, а не fputc и fgetc, чтобы увидеть, если он работает быстрее.

0
  • вы используете два вызова функции на символ (один для входа, один для вывода) вызовы функций являются медленными (они загрязняют конвейер команд)
  • fgetc() и fputc имеют свои ЕОКП()/putc(), которые (могут быть реализованы) как макросы, позволяя компилятору встроить полный цикл , за исключением чтения/записи буферов, дважды на 512 или 1024 или 4096 символов. (они будут вызывать системные вызовы, но в любом случае это неизбежно)
  • с использованием чтения/записи вместо буферизованного ввода-вывода, вероятно, не стоит усилий, дополнительная бухгалтерия wil сделает вашу петлю толще (кстати, используя fwrite (), чтобы написать один символ, безусловно, расточительно, то же самое для write())
  • возможно может помочь больший буфер вывода, но я бы не рассчитывал на это.
Смежные вопросы