目前我正在使用fread()函数读取文件,但是有人告诉我在其他语言中fread()函数效率很低。在C语言中是否也是如此?如果是,那么如何更快地读取文件?
目前我正在使用fread()函数读取文件,但是有人告诉我在其他语言中fread()函数效率很低。在C语言中是否也是如此?如果是,那么如何更快地读取文件?
这并不重要。
如果你正在从实际硬盘中读取,速度会很慢。硬盘是瓶颈,就是这样。
现在,如果你对于调用read/fread/等函数有些草率,比如每次只读取一个字节,那么速度会很慢,因为fread()的开销将超过从磁盘中读取的开销。
如果你调用read/fread/等函数并请求一定数量的数据,这将取决于你所做的事情:有时候你只需要4个字节(获取uint32),但有时你可以读取大块数据(4 KiB、64 KiB等等。内存很便宜,选择一些重要的东西吧)。
如果你正在进行小型读取,一些高级别的调用,如fread(),将通过在后台缓冲数据来帮助你。如果你正在进行大型读取,它可能没有什么帮助,但是从fread切换到read可能不会产生太多改进,因为你的瓶颈在于磁盘速度。
简而言之:如果可以的话,在读取时请求充足的数据,并尽量减少你写入的内容。对于大量数据,2的幂值通常比其他任何值更友好,但当然,这取决于操作系统、硬件和环境。
所以,让我们看看这是否会带来任何不同:
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#define BUFFER_SIZE (1 * 1024 * 1024)
#define ITERATIONS (10 * 1024)
double now()
{
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec + tv.tv_usec / 1000000.;
}
int main()
{
unsigned char buffer[BUFFER_SIZE]; // 1 MiB buffer
double end_time;
double total_time;
int i, x, y;
double start_time = now();
#ifdef USE_FREAD
FILE *fp;
fp = fopen("/dev/zero", "rb");
for(i = 0; i < ITERATIONS; ++i)
{
fread(buffer, BUFFER_SIZE, 1, fp);
for(x = 0; x < BUFFER_SIZE; x += 1024)
{
y += buffer[x];
}
}
fclose(fp);
#elif USE_MMAP
unsigned char *mmdata;
int fd = open("/dev/zero", O_RDONLY);
for(i = 0; i < ITERATIONS; ++i)
{
mmdata = mmap(NULL, BUFFER_SIZE, PROT_READ, MAP_PRIVATE, fd, i * BUFFER_SIZE);
// But if we don't touch it, it won't be read...
// I happen to know I have 4 KiB pages, YMMV
for(x = 0; x < BUFFER_SIZE; x += 1024)
{
y += mmdata[x];
}
munmap(mmdata, BUFFER_SIZE);
}
close(fd);
#else
int fd;
fd = open("/dev/zero", O_RDONLY);
for(i = 0; i < ITERATIONS; ++i)
{
read(fd, buffer, BUFFER_SIZE);
for(x = 0; x < BUFFER_SIZE; x += 1024)
{
y += buffer[x];
}
}
close(fd);
#endif
end_time = now();
total_time = end_time - start_time;
printf("It took %f seconds to read 10 GiB. That's %f MiB/s.\n", total_time, ITERATIONS / total_time);
return 0;
}
...产生:
$ gcc -o reading reading.c
$ ./reading ; ./reading ; ./reading
It took 1.141995 seconds to read 10 GiB. That's 8966.764671 MiB/s.
It took 1.131412 seconds to read 10 GiB. That's 9050.637376 MiB/s.
It took 1.132440 seconds to read 10 GiB. That's 9042.420953 MiB/s.
$ gcc -o reading reading.c -DUSE_FREAD
$ ./reading ; ./reading ; ./reading
It took 1.134837 seconds to read 10 GiB. That's 9023.322991 MiB/s.
It took 1.128971 seconds to read 10 GiB. That's 9070.207522 MiB/s.
It took 1.136845 seconds to read 10 GiB. That's 9007.383586 MiB/s.
$ gcc -o reading reading.c -DUSE_MMAP
$ ./reading ; ./reading ; ./reading
It took 2.037207 seconds to read 10 GiB. That's 5026.489386 MiB/s.
It took 2.037060 seconds to read 10 GiB. That's 5026.852369 MiB/s.
It took 2.031698 seconds to read 10 GiB. That's 5040.119180 MiB/s.
...或者没有明显的区别。(有时fread会胜出,有时是read)
注意:慢的mmap
令人惊讶。这可能是因为我要求它为我分配缓冲区。(我不确定是否需要提供指针...)
简而言之:不要过早优化。先让它运行起来,再让它正确,最后才让它快速,按照这个顺序。
应受欢迎的请求,我在真实文件上运行了测试。(Ubuntu 10.04 32位桌面安装CD ISO的前675 MiB)以下是结果:
# Using fread()
It took 31.363983 seconds to read 675 MiB. That's 21.521501 MiB/s.
It took 31.486195 seconds to read 675 MiB. That's 21.437967 MiB/s.
It took 31.509051 seconds to read 675 MiB. That's 21.422416 MiB/s.
It took 31.853389 seconds to read 675 MiB. That's 21.190838 MiB/s.
# Using read()
It took 33.052984 seconds to read 675 MiB. That's 20.421757 MiB/s.
It took 31.319416 seconds to read 675 MiB. That's 21.552126 MiB/s.
It took 39.453453 seconds to read 675 MiB. That's 17.108769 MiB/s.
It took 32.619912 seconds to read 675 MiB. That's 20.692882 MiB/s.
# Using mmap()
It took 31.897643 seconds to read 675 MiB. That's 21.161438 MiB/s.
It took 36.753138 seconds to read 675 MiB. That's 18.365779 MiB/s.
It took 36.175385 seconds to read 675 MiB. That's 18.659097 MiB/s.
It took 31.841998 seconds to read 675 MiB. That's 21.198419 MiB/s.
经过一位非常无聊的程序员的努力,我们成功从磁盘中读取了CD ISO文件,共进行了12次测试。在每次测试前,都清空了磁盘缓存,并且在每次测试期间,有足够的、大致相同数量的可用内存,可以将CD ISO文件存储两次。
值得注意的一点是,我最初使用了大型的malloc()函数来填充内存,以此来减少磁盘缓存的影响。可能值得一提的是,mmap表现十分糟糕。另外两种方法只是简单地运行,而mmap运行时,由于某些原因我无法解释,开始将内存推到swap区,导致性能下降。(据我所知(源代码在上面),程序没有泄漏,实际的“已使用内存”在试验过程中保持不变。)
总体上,read()方法的速度最快,fread()方法的时间非常稳定。然而,在测试期间可能会出现一些小问题。所有这三种方法基本上都是相等的。(特别是fread和read方法……)
如果你想超越C规范,使用操作系统特定的代码,那么内存映射通常被认为是最有效的方法。
对于Posix,请查看 mmap
,对于Windows,请查看 OpenFileMapping
。
你遇到了什么问题?
如果你需要最快的文件读取速度(同时还要与操作系统兼容),直接使用操作系统调用,并确保你学会了如何最有效地使用它们。
如果寻道时间成为问题,请重新安排磁盘上的数据(如果可以),并将其存储在较大的、预处理的文件中,而不是从这里和那里加载小块。
如果数据传输时间成为问题,也许可以考虑压缩数据。
fread
很慢,那是因为它额外增加了一些层到底层操作系统机制的读取文件,并干扰你特定程序使用 fread
的方式。换句话说,它很慢是因为你没有按照它被优化的方式使用。虽然不是最快的,但它表现相当好且简短。
#include <fcntl.h>
#include <unistd.h>
int main() {
int f = open("file1", O_RDWR);
char buffer[4096];
while ( read(f, buffer, 4096) > 0 ) {
printf("%s", buffer);
}
}
也许可以看看 Perl 是如何做的。Perl 的 I/O 例程是经过优化的,我了解到这也是使用 Perl 过滤器处理文本比使用 sed
进行相同转换快两倍的原因。
显然,Perl 相当复杂,而 I/O 只是它所做的一小部分。我从未查看过它的源代码,所以除了指向 这里,我无法给出更好的指导。
你可以尝试另一种方法(取决于你的数据可移植性),即压缩和/或转换为二进制格式,如数据库格式(例如BDB、SQL等)。某些数据库格式可以使用字节序转换函数在不同机器之间进行移植。
通常最好采用一组算法和方法,使用不同的方法运行性能测试,并评估最适合你的应用程序的平均任务的最佳算法。这将帮助你确定最佳执行算法。