在 C 中将整数写入文件的最快方法

fastest way to write integer to file in C

我正在做 C 语言编程的作业。加分是为了快速写入上传测试系统中的文件。

我正在尝试编写多行,每行包含三个 space 分隔的十进制整数字符串,然后在文件中写入 '\n'。问题是,fprintf 太慢了(他们的参考时间快了或多或少的 1/3)。

我尝试了很多可能性(一切都在一个 for 循环中)。 fprintf(太慢):

fprintf(f, "%d %d %d\n", a[i], b[i], c[i]);

转换为字符串,然后将字符串放入其中 - 更糟糕的是:

sprintf(buffer, "%d", a[i]); //or: _itoa(_itoa(a[i], buffer, 10);
fputs(buffer, f);
fputc(' ', f);

有没有什么快速的方法可以将整数写入简单的文本文件(.txt)(最后一个解决方案时间为220ms,参考为140ms供您查看时间)?我一直在尝试和谷歌搜索,但没有任何效果。不过时间这么短,总得有办法吧!

PS:数字始终为整数,大小为4字节,格式始终为:

a0 b0 c0
a1 b1 c1
a2 b2 c2
a3 b3 c3
etc...

更多信息:当我发送解决方案时,我只发送了两个文件:file.h 和 file.c。没有 main 等......所以一切都在优化中。解决方案应该在 commands/algorithm 中(即使在问题的描述中也是声明,fprintf 太慢了,我们应该尝试其他方法来加快速度)。

谢谢你所做的一切!

编辑:既然你想要完整的代码,这里是:

void save(const str_t * const str, const char *name)
{
  FILE* f;
  int i;

  if(str->cnt == 0)
      return;

  f = fopen(name, "w");
  if(f == NULL)
      return;

  for(i = 0; i < str->cnt; i++)
  {
      fprintf(f, "%d %d %d\n", str->a[i], str->b[i], str->c[i]);
  }
  fclose(f);
}

它可能是特定于操作系统和实现的。

也许您可以使用 setvbuf(3) 显式设置缓冲(我建议至少使用 32Kbyte 缓冲区,可能更多)。

不要忘记明确要求编译器进行优化,例如使用 gcc -Wall -O2

你也可以明确地编码你的整数到十进制表示例程(提示:编写一个例程,其中 ginven 一些 int x 像 1234,用反向数字填充给定的 char 小数组订单(例如 "4321")非常简单,而且 运行 很快。

使用 printf 的任何变体,该函数将必须扫描格式字符串以找到 %d,并解析它以获得任何额外选项(例如 %-03d),并且相应地工作。那是 很多 的处理时间。 printf 很棒,因为它超级灵活,而不是因为它

如果您使用 itoa 类型的函数来写入每个数字,您仍然会将整数转换为字符串,然后将该字符串复制到文件中。您将花费所有处理时间在字符串缓冲区和文件写入之间移动。

我认为最快的方法是在内存中创建一个非常大的缓冲区,将所有内容写入其中,然后执行一次且仅一次写入以将整个缓冲区转储到文件中。

大纲:

char buffer[10000];
for(i = 0; i < str->cnt; i++)
{
    /* write to buffer */
}

fwrite(buffer, buffer_size, 1, my_file);  // One fast write.

您可以减少文件 I/O 的开销,方法是在大块中写入文件以减少单个写入操作的数量。

#define CHUNK_SIZE 4096
char file_buffer[CHUNK_SIZE + 64] ;    // 4Kb buffer, plus enough 
                                       // for at least one one line
int buffer_count = 0 ;
int i = 0 ;

while( i < cnt )
{
    buffer_count += sprintf( &file_buffer[buffer_count], "%d %d %d\n", a[i], b[i], c[i] ) ;
    i++ ;

    // if the chunk is big enough, write it.
    if( buffer_count >= CHUNK_SIZE )
    {
        fwrite( file_buffer, buffer_count, 1, f ) ;
        buffer_count = 0 ;
    }
}

// Write remainder
if( buffer_count > 0 )
{
    fwrite( file_buffer, buffer_count, 1, f ) ;    
}

在单次写入中 正好 4096 字节(或其他一些二次方)可能会有一些优势,但这在很大程度上取决于文件系统和代码这样做会变得有点复杂:

#define CHUNK_SIZE 4096
char file_buffer[CHUNK_SIZE + 64] ;
int buffer_count = 0 ;
int i = 0 ;

while( i < cnt )
{
    buffer_count += sprintf( &file_buffer[buffer_count], "%d %d %d\n", a[i], b[i], c[i] ) ;
    i++ ;

    // if the chunk is big enough, write it.
    if( buffer_count >= CHUNK_SIZE )
    {
        fwrite( file_buffer, CHUNK_SIZE, 1, f ) ;
        buffer_count -= CHUNK_SIZE ;
        memcpy( file_buffer, &file_buffer[CHUNK_SIZE], buffer_count ) ;
    }
}

// Write remainder
if( buffer_count > 0 )
{
    fwrite( file_buffer, 1, buffer_count, f ) ;    
}

您可以尝试 CHUNK_SIZE 的不同值 - 较大的值可能是最佳的,或者您可能会发现它几乎没有什么区别。我建议 至少 512 字节。


测试结果:

使用VC++ 2015,在以下平台上:

配备希捷 ST1000DM003 1TB 64MB 缓存 SATA 6.0Gb/s 硬盘。

单个测试写入 100000 行的结果变化很大,正如您在桌面系统上所期望的那样 运行 多个进程共享同一个硬盘,所以我 运行 每个测试 100 次并选择了最短时间结果(在结果下方的代码中可以看到):

使用默认 "Debug" 构建设置(4K 块):

line_by_line: 0.195000 seconds
block_write1: 0.154000 seconds
block_write2: 0.143000 seconds

使用默认 "Release" 构建设置(4K 块):

line_by_line: 0.067000 seconds
block_write1: 0.037000 seconds
block_write2: 0.036000 seconds

优化对所有三种实现的影响比例相似,固定大小的块写入速度略快于 "ragged" 块。

当使用 32K 块时,性能仅稍高,固定版本和不规则版本之间的差异可以忽略不计:

使用默认 "Release" 构建设置(32K 块):

block_write1: 0.036000 seconds
block_write2: 0.036000 seconds

使用 512 字节块与 4K 块没有明显区别:

使用默认 "Release" 构建设置(512 字节块):

block_write1: 0.036000 seconds
block_write2: 0.037000 seconds

以上均为 32 位 (x86) 版本。构建 64 位代码 (x64) 产生了有趣的结果:

使用默认 "Release" 构建设置(4K 块)- 64 位代码:

line_by_line: 0.049000 seconds
block_write1: 0.038000 seconds
block_write2: 0.032000 seconds

参差不齐的块稍微慢一点(尽管可能在统计上不显着),固定块明显快于逐行写入(但不足以使其比任何块写入更快)。

测试代码(4K块版):

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


void line_by_line_write( int count )
{
  FILE* f = fopen("line_by_line_write.txt", "w");
  for( int i = 0; i < count; i++)
  {
      fprintf(f, "%d %d %d\n", 1234, 5678, 9012 ) ;
  }
  fclose(f);       
}

#define CHUNK_SIZE (4096)

void block_write1( int count )
{
  FILE* f = fopen("block_write1.txt", "w");
  char file_buffer[CHUNK_SIZE + 64];
  int buffer_count = 0;
  int i = 0;

  while( i < count )
  {
      buffer_count += sprintf( &file_buffer[buffer_count], "%d %d %d\n", 1234, 5678, 9012 );
      i++;

      // if the chunk is big enough, write it.
      if( buffer_count >= CHUNK_SIZE )
      {
          fwrite( file_buffer, buffer_count, 1, f );
          buffer_count = 0 ;
      }
  }

  // Write remainder
  if( buffer_count > 0 )
  {
      fwrite( file_buffer, 1, buffer_count, f );
  }
  fclose(f);       

}

void block_write2( int count )
{
  FILE* f = fopen("block_write2.txt", "w");
  char file_buffer[CHUNK_SIZE + 64];
  int buffer_count = 0;
  int i = 0;

  while( i < count )
  {
      buffer_count += sprintf( &file_buffer[buffer_count], "%d %d %d\n", 1234, 5678, 9012 );
      i++;

      // if the chunk is big enough, write it.
      if( buffer_count >= CHUNK_SIZE )
      {
          fwrite( file_buffer, CHUNK_SIZE, 1, f );
          buffer_count -= CHUNK_SIZE;
          memcpy( file_buffer, &file_buffer[CHUNK_SIZE], buffer_count );
      }
  }

  // Write remainder
  if( buffer_count > 0 )
  {
      fwrite( file_buffer, 1, buffer_count, f );
  }
  fclose(f);       

}

#define LINES 100000

int main( void )
{
    clock_t line_by_line_write_minimum = 9999 ;
    clock_t block_write1_minimum = 9999 ;
    clock_t block_write2_minimum = 9999 ;

    for( int i = 0; i < 100; i++ )
    {
        clock_t start = clock() ;
        line_by_line_write( LINES ) ;
        clock_t t = clock() - start ;
        if( t < line_by_line_write_minimum ) line_by_line_write_minimum = t ;

        start = clock() ;
        block_write1( LINES ) ;
        t = clock() - start ;
        if( t < block_write1_minimum ) block_write1_minimum = t ;

        start = clock() ;
        block_write2( LINES ) ;
        t = clock() - start ;
        if( t < block_write2_minimum ) block_write2_minimum = t ;
    }

    printf( "line_by_line: %f seconds\n", (float)(line_by_line_write_minimum) / CLOCKS_PER_SEC ) ;
    printf( "block_write1: %f seconds\n", (float)(block_write1_minimum) / CLOCKS_PER_SEC ) ;
    printf( "block_write2: %f seconds\n", (float)(block_write2_minimum) / CLOCKS_PER_SEC ) ;
}