Linux下的pipe使用非常广泛, shell本身就大量用pipe来粘合生产者和消费者的. 我们的服务器程序通常会用pipe来做线程间的ipc通讯. 由于unix下的任何东西都是文件,只要是文件,在读取的时候,,就会设置last access time, 所以pipe也不例外., 但是这个时间对我们没有意义 如果pipe使用的非常频繁的时候会碰到由于设置访问时间导致的性能问题. 这个开销远比pipe读写的本身开销大. 相比文件读写的开销, atime微不足道,但是对pipe来讲就不同了.
这个事情是上次和多隆同学在把玩他的网络框架的时候,无意发现的.
我们来分析下pipe的这部分代码:
1
2
3
4
5
6
7
8
9
10
|
//pipe.c:L349
static
ssize_t pipe_read( struct
kiocb *iocb, const
struct iovec *_iov,
unsigned
long nr_segs, loff_t pos)
{ ... if
(ret > 0) file_accessed(filp);
return
ret; } |
我们可以看到在pipe读的时候要设置 file_accessed时间的,接着:
1
2
3
4
5
6
7
|
//fs.h:L1761
extern
void touch_atime( struct
vfsmount *mnt, struct
dentry *dentry); static
inline void
file_accessed( struct
file *file) { if
(!(file->f_flags & O_NOATIME)) touch_atime(file->f_path.mnt, file->f_path.dentry);
} |
如果文件没设置 O_NOATIME就真正动手设置atime,接着:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
//inode.c:L1493
void
touch_atime( struct
vfsmount *mnt, struct
dentry *dentry) { struct
inode *inode = dentry->d_inode; struct
timespec now; if
(inode->i_flags & S_NOATIME) return ;
if
(IS_NOATIME(inode)) return ;
if
((inode->i_sb->s_flags & MS_NODIRATIME) && S_ISDIR(inode->i_mode))
return ;
if
(mnt->mnt_flags & MNT_NOATIME) return ;
if
((mnt->mnt_flags & MNT_NODIRATIME) && S_ISDIR(inode->i_mode))
return ;
now = current_fs_time(inode->i_sb);
if
(!relatime_need_update(mnt, inode, now)) return ;
if
(timespec_equal(&inode->i_atime, &now)) return ;
if
(mnt_want_write(mnt)) return ;
inode->i_atime = now;
mark_inode_dirty_sync(inode);
mnt_drop_write(mnt);
} |
我们可以看出上面的流程还是比较复杂的,开销也很大.
我们来演示下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
$ cat
> pipe_test.c #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/unistd.h>
static int fds[2];
static pthread_t rp;
static void *rp_entry(void *arg) {
char c[1];
while
(1 == read (fds[0], c, 1)) {
if
(*c == 'Q' )
break ; }
fprintf(stderr,
"pipe read ok\n" );
return
NULL; } int main(int argc, char *argv[]) {
long i, n;
int rc;
if
(argc < 2) { fprintf(stderr,
"usage: pipe_test NNNNNN\n" );
return
-1; }
n = atol(argv[1]);
pipe(fds);
//fcntl (fds[0], F_SETFL, O_NOATIME);
pthread_create(&rp, NULL, rp_entry, NULL);
fprintf(stderr,
"pipe write %ld..." , n);
for
(i = 0; i < n; i++) { write(fds[1],
"A" , 1);
}
write(fds[1],
"Q" , 1);
fprintf(stderr,
"ok\n" );
pthread_join(rp, NULL);
close(fds[0]);
close(fds[1]);
return
0; } CTRL+D $ gcc -D_GNU_SOURCE pipe_test.c -lpthread
$ sudo
opcontrol --setup --vmlinux= /usr/lib/debug/lib/modules/2 .6.18-164.el5 /vmlinux $ sudo
opcontrol --init && sudo
opcontrol --reset && sudo
opcontrol --start $ . /a .out 10000000
pipe write 10000000...ok
pipe read
ok $ sudo
opcontrol -- shutdown $ opreport -l| less samples % app name symbol name
378654 92.7742 vmlinux .text.acpi_processor_idle
12978 3.1797 vmlinux current_fs_time
2530 0.6199 vmlinux thread_return
2345 0.5745 vmlinux touch_atime
2253 0.5520 vmlinux .text.acpi_safe_halt
1597 0.3913 vmlinux timespec_trunc
1368 0.3352 vmlinux file_update_time
1253 0.3070 vmlinux __mark_inode_dirty
901 0.2208 vmlinux pipe_writev
768 0.1882 vmlinux __mutex_lock_slowpath
763 0.1869 vmlinux try_to_wake_up
270 0.0662 vmlinux copy_user_generic_unrolled
254 0.0622 vmlinux acpi_set_register
254 0.0622 vmlinux system_call
233 0.0571 vmlinux pipe_readv
188 0.0461 vmlinux dnotify_parent
167 0.0409 vmlinux mutex_unlock
... |
我们可以看到touch_atime的开销很大,远比pipe的读写大.
这次把这行注释去掉: fcntl(fds[0], F_SETFL, O_NOATIME); 指示pipe在读的时候不更新atime,看下效果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
$ opreport -l| less samples % app name symbol name
599018 95.2466 vmlinux .text.acpi_processor_idle
4140 0.6583 vmlinux .text.acpi_safe_halt
3281 0.5217 vmlinux thread_return
2812 0.4471 vmlinux current_fs_time
2615 0.4158 vmlinux file_update_time
1790 0.2846 vmlinux __mutex_lock_slowpath
1657 0.2635 vmlinux timespec_trunc
1341 0.2132 vmlinux try_to_wake_up
1281 0.2037 vmlinux mutex_unlock
1080 0.1717 vmlinux mutex_lock
1001 0.1592 vmlinux pipe_readv
925 0.1471 vmlinux pipe_writev |
这下看不到touch_atime了,开销省了,对于高性能服务器是很重要的.
小结: 细节很重要,记得开文件open的时候设置O_NOATIME或者用fcntl搞定它.