Linux大檔案重定向和管道的效率對比總結

大雄45發表於2020-03-26
導讀 大家先看一下二個 ,假如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,重定向方式:只讀一次。
  • 結論: 下大檔案重定向效率更高。

    原文來自: 


    來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2682795/,如需轉載,請註明出處,否則將追究法律責任。

    相關文章