導讀
大家先看一下二個指令,假如huge_dump.sql檔案很大,然後猜測一下哪種導入方式效率會更高一些?
# 指令1,管道導入
shell> cat huge_dump.sql | mysql -uroot;
# 指令2,重定向導入
shell> mysql -uroot < huge_dump.sql;
大家先看一下上面二個指令,假如huge_dump.sql檔案很大,然後猜測一下哪種導入方式效率會更高一些?
這個問題挺有意思的,我的第一反應是:沒比較過,應該是一樣的,一個是cat負責打開檔案,一個是bash
這種場景在MySQL運維操作裡面應該比較多,是以就花了點時間做了個比較和原理上的分析:
我們先構造場景:
首先準備一個程式b.out來模拟mysql對資料的消耗:
int main(int argc, char *argv[])
while(fread(buf, sizeof(buf), 1, stdin) > 0);
return 0;
}
$ gcc -o b.out b.c
$ ls|./b.out
再來寫個systemtap腳本用來友善觀察程式的行為。
$ cat test.stp
function should_log(){
return (execname() == "cat" ||
execname() == "b.out" ||
execname() == "bash") ;
}
probe syscall.open,
syscall.close,
syscall.read,
syscall.write,
syscall.pipe,
syscall.fork,
syscall.execve,
syscall.dup,
syscall.wait4
{
if (!should_log()) next;
printf("%s -> %s\n", thread_indent(0), probefunc());
}
probe kernel.function("pipe_read"),
kernel.function("pipe_readv"),
kernel.function("pipe_write"),
kernel.function("pipe_writev")
{
if (!should_log()) next;
printf("%s -> %s: file ino %d\n", thread_indent(0), probefunc(), __file_ino($filp));
}
probe begin { println(":~") }
這個腳本重點觀察幾個系統調用的順序和pipe的讀寫情況,然後再準備個419M的大檔案huge_dump.sql,在我們幾十G記憶體的機器很容易在記憶體裡放下:
$ sudo dd if=/dev/urandom of=huge_dump.sql bs=4096 count=102400
102400+0 records in
102400+0 records out
419430400 bytes (419 MB) copied, 63.9886 seconds, 6.6 MB/s
因為這個檔案是用bufferio寫的,是以它的内容都cache在pagecahce記憶體裡面,不會涉及到磁盤。
好了,場景齊全了,我們接着來比較下二種情況下的速度,第一種管道:
# 第一種管道方式
$ time (cat huge_dump.sql|./b.out)
real 0m0.596s
user 0m0.001s
sys 0m0.919s
從執行時間數看出來速度有3倍左右的差别了,第二種明顯快很多。
是不是有點奇怪?好吧我們來從原來上面分析下,還是繼續用資料說話:
這次準備個很小的資料檔案,友善觀察然後在一個視窗運作stap
$ echo hello > huge_dump.sql
$ sudo stap test.stp
:~
0 bash(26570): -> sys_read
0 bash(26570): -> sys_read
0 bash(26570): -> sys_write
0 bash(26570): -> sys_read
0 bash(26570): -> sys_write
0 bash(26570): -> sys_close
0 bash(26570): -> sys_pipe
0 bash(26570): -> sys_pipe
0 bash(26570): -> do_fork
0 bash(26570): -> sys_close
0 bash(26570): -> sys_close
0 bash(26570): -> do_fork
0 bash(13775): -> sys_close
0 bash(13775): -> sys_read
0 bash(13775): -> pipe_read: file ino 20906911
0 bash(13775): -> pipe_readv: file ino 20906911
0 bash(13776): -> sys_close
0 bash(13776): -> sys_close
0 bash(13776): -> sys_close
0 bash(13776): -> do_execve
0 bash(26570): -> sys_close
0 bash(26570): -> sys_close
0 bash(26570): -> sys_close
0 bash(13775): -> sys_close
0 bash(26570): -> sys_wait4
0 bash(13775): -> sys_close
0 bash(13775): -> sys_close
0 b.out(13776): -> sys_close
0 b.out(13776): -> sys_close
0 bash(13775): -> do_execve
0 b.out(13776): -> sys_open
0 b.out(13776): -> sys_close
0 b.out(13776): -> sys_open
0 b.out(13776): -> sys_read
0 b.out(13776): -> sys_close
0 cat(13775): -> sys_close
0 cat(13775): -> sys_close
0 b.out(13776): -> sys_read
0 b.out(13776): -> pipe_read: file ino 20906910
0 b.out(13776): -> pipe_readv: file ino 20906910
0 cat(13775): -> sys_open
0 cat(13775): -> sys_close
0 cat(13775): -> sys_open
0 cat(13775): -> sys_read
0 cat(13775): -> sys_close
0 cat(13775): -> sys_open
0 cat(13775): -> sys_close
0 cat(13775): -> sys_open
0 cat(13775): -> sys_read
0 cat(13775): -> sys_write
0 cat(13775): -> pipe_write: file ino 20906910
0 cat(13775): -> pipe_writev: file ino 20906910
0 cat(13775): -> sys_read
0 b.out(13776): -> sys_read
0 b.out(13776): -> pipe_read: file ino 20906910
0 b.out(13776): -> pipe_readv: file ino 20906910
0 cat(13775): -> sys_close
0 cat(13775): -> sys_close
0 bash(26570): -> sys_wait4
0 bash(26570): -> sys_close
0 bash(26570): -> sys_wait4
0 bash(26570): -> sys_write
stap在收集資料了,我們在另外一個視窗運作管道的情況:
$ cat huge_dump.sql|./b.out
我們從systemtap的日志可以看出:
bash fork了2個程序。
然後execve分别運作cat 和 b.out程序, 這二個程序用pipe通信。
資料從由cat從 huge_dump.sql讀出,寫到pipe,然後b.out從pipe讀出處理。
那麼再看下指令2重定向的情況:
$ ./b.out < huge_dump.sql
stap輸出:
0 bash(26570): -> sys_read
0 bash(26570): -> sys_read
0 bash(26570): -> sys_write
0 bash(26570): -> sys_read
0 bash(26570): -> sys_write
0 bash(26570): -> sys_close
0 bash(26570): -> sys_pipe
0 bash(26570): -> do_fork
0 bash(28926): -> sys_close
0 bash(28926): -> sys_read
0 bash(28926): -> pipe_read: file ino 20920902
0 bash(28926): -> pipe_readv: file ino 20920902
0 bash(26570): -> sys_close
0 bash(26570): -> sys_close
0 bash(26570): -> sys_wait4
0 bash(28926): -> sys_close
0 bash(28926): -> sys_open
0 bash(28926): -> sys_close
0 bash(28926): -> do_execve
0 b.out(28926): -> sys_close
0 b.out(28926): -> sys_close
0 b.out(28926): -> sys_open
0 b.out(28926): -> sys_close
0 b.out(28926): -> sys_open
0 b.out(28926): -> sys_read
0 b.out(28926): -> sys_close
0 b.out(28926): -> sys_read
0 b.out(28926): -> sys_read
0 bash(26570): -> sys_wait4
0 bash(26570): -> sys_write
0 bash(26570): -> sys_read
bash fork了一個程序,打開資料檔案。
然後把檔案句柄搞到0句柄上,這個程序execve運作b.out。
然後b.out直接讀取資料。
現在就非常清楚為什麼二種場景速度有3倍的差别:
指令1,管道方式: 讀二次,寫一次,外加一個程序上下文切換。
指令2,重定向方式:隻讀一次。
結論:Linux下大檔案重定向效率更高。