http://www.byvoid.com/blog/fast-readfile/
http://blog.csdn.net/jifengszf/article/details/3886802
在竞赛中,遇到大数据时,往往读文件成了程序运行速度的瓶颈,需要更快的读取方式。相信几乎所有的C++学习者都在cin机器缓慢的速度上栽过跟头,于是从此以后发誓不用cin读数据。还有人说Pascal的read语句的速度是C/C++中scanf比不上的,C++选手只能干着急。难道C++真的低Pascal一等吗?答案是不言而喻的。一个进阶的方法是把数据一下子读进来,然后再转化字符串,这种方法传说中很不错,但具体如何从没试过,因此今天就索性把能想到的所有的读数据的方式都测试了一边,结果是惊人的。
竞赛中读数据的情况最多的莫过于读一大堆整数了,于是我写了一个程序,生成一千万个随机数到data.txt中,一共55MB。然后我写了个程序主干计算运行时间,代码如下:
1 2 3 4 5 6 7 | |
最简单的方法就算写一个循环scanf了,代码如下:
1 2 3 4 5 6 7 8 9 10 | |
可是效率如何呢?在我的电脑Linux平台上测试结果为2.01秒。接下来是cin,代码如下
1 2 3 4 5 6 7 8 9 10 | |
出乎我的意料,cin仅仅用了6.38秒,比我想象的要快。cin慢是有原因的,其实默认的时候,cin与stdin总是保持同步的,也就是说这两种方法可以混用,而不必担心文件指针混乱,同时cout和stdout也一样,两者混用不会输出顺序错乱。正因为这个兼容性的特性,导致cin有许多额外的开销,如何禁用这个特性呢?只需一个语句std::ios::sync_with_stdio(false);,这样就可以取消cin于stdin的同步了。程序如下:
1 2 3 4 5 6 7 8 9 10 11 | |
取消同步后效率究竟如何?经测试运行时间锐减到了2.05秒,与scanf效率相差无几了!有了这个以后可以放心使用cin和cout了。
接下来让我们测试一下读入整个文件再处理的方法,首先要写一个字符串转化为数组的函数,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
把整个文件读入一个字符串最常用的方法是用fread,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
上述代码有着惊人的效率,经测试读取这10000000个数只用了0.29秒,效率提高了几乎10倍!掌握着种方法简直无敌了,不过,我记得fread是封装过的read,如果直接使用read,是不是更快呢?代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
测试发现运行时间仍然是0.29秒,可见read不具备特殊的优势。到此已经结束了吗?不,我可以调用Linux的底层函数mmap,这个函数的功能是将文件映射到内存,是所有读文件方法都要封装的基础方法,直接使用mmap会怎样呢?代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 | |
经测试,运行时间缩短到了0.25秒,效率继续提高了14%。到此为止我已经没有更好的方法继续提高读文件的速度了。回头测一下Pascal的速度如何?结果令人大跌眼镜,居然运行了2.16秒之多。程序如下:
1 2 3 4 5 6 7 8 9 10 11 | const MAXN = 10000000; var numbers :array[0..MAXN] of longint; i :longint; begin assign(input,'data.txt'); reset(input); for i:=0 to MAXN do read(numbers[i]); end. |
为确保准确性,我又换到Windows平台上测试了一下。结果如下表:
方法/平台/时间(秒) | Linux gcc | Windows mingw | Windows VC2008 |
scanf | 2.010 | 3.704 | 3.425 |
cin | 6.380 | 64.003 | 19.208 |
cin取消同步 | 2.050 | 6.004 | 19.616 |
fread | 0.290 | 0.241 | 0.304 |
read | 0.290 | 0.398 | 不支持 |
mmap | 0.250 | 不支持 | 不支持 |
Pascal read | 2.160 | 4.668 |
从上面可以看出几个问题
- Linux平台上运行程序普遍比Windows上快。
- Windows下VC编译的程序一般运行比MINGW(MINimal Gcc for Windows)快。
- VC对cin取消同步与否不敏感,前后效率相同。反过来MINGW则非常敏感,前后效率相差8倍。
- read本是linux系统函数,MINGW可能采用了某种模拟方式,read比fread更慢。
- Pascal程序运行速度实在令人不敢恭维
在竞赛中,遇到大数据时,往往读文件成了程序运行速度的瓶颈,需要更快的读取方式。相信几乎所有的C++学习者都在cin机器缓慢的速度上栽过跟头,于是从此以后发誓不用cin读数据。还有人说Pascal的read语句的速度是C/C++中scanf比不上的,C++选手只能干着急。难道C++真的低Pascal一等吗?答案是不言而喻的。一个进阶的方法是把数据一下子读进来,然后再转化字符串,这种方法传说中很不错,但具体如何从没试过,因此今天就索性把能想到的所有的读数据的方式都测试了一边,结果是惊人的。
竞赛中读数据的情况最多的莫过于读一大堆整数了,于是我写了一个程序,生成一千万个随机数到data.txt中,一共55MB。然后我写了个程序主干计算运行时间,代码如下:
1 2 3 4 5 6 7 | |
最简单的方法就算写一个循环scanf了,代码如下:
1 2 3 4 5 6 7 8 9 10 | |
可是效率如何呢?在我的电脑Linux平台上测试结果为2.01秒。接下来是cin,代码如下
1 2 3 4 5 6 7 8 9 10 | |
出乎我的意料,cin仅仅用了6.38秒,比我想象的要快。cin慢是有原因的,其实默认的时候,cin与stdin总是保持同步的,也就是说这两种方法可以混用,而不必担心文件指针混乱,同时cout和stdout也一样,两者混用不会输出顺序错乱。正因为这个兼容性的特性,导致cin有许多额外的开销,如何禁用这个特性呢?只需一个语句std::ios::sync_with_stdio(false);,这样就可以取消cin于stdin的同步了。程序如下:
1 2 3 4 5 6 7 8 9 10 11 | |
取消同步后效率究竟如何?经测试运行时间锐减到了2.05秒,与scanf效率相差无几了!有了这个以后可以放心使用cin和cout了。
接下来让我们测试一下读入整个文件再处理的方法,首先要写一个字符串转化为数组的函数,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
把整个文件读入一个字符串最常用的方法是用fread,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
上述代码有着惊人的效率,经测试读取这10000000个数只用了0.29秒,效率提高了几乎10倍!掌握着种方法简直无敌了,不过,我记得fread是封装过的read,如果直接使用read,是不是更快呢?代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
测试发现运行时间仍然是0.29秒,可见read不具备特殊的优势。到此已经结束了吗?不,我可以调用Linux的底层函数mmap,这个函数的功能是将文件映射到内存,是所有读文件方法都要封装的基础方法,直接使用mmap会怎样呢?代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 | |
经测试,运行时间缩短到了0.25秒,效率继续提高了14%。到此为止我已经没有更好的方法继续提高读文件的速度了。回头测一下Pascal的速度如何?结果令人大跌眼镜,居然运行了2.16秒之多。程序如下:
1 2 3 4 5 6 7 8 9 10 11 | const MAXN = 10000000; var numbers :array[0..MAXN] of longint; i :longint; begin assign(input,'data.txt'); reset(input); for i:=0 to MAXN do read(numbers[i]); end. |
为确保准确性,我又换到Windows平台上测试了一下。结果如下表:
方法/平台/时间(秒) | Linux gcc | Windows mingw | Windows VC2008 |
scanf | 2.010 | 3.704 | 3.425 |
cin | 6.380 | 64.003 | 19.208 |
cin取消同步 | 2.050 | 6.004 | 19.616 |
fread | 0.290 | 0.241 | 0.304 |
read | 0.290 | 0.398 | 不支持 |
mmap | 0.250 | 不支持 | 不支持 |
Pascal read | 2.160 | 4.668 |
从上面可以看出几个问题
- Linux平台上运行程序普遍比Windows上快。
- Windows下VC编译的程序一般运行比MINGW(MINimal Gcc for Windows)快。
- VC对cin取消同步与否不敏感,前后效率相同。反过来MINGW则非常敏感,前后效率相差8倍。
- read本是linux系统函数,MINGW可能采用了某种模拟方式,read比fread更慢。
- Pascal程序运行速度实在令人不敢恭维。
希望此文能对大家有所启发,欢迎与我继续讨论。
一. 文件一次读入速度
linux下读文件这东西最后都是要通过系统调用sys_read(fd,buf,count)来实现的,所以如果要提高速度,就是最简单地调用sys_read的封装,比如直接用read()或fread()。下面是我在linux下的几个测试。
首先创建一个130M数据文件 dd if=/dev/zero of=data bs=1024k count=130
分别用fread,read和fgets一次读入全部大小文件所消耗时间对比,其中
size=130*1024*1024
char *buf=new char[size];
下面是测试结果(机器Intel(R) Pentium(R) 4 CPU 3.20GHz, Mem 1G):
1.fread(buf,size,1,fp)一次读入
real 0m0.187s
user 0m0.000s
sys 0m0.180s
2.read(fdin,(void *)buf,size)一次读入
real 0m0.187s
user 0m0.000s
sys 0m0.184s
3.多次fgets(buf,size,fp),每次1k
real 0m0.356s
user 0m0.136s
sys 0m0.220s
4.fgets(buf,size,fp)一次读入
real 0m0.305s
user 0m0.072s
sys 0m0.232s
上 面看到越简单的函数(read()和fread()),速度越快,其他的输入封装函数只不过是为了方便满足特殊需要,并不见得读的速度会很快。对于3和4,因为在sys_read()内部有对读入大小的判断和while循环,所以大文件读取也没必要像3那样分多次调用fgets()来读文件.
另外用mmap()内存映射来读入文件的时间如下:
real 0m0.231s
user 0m0.068s
sys 0m0.164s
也并没有比直接read()快,网上找到一种解释是对于需要频繁读写操作的,mmap效率才会显著提高。下面来模拟频繁读写操作。
二. 文件频繁读写速度测试
1. 这一个测试模拟频繁文件读写操作,500M大小的数据文件data.in,每次从中读入1k个字节对每个字节做加1简单计算,再写到另一个文件data.out中。
//mmapx1.c
#define size 1024*1024*500
#define LEN 1024
#include <stdio.h>
int main()
{
FILE *fp1,*fp2;
char *buf=new char[LEN];
int i,j;
fp1=fopen("data.in","rb");
fp2=fopen("data.out","wb");
for(j=0;j<1024*500;j++)
{
fread(buf,1024,1,fp1);
for(i=0;i<LEN;i++)
buf[i]++;
fwrite(buf,LEN,1,fp2);
}
printf("ok!/n");
fclose(fp1);
fclose(fp2);
}
time 命令测试时间,每次结果都不一样,机器负载有关。5次输出的结果如下:
real 19.592s 18.517s 18.003s 20.470s 20.004s
usr 2.924s 2.964s 3.000s 2.812s 2.972s
sys 2.472s 2.360s 2.344s 2.652s 0.396s
2. 如果采用内存映射,将文件data.in映射到存储区,就避免的对文件的频繁读写操作,而全部转变为I/O存储读写。下面一个程序就是用mmap映射将文件data.in中每个数据加1存储到data.out中。
//mmapx2.c
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#define size 1024*1024*500
#define LEN 1024
int main()
{
int fdin,fdout;
struct stat statbuf;
void *src,*dst;
char *p;
int i,j;
fdin=open("data.in",O_RDONLY);
fdout=open("data.out",O_RDWR|O_CREAT|O_TRUNC);
if((src=mmap(0,size,PROT_READ,MAP_SHARED,fdin,0))==MAP_FAILED)
{
printf("src map error/n");
return -1;
}
lseek(fdout,size-1,SEEK_SET);
write(fdout,"/0",1);
//因为data.out是空文件,必须创建一个空洞让其大小成为size来进行下面的mmap
if((dst=mmap(0,size,PROT_READ|PROT_WRITE,MAP_SHARED,fdout,0))==MAP_FAILED)
{
printf("dest map error/n");
return -1;
}
memcpy(dst,src,size);
p=(char*)dst;
for(i=0;i<size/LEN;i++)
{
for(j=0;j<LEN;j++)
p[j]++;
p+=LEN;
}
printf("ok/n");
close(fdout);
return 0;
}
time测试的5次运行时间如下:
real 9.603s 8.977s 9.416s 9.587s 9.322s
usr 2.764s 2.748s 2.784s 2.840s 2.787s
sys 1.516s 1.384s 1.384s 1.224s 1.276s
结论:上面可以看到,对于频繁IO读写,采用mmap存储映射可以有效提高效率(20s到9s)
3.mmap内存映射还有一个好处,就是直接在一个文件内任意读写修改某个字节,就像操作存储区一样,比如下面一段程序是实现对data.in中每个字节数据加1的计算。
//mmapx3.c
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#define size 1024*1024*500
#define LEN 1024
int main()
{
int fd;
void *src;
char *p;
int i,j;
fd=open("data.in",O_RDWR);
if((src=mmap(0,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0))==MAP_FAILED)
{
printf("src map error/n");
return -1;
}
p=(char*)src;
for(i=0;i<size/LEN;i++)
{
for(j=0;j<LEN;j++)
p[j]++;
p+=LEN;
}
printf("ok/n");
close(fd);
}
time测试的5次的运行时间如下:
real 2.820s 2.828s 2.856s 2.818s 2.889s
usr 2.624s 2.624s 2.676s 2.648s 2.584s
sys 0.196s 0.196s 0.176s 0.172s 0.288s
上面对一个大文件内的所有字节各自改变加1,并保存在原来的文件中,采用mmap存储映射的方法可以大大提高效率。对于频繁地随机改写某个文件内的某些部分字节内容的情况来说,这是一个有效的选择。