【问题标题】:Proper, efficient file reading正确、高效的文件读取
【发布时间】:2013-05-04 20:07:09
【问题描述】:

我想一次读取和处理(例如打印)来自 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 执行相同的任务不会降低效率

【问题讨论】:

  • 我不知道 CSV 文件可以在引号内包含非定界逗号。然而,这是一个普遍的问题,我可以很容易地考虑到任何具体的实现。
  • 没那么容易。如果你支持引用的值,你必须支持正确的转义,所以"foo\"bar"被识别为foo"bar
  • 您追求的是效率的抽象概念,还是您实际上已针对效率较低的方法对该方法进行了计时?如果你做了一些计时,结果如何?
  • @EdS。 C++参考问题3。

标签: c++ c file-io


【解决方案1】:

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

话虽如此,您可以尝试一些事情:

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

不过,如果有疑问,请尝试一些可能性,然后进行个人资料、个人资料、个人资料。

同样对于此类问题,请务必注意由操作系统和硬件缓存引起的运行之间的差异:在每次更改后分析一组运行,而不是仅分析一次——如果可能,使用可能的测试总是命中缓存(如果您尝试测量最佳情况下的性能)或可能会错过的测试(如果您尝试测量最坏情况下的性能)。


关于 C++ 文件 IO(fstream 等),请注意它们是更大、更复杂的野兽。它们往往包括诸如语言环境管理、自动缓冲等内容——以及不太容易出现特定类型的编码错误。

如果您正在做一些非常简单的事情(就像您在此处描述的那样),我倾向于发现 C++ 库的东西会妨碍您。 (有时使用调试器和通过 stringstream 方法与一些 C 字符串函数进行“步进指令”,您会很快对此有很好的感觉。)

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


最后,强制性的“不要为小事出汗”。如果它真的很重要,只花时间在这里优化。否则,请相信库和操作系统在大多数情况下会为您做正确的事情——如果您在微优化方面走得太远,您会发现您稍后会自找麻烦。这并不是要阻止您思考“我是否应该提前阅读整个文件,这会破坏未来的用例”——因为那是宏观的,而不是微观的。

但一般来说,如果您没有进行这种“让它更快”调查是有充分理由的 - 即“现在我已经编写了它,需要这个应用程序性能更好,而这段代码显示速度很慢在探查器中”,或者“这样做是为了好玩,以便我可以更好地理解系统”——好吧,先把你的时间花在其他地方。 =)

【讨论】:

    【解决方案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 时发生。

    我在之前使用 fopen 和 fgetc 的 XML 解析器中转移到了这样的方案,并且速度提升很大。加载一个 15 兆的 XML 文件并对其进行处理从几分钟缩短到几秒钟。当然,您的里程可能会有所不同。

    【讨论】:

    • 不错的真实世界证明。 =) 总是喜欢看这些。
    【解决方案3】:

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

    【讨论】:

    • 使用fgets() 阅读后,我必须对字符串进行标记,这将涉及再次循环缓冲区并复制其内容。我的方式只对缓冲区进行一次传递,不需要任何复制。
    猜你喜欢
    • 1970-01-01
    • 2021-07-03
    • 1970-01-01
    • 2017-04-19
    • 2021-01-05
    • 2019-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多