Linux下pipe使用注意事項
Linux下的pipe使用非常廣泛, shell本身就大量用pipe來粘合生產者和消費者的. 我們的伺服器程式通常會用pipe來做執行緒間的ipc通訊. 由於unix下的任何東西都是檔案,只要是檔案,在讀取的時候,,就會設定last access time, 所以pipe也不例外., 但是這個時間對我們沒有意義 如果pipe使用的非常頻繁的時候會碰到由於設定訪問時間導致的效能問題. 這個開銷遠比pipe讀寫的本身開銷大. 相比檔案讀寫的開銷, atime微不足道,但是對pipe來講就不同了.
這個事情是上次和多隆同學在把玩他的網路框架的時候,無意發現的.
我們來分析下pipe的這部分程式碼:
//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時間的,接著:
//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,接著:
//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);
}
我們可以看出上面的流程還是比較複雜的,開銷也很大.
我們來演示下:
$ 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,看下效果:
view sourceprint?
$ 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搞定它.
祝玩得開心!