【问题标题】:Usage of getc with a file对文件使用 getc
【发布时间】:2021-02-16 05:36:32
【问题描述】:

要打印文件的内容,可以使用getc

int ch;
FILE *file = fopen("file.txt", "r");
while ((ch = getc(file)) != EOF) {
    // do something
}

getc 函数的效率如何?也就是说,它实际上多久执行一次操作系统调用或需要花费大量时间的事情?例如,假设我有一个 10TB 的文件——调用这个函数数万亿次是不是获取数据的糟糕方法?

【问题讨论】:

  • 标准 I/O 缓冲的目的是使一次字符的 I/O 变得可行。除非您采取其他措施(默认情况下,换句话说),否则 I/O 库将读取相当大的数据块(例如 macOS 上的 1 KiB),然后一次分配一个字符有效率的。在现代多线程系统上,每次使用getc() 都会产生一些锁定和解锁开销(名义上至少是这样)——请参阅lockfile(), unlockfile(), trylockfile()[...继续...]
  • […continuation…] 如果你的代码是严格的单线程的,你可以使用getc_unlocked() et al 来避免锁定开销。如果您的代码可以处理更大的块(使用fread() 等),则读取从 8 KiB 到 64 KiB 的块可能会带来一些性能优势;您的代码将处理对每个字符的访问。较大的缓冲区大小通常带来的好处可以忽略不计,但您可以衡量一下哪种方法最适合您。
  • getc(), fgetc() 有一个由 I/O 系统填充的读取缓冲区。在 Linux 上,大小为 8192 字节,在 Windows 上为 512 字节。因此,无论您使用具有 8K 或更少缓冲区的fgets(),还是使用getc(),基本上都是一种清洗。不会有可测量的差异。用于设置低级缓冲区大小的宏已经更改了几次名称,最近的是BUFSIZ。进行更改的提交是 glibc/libio/stdio.h - #define BUFSIZ 8192
  • @David542:你们正在开发什么样的应用程序?是否需要比wc(1)cat(1) 在同一个输入文件上操作更快地编写一些代码。
  • @David542 — 深夜记忆失效,添加评论后无法检查。抱歉(感谢您指出我的错误)。函数名称实际上以f 开头,因此正确的链接(三个函数一页)是flockfile(), funlockfile(), ftrylockfile()。 (是的,这次我在点击“提交”后检查了 URL。)

标签: c linux performance system-calls


【解决方案1】:

也就是说,它实际上多久执行一次操作系统调用或需要花费大量时间的事情?

您可以查看GNU libcmusl-libc的源代码来研究getc的实现。您还应该研究cat(1)wc(1) 的实现。两者都是open source。而 GNU asGNU binutils 的一部分)是一个 free software(大多数编译内部使用 GCC),它在实践中运行得非常快并且进行文本操作(将汇编器文本输入转换为二进制目标文件)。 您可以从它的源代码中获得灵感

您可以使用 setvbuf(3) 更改缓冲区大小

您可能希望使用fread(3)fgets(3) 一次读取几个字节,可能是几千字节的数据片段

您还可以使用调试器gdb(1)strace(1) 实用程序来了解何时使用syscalls(2) 以及使用哪些。

例如,假设我有一个 10TB 的文件——调用这个函数数万亿次是不是获取数据的糟糕方式?

很可能不是,因为内核的page cache

您应该对您的程序进行分析和基准测试,以找出它的瓶颈。

大多数时候不会是getc。请参阅 time(7)gprof(1)(并使用 GCC 作为 gcc -O3 -pg -Wall 调用来编译所有代码)

如果原始输入性能在您的程序中至关重要,请考虑直接并明智地使用open(2)read(2)mmap(2)madvise(2)readahead(2)posix_fadvise(2)close(2)。大多数这些系统调用都可能失败,请参阅errno(3)

您还可以更改文件系统(例如,从 Ext4 更改为 XFS,请参阅 ext4(5)xfs(5)),购买更好的 SSD 磁盘或更多物理 RAM,或使用 mount(2) 选项来提高性能。

另见/proc伪文件系统(所以proc(5)...);和this 回答。

您可能希望使用 sqlitePostGreSQL 等数据库

您的程序可以在运行时生成 C 代码(就像 manydl.c 一样),尝试各种方法(使用 @ 将生成的 C 代码 /tmp/generated-c-1234.c 编译为插件987654382@,然后是dlopen(3)-ing 和dlsym(3)-ing 那个/tmp/generated-plugin-1234.so 生成的插件),并使用machine learning 的技术找到一个很好的(具体到目前的硬件和电脑)。它还可以使用asmjitlibgccjit 更直接地生成机器代码,尝试几种方法,并针对特定情况选择最佳的一种。 Pitrat 的书Artificial Beings 和博客(仍然是here)更详细地解释了这种方法。 概念框架称为partial evaluation。另见this

您也可以使用现有的解析器生成器,例如GNU bisonANTLR。他们正在生成 C 代码。

Ian Taylor 的 libbacktrace 在这种动态元编程方法中也很有用(生成各种形式的 C 代码,并根据使用 dladdr(3) 检查的调用堆栈选择最佳代码)。

很可能您的问题是parsing 问题。所以请阅读Dragon book的前半部分。

在尝试任何实验之前,与您的经理/老板/客户讨论是否有机会花费数月的全职工作来获得百分之几的绩效。考虑到通过升级硬件可以获得相同的增益。

如果您的 TB 输入文本文件不经常更改(例如每周给出一次,例如在 bioinformatics 软件中),则可能值得对其进行预处理并将其以批处理模式转换为二进制文件,或一些sqlite 数据库,或者一些GDBM 索引文件,或者一些REDIS 的东西。然后记录该二进制文件或数据库的格式(使用EBNF 表示法,从elf(5) 汲取灵感)非常重要。

【讨论】:

    猜你喜欢
    • 2016-09-09
    • 1970-01-01
    • 2020-09-21
    • 2013-07-13
    • 1970-01-01
    • 2018-07-06
    • 2018-07-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多