【问题标题】:Faster I/O in CC 中更快的 I/O
【发布时间】:2010-10-16 20:37:33
【问题描述】:

我有一个问题,需要从控制台输入 1000000 行,如下所示。

0 1 23 4 5
1 3 5 2 56
12 2 3 33 5
...
...

我用过scanf,但是很慢。有没有办法以更快的方式从控制台获取输入?我可以使用 read(),但我不确定每行中的字节数,所以我不能像 read() 那样读取“n”个字节。 谢谢, 非常感谢

【问题讨论】:

  • 你试过 readline 吗?

标签: c optimization io


【解决方案1】:

您使用多个具有固定大小缓冲区的reads,直到到达文件末尾。

【讨论】:

    【解决方案2】:

    出于好奇,是什么在控制台中生成这么多行?

    【讨论】:

    • % a.out
    • 如果这是某个程序的输出到标准输出,重定向到文件并从文件加载会快得多。控制台渲染是一个缓慢的过程
    【解决方案3】:

    一次读取一行(如果缓冲区不足以容纳一行,则扩展并继续使用更大的缓冲区)。

    然后使用专用函数(例如 atoi)而不是通用函数进行转换。

    但是,最重要的是,设置一个可重复的带有分析的测试工具,以确保更改确实可以加快速度。

    【讨论】:

      【解决方案4】:

      使用 fgets(...) 一次拉入一行。请注意,您应该检查行尾的 '\n',如果没有,则说明您处于 EOF,或者您需要读取另一个缓冲区的值,并将两者连接在一起。起泡,冲洗,重复。不要陷入缓冲区溢出。

      那么,您可以自己解析内存中的每个逻辑行。我喜欢使用 strspn(...) 和 strcspn(...) 来处理这类事情,但你的里程可能会有所不同。

      解析: 定义分隔符字符串。使用 strspn() 计算与分隔符匹配的“非数据”字符,并跳过它们。使用 strcspn() 计算与分隔符不匹配的“数据”字符。如果此计数为 0,则您已完成(行中没有更多数据)。否则,复制这 N 个字符以交给解析函数,例如 atoi(...) 或 sscanf(...)。然后,将您的指针基重置到该块的末尾并重复跳过分隔符、复制数据、转换为数字的过程。

      【讨论】:

        【解决方案5】:

        如果您的示例具有代表性,即您确实有每行五个十进制数字的固定格式,我可能会使用fgets() 的组合来读取行,然后调用strtol() 的循环从字符串转换为整数。

        这应该比scanf() 更快,同时仍然比自己进行字符串到整数的转换更清晰、更高级。

        类似这样的:

        typedef struct {
          int number[5];
        } LineOfNumbers;
        
        int getNumbers(FILE *in, LineOfNumbers *line)
        {
          char buf[128];  /* Should be large enough. */
          if(fgets(buf, sizeof buf, in) != NULL)
          {
            int i;
            char *ptr, *eptr;
        
            ptr = buf;
            for(i = 0; i < sizeof line->number / sizeof *line->number; i++)
            {
              line->number[i] = (int) strtol(ptr, &eptr, 10);
              if(eptr == ptr)
                return 0;
              ptr = eptr;
            }
            return 1;
          }
          return 0;
        }
        

        注意:这是未经测试(甚至未经编译!)浏览器编写的代码。但作为一个具体的例子也许有用。

        【讨论】:

        • 我喜欢 "char buf[128]; /* 应该足够大。*/" 很好的做法。
        • @Chris:嗯……不知道你是不是在讽刺。这是示例代码,基于一个非常模糊的规范,并且 fgets() 也使用 sizeof,所以这里应该没有溢出的风险。或者也许我只是过于偏执了。
        • 我在讽刺。我倾向于避免固定长度的行读取,即使它可以正常工作,只是因为我通常喜欢将整行一次放在一个变量中(我可能从 Perl 继承了这种味道)。但是这段代码很好。我只是觉得看到这样的 cmets 很有趣,即使它们是真的。
        【解决方案6】:

        如果可以,请使用二进制 I/O。文本转换会使阅读速度减慢几倍。如果您因为易于调试而使用文本 I/O,请再次考虑二进制格式,并在需要时使用 od 程序(假设您使用的是 unix)使其易于阅读。

        哦,还有一件事:有 AT&T 的 SFIO 库,它代表更安全/更快的文件 IO。你可能也有一些运气,但我怀疑你会得到与二进制格式相同的加速。

        【讨论】:

        • 使用二进制文件的 Unix 程序不是真正的 Unix 程序,或者类似的东西 :-) 在实际情况下支持人类可读的数据——或者——老板/老师说这就是我们得到的!
        • ++ 如果您有选择,二进制是最快的。如果列表中有非整数,二进制也是最准确的。
        【解决方案7】:

        如果您尝试读取的字节数多于实际字节数,fread 仍会返回。

        我发现最快的文件读取方法是这样的:

        /*寻找文件末尾*/ fseek(file,0,SEEK_END);

        /*获取文件大小*/ 大小 = ftell(文件);

        /*寻找文件开头*/ fseek(file,0,SEEK_SET);

        /* 为文件创建一个缓冲区 */ 缓冲区 = malloc(1048576);

        /*一次读取 1MB 直到达到 size 字节等 */

        在现代计算机上,使用您的 ram 并将整个内容加载到 ram,然后您可以轻松地通过内存工作。

        至少您应该使用尽可能大的块大小的 fread,并且至少与缓存块或 HDD 扇区大小一样大(最小 4096 字节,我个人至少会使用 1048576)。您会发现,对于更大的读取请求,rfead 能够在一次操作中按顺序获得一个大流。一些人在这里使用 128 字节的建议是可笑的......因为你最终将不得不一直寻找驱动器,因为调用之间的微小延迟将导致磁头已经超过几乎可以肯定的下一个扇区有你想要的顺序数据。

        【讨论】:

        • 你可以 malloc() 缓冲区大到足以一举容纳所有size,但是对于一个变量来说这是很多内存,我知道要避免这种情况。
        • Seek-to-EOF 在管道上不起作用。 stdin 通常只是一个重定向的管道,而不是一个实际的文件。我假设“控制台”是指标准输入。据我所知,这听起来有点像 Windows 语言的延续。
        • 说到Windoze:这个解决方案有点像内存映射文件。
        • 如果您的操作系统中有现代内核,例如 Linux,请不要复制。内核将使用您拥有的所有 RAM 进行缓存。如果您复制到 RAM(分配缓冲区),您将在 RAM 中有两个副本,除非您不会,因为您将用完 RAM,缓存将被刷新,分配的内存将被交换。
        【解决方案8】:

        您可以通过使用fread()fread_unlocked()(如果您的程序是单线程的)获取输入来大大减少执行时间。锁定/解锁一次输入流所花费的时间可以忽略不计,因此请忽略它。

        代码如下:

        #include <iostream>
        
        int maxio=1000000;
        char buf[maxio], *s = buf + maxio;
        
        inline char getc1(void)
        {
           if(s >= buf + maxio) { fread_unlocked(buf,sizeof(char),maxio,stdin); s = buf; }
           return *(s++);
        }
        inline int input()
        {
           char t = getc1();
           int n=1,res=0;
           while(t!='-' && !isdigit(t)) t=getc1(); if(t=='-')
           {
              n=-1; t=getc1();
           }
           while(isdigit(t))
           {
             res = 10*res + (t&15);
             t=getc1();
           }
           return res*n;
        }
        

        这是在C++ 中实现的。在C 中,您不需要包含iostream,函数isdigit() 是隐式可用的。

        您可以通过调用getc1() 将输入作为字符流,并通过调用input() 将整数输入。

        使用fread() 背后的整个想法是一次获取所有输入。调用scanf()/printf(),反复占用宝贵的时间来锁定和解锁流,这在单线程程序中完全是多余的。

        还要确保maxio 的值使得所有输入只能在几次“往返”中获取(在这种情况下最好是一次)。根据需要进行调整。

        希望这会有所帮助!

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-05-24
          • 2017-03-26
          • 2021-08-17
          • 2014-07-25
          • 2023-03-10
          • 2011-04-14
          • 2017-09-20
          • 2013-09-22
          相关资源
          最近更新 更多