【问题标题】:fastest way to write integer to file in C在 C 中将整数写入文件的最快方法
【发布时间】:2016-12-18 16:07:04
【问题描述】:

我正在做 C 编程作业。奖励积分用于快速写入上传测试系统中的文件。

我正在尝试编写多行,每行包含三个空格分隔的十进制整数字符串,然后是文件中的 '\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)(最后一个解决方案的时间是 220 毫秒,参考是 140 毫秒,您可以画出时间)?我一直在尝试和谷歌搜索,但没有任何效果。不过时间这么短,总得有办法!

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

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

更多信息:当我发送解决方案时,我只发送两个文件:file.h 和 file.c。没有主要等......所以一切都在他们的优化中。解决方案应该在命令/算法中(即使在问题的描述中也是声明,即 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);
}

【问题讨论】:

  • 显示完整的源代码和您正在使用的编译命令(并提供有关您的操作系统、计算机和编译器的更多详细信息)。
  • 在您的示例中,您不是在编写“整数”,而是在编写整数的十进制字符串表示形式-时间可能很大程度上取决于输出的位数,这个问题没有什么意义;实际写入时间几乎完全取决于硬盘驱动器的速度,但由于在大多数情况下(即在桌面操作系统上),写入将被操作系统写入缓存缓冲和延迟,fprintf() 调用本身应该占用只有几微秒的问题。我认为在您的问题中,您需要准确地包括您正在测量的内容以及如何测量它。
  • 此外,如果写入确实需要 220 毫秒,我敢打赌,如果您写入 30 字节或 1Mb,则时间差别不大 - 您测量的主要是操作系统 / 硬驱动器性能,这可能会受到许多因素的影响,包括正在运行和/或写入驱动器的其他进程。除非您正在编写大量数据并反复测试以获得平均值/最小值/最大值,否则基准测试毫无意义。
  • 啊 - 等等;你说你正在写三个整数,它花了 220 毫秒 - 但更仔细地阅读这个问题,你似乎很清楚你在一个循环中写的可能多于三个整数 - 你写了多少 lines ?您已经被要求发布完整的解决方案,如果您这样做,就会避免很多混乱!
  • 所以您应该仍然发布所有代码 - 即您的解决方案部分的全部内容。你真的没有用你的机智来帮助自己。至少您在 cmets 中给出的说明应该添加到问题中,而不是留在 cmets 中。整个代码是必要的,这样至少我们可以看到您是否负责打开和关闭文件,或者您是否只是获得了一个文件指针 - 这可能会影响可能的解决方案。

标签: c file text-files stdio


【解决方案1】:

使用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.

【讨论】:

  • 这发生在我的脑海中,但我仍然需要将所有数字转换为字符串。无论如何,我无法避免。谢谢,我会试试的。
  • 您仍然可以使用itoa 转换为字符串,只是不要将每个字符串单独写入文件。你宁愿写 20 次文件,还是写一次大文件?
  • 好吧,它不一定是地球上最快的。写几行就可以了。
  • 由于文件 I/O 操作在这个时间点肯定占主导地位,我怀疑格式化 I/O 的开销对于实现所需的性能是否重要。
【解决方案2】:

您可以通过大块写入文件来减少文件 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 字节(或其他 2 的幂)可能有一些优势,但这在很大程度上取决于文件系统,并且执行此操作的代码变得有点更复杂:

#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 次并选择最短时间结果(蜜蜂可以见下面的代码结果):

使用默认的“调试”构建设置(4K 块):

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

使用默认的“发布”构建设置(4K 块):

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

优化对所有三种实现都有类似的影响,固定大小的块写入速度略快于“参差不齐”的块。

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

使用默认的“发布”构建设置(32K 块):

block_write1: 0.036000 seconds
block_write2: 0.036000 seconds

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

使用默认的“发布”构建设置(512 字节块):

block_write1: 0.036000 seconds
block_write2: 0.037000 seconds

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

使用默认的“发布”构建设置(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 ) ;
}

【讨论】:

  • @Stepan :当你尝试过这个时,我很想知道基准测试结果是什么,你使用了什么 CHUNK_SIZE 以及你是否比较了两个版本。
  • 嗯,上传数量有限(所以我不能随便上传)。无论如何,我在任何地方都看不到“line_buffer”,然后您使用了未初始化的“file_buffer”,或者我错过了什么?这些“缓冲区”等对我来说是一种新事物。这就是我发布这个问题的原因(经过深入搜索)。
  • @Stepan : line_buffer 是我最初的想法的宿醉,但改变了。应该是file_buffer - 已修复。 sprinff() 调用不太正确 - 也修复了该问题。 file_buffer 不需要初始化 - 它由 sprintf() 写入。您可以使用可能更有效的atoi(),但随后的字符串处理来确定字符串的结尾可能会更昂贵 - sprintf() 返回写入的字符数,因此您不必扫描字符串的结尾.
  • @Stepan :我已经进行了一些其他更正 - 请确保您拥有我的最新版本。在无法测试的情况下发布代码片段的问题。
  • @Nemo :我添加了测试结果。块写入明显更快(正如我根据经验预期的那样); 32 位代码的速度几乎是 64 位代码的两倍,但差异不是很大,但仍然很显着。我认为“完全错误”可能夸大了它;我会接受“需要验证”,但你至少有权在没有测试结果的情况下查询它。
【解决方案3】:

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

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

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

您还可以将整数显式编码为十进制表示例程(提示:编写一个例程,该例程将int x 像 1234 一样,用相反顺序的数字填充给定的小数组char(例如"4321" ) 非常简单,而且运行速度很快)。

【讨论】:

  • 当我发送解决方案时,我只发送两个文件:file.h 和 file.c。没有主要等......所以一切都在他们的优化中。解决方案应该在命令中(即使在问题的描述中,也是声明,即 fprintf 太慢,我们应该尝试其他方法来加快速度)。
猜你喜欢
  • 1970-01-01
  • 2020-05-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-07
  • 1970-01-01
  • 1970-01-01
  • 2010-11-06
相关资源
最近更新 更多