正确,高效的文件读取

时间:2013-05-04 20:07:10

标签: c++ c file-io

我想一次一个地从CSV文件的第一行读取和处理(例如打印)条目。我假设Unix风格的\n换行符,没有条目超过255个字符,并且(现在)在EOF之前有一个换行符。这是fgets()后跟strtok()的更有效的替代方法。

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

int main() {
    int i;
    char ch, buf[256];
    FILE *fp = fopen("test.csv", "r");

    for (;;) {
        for (i = 0; ; i++) {
            ch = fgetc(fp);
            if (ch == ',') {
                buf[i] = '\0'; 
                puts(buf);
                break;
            } else if (ch == '\n') {
                buf[i] = '\0'; 
                puts(buf);
                fclose(fp);
                return 0;
            } else buf[i] = ch;
        }
    }
}
  1. 这种方法是否尽可能高效和正确?
  2. 使用此方法测试EOF和文件读取错误的最佳方法是什么? (可能性:针对角色宏EOFfeof()ferror()等进行测试。
  3. 我可以使用C ++文件I / O 执行相同的任务而不会降低效率吗?

3 个答案:

答案 0 :(得分:5)

最有效的方法很大程度上取决于操作系统,标准库(例如libc),甚至是您运行的硬件。这使得几乎不可能告诉你什么是“最有效的”。

有人说过,你可以尝试一些事情:

  • 使用mmap()或等效的本地操作系统(Windows有CreateFileMapping / OpenFileMapping / MapViewOfFile,可能还有其他操作系统)。然后你不进行显式的文件读取:你只需要访问该文件就好像它已经在内存中一样,并且任何不存在的文件都会被页面错误机制搞砸。
  • 手动将整个文件读入缓冲区,然后处理该缓冲区。调用文件读取函数的次数越少,您执行的函数调用开销就越少,并且可能还会减少应用程序/操作系统域切换。显然这会占用更多内存,但可能非常值得。
  • 为您的问题和平台使用更优化的字符串扫描程序。逐个字符地进行自己几乎从来没有像依靠现有的与您的问题域接近的东西一样快。例如,您可以打赌,strchrmemchr可能比您可以自己动手的大多数代码更优化,执行诸如一次读取整个缓存行或单词之类的操作,使用更好的算法扫描这种类型的对于更复杂的情况,你可能会考虑一个完整的正则表达式引擎,它可以将你的正则表达式快速编译成复杂的情况。
  • 避免复制你的字符串。根据“查找分隔符”然后“分隔符之间的输出”来思考可能会有所帮助。例如,您可以使用strchr查找下一个感兴趣的字符,然后使用fwrite或其他东西直接从输入缓冲区写入stdout。然后,您将大部分工作保留在几个本地寄存器中,而不是使用堆栈或堆buf

但是,如果有疑问,请尝试一些可能性和个人资料,个人资料,个人资料。

同样对于这类问题,要非常了解由操作系统和硬件缓存引起的运行之间的差异:在每次更改后分析一堆运行而不是仅运行一次 - 如果可能的话,使用可能的测试总是点击缓存(如果你试图测量最佳情况的性能)或可能会错过的测试(如果你试图测量最坏情况的性能)。


关于C ++文件IO(fstream等),请注意它们是更大,更复杂的野兽。它们往往包括诸如区域设置管理,自动缓冲等之类的东西 - 以及不太容易出现特定类型的编码错误。

如果你正在做一些非常简单的事情(就像你在这里描述的那样),我倾向于发现C ++库的东西会受到妨碍。 (通过字符串流方法使用调试器和“步骤指令”,而不是某些C字符串函数,你很快就会对此感觉良好。)

这完全取决于您将来是否想要或需要额外的功能或安全性。


最后,强制性的“不要让小东西流汗”。如果真的很重要的话,只花时间在这里进行优化。否则,请相信图书馆和操作系统会在大多数时间为您做正确的事情 - 如果您对微观优化过于苛刻,您会发现自己在以后拍摄自己。这并不是为了阻止你思考“我应该提前阅读整个文件,是否会破坏未来的用例” - 因为这是宏观,而不是微观。

但总的来说,如果你没有做出这种“快速进行”调查的原因有充分的理由 - 即“现在我已经写好了,需要这个应用程序才能表现得更好”,这段代码表现得很慢在探查器“,或”这样做是为了好玩,所以我可以更好地理解系统“ - 好吧,先把时间花在别的地方。 =)

答案 1 :(得分:2)

如果要连续扫描文件,一种方法是使用2个足够大的缓冲区(16K是SSD的最佳大小,HDD是硬盘IIRC的4K。但16K应该足够了)。首先执行异步加载(在Windows中查找Overlapped I/O并在Unix / OSX上使用O_NONBLOCK)将第一个16K放入缓冲区0,然后再启动另一个加载到缓冲区1中的16K到32K字节。当您的读取位置达到16K时,交换缓冲区(因此您现在正在从缓冲区1读取)等待任何进一步的加载完成到缓冲区1中并执行32K到48K字节的异步加载到缓冲区0中,依此类推。通过这种方式,您在处理前一个16K时,不必等待负载完成的可能性要小得多。

我在我的XML解析器中转移到这样的方案之前一直在使用fopen和fgetc,并且加速很大。加载15兆字节的XML文件并将其处理从几分钟缩短到几秒钟。当然,你的milage可能会有所不同。

答案 2 :(得分:0)

使用fgets一次读取一行。 C ++文件I / O基本上是包装代码,其中包含一些编译器优化(以及许多不需要的功能)。除非您正在阅读数百万行代码并测量时间,否则无关紧要。