最近上课老师说了这样一句话:mmap()内存映射可以实现文件的拷贝,并且速度明显快于一般的文件拷贝,于是
我试着实现了两种文件拷贝所花费时间的比较,首先看代码:
include
include
include
include
include
include
include
include
include
include
define BUFFER_SIZE 1
void my_copy1()
{
int fin,fout; //文件描述符
void *start;
void *end;
struct stat sb;
if((fin = open(“file.in”,O_RDONLY)) < 0){
perror(“open error”);
exit(EXIT_FAILURE);
}
if((fout = open( “file.out”,O_RDWR | O_CREAT | O_TRUNC,00600)) < 0 ){
perror( “write error” );
exit( EXIT_FAILURE );
}
fstat(fin,&sb);
//这块必须给fout一个需求大小的偏移,因为mmap没有扩展空间的能力
if(lseek(fout,sb.st_size-1,SEEK_SET) < 0 ){
exit(EXIT_FAILURE);
}
if(write(fout, &sb,1) != 1 ){
exit(EXIT_FAILURE);
}
start = mmap(NULL,sb.st_size,PROT_READ,MAP_PRIVATE,fin,0);
if(start == MAP_FAILED)
return;
end = mmap(0,(size_t)sb.st_size,PROT_WRITE,MAP_SHARED,fout,0);
if(end == MAP_FAILED){
return ;
}
memcpy(end,start,(size_t)sb.st_size);
munmap(start,sb.st_size); //关闭映射
munmap(end,sb.st_size);
close(fin);
close(fout);
return;
}
void my_copy2()
{
int fin,fout;
int bytes_read,bytes_write;
char buffer[BUFFER_SIZE];
char *ptr;
if((fin = open(“file.in”,O_RDONLY)) < 0){
perror(“open error”);
exit(EXIT_FAILURE);
}
if((fout = open( “file.out”,O_RDWR | O_CREAT | O_TRUNC,00700)) < 0 ){
perror( “write error” );
exit( EXIT_FAILURE );
}
while(bytes_read=read(fin,buffer,BUFFER_SIZE)){
if((bytes_read==-1)&&(errno!=EINTR))
break;
else if(bytes_read>0){
ptr=buffer;
while(bytes_write=write(fout,ptr,bytes_read)){
if((bytes_write==-1)&&(errno!=EINTR))
break;
else if(bytes_write==bytes_read)
break;
else if(bytes_write>0){
ptr+=bytes_write;
bytes_read-=bytes_write;
}
}
if(bytes_write==-1)
break;
}
}
close(fin);
close(fout);
return;
}
main()
{
struct timeval tv;
struct timezone tz;
long time_start,time_end;
gettimeofday(&tv,&tz);
time_start = tv.tv_sec*1000000 + tv.tv_usec;
my_copy1();
printf(“\ndone.\n\n”);
gettimeofday(&tv,&tz);
time_end = tv.tv_sec*1000000 + tv.tv_usec;
printf(“using \”mmap()\” to copy costs %ld microseconds \n”,time_end - time_start);
gettimeofday(&tv,&tz);
time_start = tv.tv_sec*1000000 + tv.tv_usec;
my_copy2();
gettimeofday(&tv,&tz);
time_end = tv.tv_sec*1000000 + tv.tv_usec;
//这块之前有时会出现打印出的负数,后来查看gettimeofday()函数,才知道,我所使用的微秒位在满一秒
//的时候会进位到秒,也即是微秒位清零。所以,正确的方法是在计算time_start和time_end的时候加上
//秒这一位,不过要首先转换其成为微秒。
printf(“using \”read() and write()\” to copy costs %ld microseconds \n”,time_end - time_start);
}
代码不是很难,中间使用了一些LinuxC的一些函数,不懂的可以自己查阅相关资料。我现在主要使想就两种
不同的拷贝的实现在所花费的时间上的一些比较以及的出我自己的一些观点,调试程序时可以将BUFFER_SIZE
随意更改一个数字,表示的是使用read函数从文件中一次读取的字符个数。当然,强调了这个必然有原因。
如果BUFFER_SIZE很小的话,最终的结果差别很大。比如我的
BUFFER_SIZE=1时我的运行结果如下:
zhou@zhou:~/LinuxC/file/mmcopy$ ./mmap
done.
using “mmap()” to copy costs 591 microseconds
using “read() and write()” to copy costs 505337 microseconds
zhou@zhou:~/LinuxC/file/mmcopy
两个完全不是一个数量级的。下面换个数字BUFFERSIZE=10000时我的运行情况如下:zhou@zhou: /LinuxC/file/mmcopy
两
个
完
全
不
是
一
个
数
量
级
的
。
下
面
换
个
数
字
B
U
F
F
E
R
S
I
Z
E
=
10000
时
我
的
运
行
情
况
如
下
:
z
h
o
u
@
z
h
o
u
:
/
L
i
n
u
x
C
/
f
i
l
e
/
m
m
c
o
p
y
./mmap
done.
using “mmap()” to copy costs 594 microseconds
using “read() and write()” to copy costs 585 microseconds
zhou@zhou:~/LinuxC/file/mmcopy$
这时两个的消耗时间很接近,可以想象。如果BUFFER_SIZE定义的很大的话,那么read()write()方法将会
非常快,但是。如果你要拷贝的文件很小呢,加入只有100字节,但是你却每次申请10000个字节,这样岂不
是很浪费内存。这也就是mmap()的优势,不仅没有浪费内存,而且速度相当的快。
话题一转,这是为什么呢,我的理解使这样的:mmap首先将要拷贝的文件的内容全部映射到内存,然后写到目
的文件,总共的磁盘操作就两次,而read()write()不同,会根据你的BUFFER_SIZE定义的,然后会执行
(文件内容的总的字节数/BUFFER_SIZE)*2 次的磁盘操作,因此在这上面浪费了大量的时间。
再看另一个程序:
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/times.h>
#define error(fmt,args...) \
printf(fmt, ##args) ; \
printf(":%s\n",strerror(errno))
inline int cp_rw(int srcfd,int dstfd,char *buf,int len);
inline int cp_map(int srcfd,int dstfd,size_t len);
int main(int argc,char **argv)
{
char buf[8192];
int srcfd,dstfd;
clock_t start,end;
struct tms stm,ntm;
struct stat filestat;
int tck;
char cmdline[30];
if(argc!=3)
printf("usage: cmd ");
tck=sysconf(_SC_CLK_TCK);
start = times(&stm);
if((srcfd=open(argv[1],O_RDONLY))==-1)
{
error("open %s error",argv[1]);
exit(0);
}
if((dstfd=open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0666))==-1)
{
error("creat %s error",argv[2]);
exit(0);
}
fstat(srcfd,&filestat);
if(lseek(dstfd,filestat.st_size,SEEK_SET)==-1)
{
error("lseek error");
exit(0);
}
if(write(dstfd," ",1)!=1)
{
error("write error");
exit(0);
}
cp_map(srcfd,dstfd,filestat.st_size);
close(srcfd);
close(dstfd);
end = times(&ntm);
printf("copying %s to %s using cp_map:filesize=%lu MBytes Using %f seconds\n"
,argv[1],argv[2],filestat.st_size>>20,(end-start)/(double)tck);
sprintf(cmdline,"rm -f %s",argv[2]);
system(cmdline);
start = times(&stm);
if((srcfd=open(argv[1],O_RDONLY))==-1)
{
error("open %s error",argv[1]);
}
if((dstfd=open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0666))==-1)
{
error("creat %s error",argv[2]);
}
cp_rw(srcfd,dstfd,buf,sizeof(buf));
fstat(srcfd,&filestat);
close(srcfd);
close(dstfd);
end = times(&ntm);
printf("copying %s to %s using cp_rw:filesize=%lu MBytes Using %f seconds\n"
,argv[1],argv[2],filestat.st_size>>20,(end-start)/(double)tck);
return 0;
}
inline int cp_rw(int srcfd,int dstfd,char *buf,int len)
{
int nread;
while((nread=read(srcfd,buf,len))>0)
{
if(write(dstfd,buf,nread)!=nread)
{
error("write error");
return -1;
}
}
if(nread ==-1)
{
error("read error");
return -1;
}
return 0;
}
inline int cp_map(int srcfd,int dstfd,size_t len)
{
char *src,*dst;
if((src=mmap(0,len,PROT_READ,MAP_SHARED,srcfd,0))==MAP_FAILED)
{
error("mmap src error");
return -1;
}
if((dst=mmap(0,len,PROT_WRITE,MAP_SHARED,dstfd,0))==MAP_FAILED)
{
error("mmap dst error");
return -1;
}
if(memcpy(dst,src,len)==NULL)
{
error("memcpy error");
return -1;
}
munmap(src,len);
munmap(dst,len);
return 0;
}
运行,拷贝一个1.1G的文件,得到如下结果
[root@garden copy]# ./copy /home/ker.tgz ./ker.tgz
copying /home/ker.tgz to ./ker.tgz using cp_map:filesize=1030 MBytes Using 61.900000 seconds
copying /home/ker.tgz to ./ker.tgz using cp_rw:filesize=1030 MBytes Using 34.330000 seconds
使用read/write的方法居然比mmap的快一倍,这是怎么回事呢?理论上mmap系统调用只进行了一次,而且拷贝文件是直接在内核空间进行的,read/write则需要通过系统调用把内核空间的缓存复制到用户空间,再将用户空间缓存复制到内核空间,拷贝次数明显多了一个呢?速度为什么于理论预测的不一致呢?