Tại sao tập tin `tac | grep foo '(đường ống) nhanh hơn `grep foo << (tệp tac)' (thay thế quá trình)?


7

Câu hỏi này được thúc đẩy bởi " Đảo ngược ", về việc gồng một tập tin khổng lồ từ dưới lên.

@chaos nói :

tac file | grep whatever

Hoặc hiệu quả hơn một chút:

grep whatever < <(tac file)

@ vinc17 nói :

Các < <(tac filename)nên càng nhanh như ống

Ngoài ra còn có nhiều bình luận thú vị từ những người dùng khác.

Những câu hỏi của tôi:

  • Sự khác biệt giữa |và là < <()gì?
  • Tại sao cái này nhanh hơn cái kia?
  • Và cái nào thực sự nhanh hơn?
  • Tại sao không ai đề nghị xargs?

1
Lưu ý rằng nó là < <(...)viết tắt của 0< <(...), không <<(...).
Stéphane Chazelas

1
@Anthon Cảm ơn bạn đã sửa lỗi "greping" thành "grepping" - Tôi vừa quay lại mẫu tiếng Anh.SE để sửa nó; Tôi thực sự tò mò làm thế nào nó đúng, và tại sao . Học rất nhiều về tiếng Anh trong một thời gian ngắn. Xem 6 câu trả lời tại Deriving một từ cho hoạt động sử dụng một công cụ từ tên công cụ (Nhật ký grep))
Volker Siegel

@VolkerSiegel Tôi chỉ nhìn vào tiêu đề của câu hỏi được liên kết, để đánh vần nhất quán ;-)
Anthon

Tôi biết điều đó không nhất quán - nhưng tôi không biết ai đúng;) Tôi rất thích "greping", bởi vì "p" bổ sung trong "grepping" bằng cách nào đó trở thành một phần của tên lệnh - cảm thấy "rất sai" đối với tôi - chỉ hoàn toàn không có ý nghĩa gì cả ... Nhưng hóa ra các anh chàng ở tiếng Anh. Tôi đã khiến tôi chấp nhận "grepping" ngay trong khoảng một giờ và 6 câu trả lời :)
Volker Siegel

@ StéphaneChazelas Đã chỉnh sửa ví dụ cho phù hợp.
Volker Siegel

Câu trả lời:


10

Các <(tac file)nguyên nhân xây dựng để vỏ:

  • Tạo một đường ống có tên
    • Trên các hệ thống như Linux và SysV có /dev/fd, một đường ống thông thường được sử dụng và /dev/fd/<the-file-descriptor-of-the-pipe>được sử dụng làm tên.
    • Trên các hệ thống khác, một ống có tên được sử dụng, yêu cầu tạo một mục nhập tệp thực tế trên đĩa.
  • Khởi chạy lệnh tac filevà kết nối nó với một đầu của đường ống.
  • Thay thế toàn bộ công trình trên dòng lệnh bằng tên của đường ống.

Sau khi thay thế, dòng lệnh trở thành:

grep whatever < /tmp/whatever-name-the-shell-used-for-the-named-pipe

Và sau đó grepđược thực thi, và nó đọc đầu vào tiêu chuẩn của nó (là đường ống), đọc nó và tìm kiếm đối số đầu tiên của nó trong đó.

Vì vậy, kết quả cuối cùng giống như với ...

tac file | grep whatever

... trong đó hai chương trình tương tự được đưa ra và một đường ống vẫn được sử dụng để kết nối chúng. Nhưng việc <( ... )xây dựng phức tạp hơn vì nó bao gồm nhiều bước hơn và có thể liên quan đến một tệp tạm thời (đường ống có tên).

Cấu <( ... )trúc này là một phần mở rộng và không có sẵn trong vỏ bourne POSIX tiêu chuẩn cũng như trên các nền tảng không hỗ trợ /dev/fdhoặc đặt tên ống. Chỉ riêng vì lý do này, bởi vì hai lựa chọn thay thế đang được xem xét là hoàn toàn tương đương về chức năng, command | other-commandhình thức di động hơn là một lựa chọn tốt hơn.

Việc <( ... )xây dựng nên chậm hơn vì tích chập bổ sung, nhưng nó chỉ trong giai đoạn khởi động và tôi không hy vọng sự khác biệt có thể dễ dàng đo lường được.

LƯU Ý : Trên nền tảng Linux SysV, < ( ... )không sử dụng các đường dẫn có tên mà thay vào đó sử dụng các đường ống thông thường. Các đường ống thông thường (thực sự là tất cả các mô tả tập tin) có thể được gọi bằng tên đặc biệt /dev/fd/<file-descriptor-numberđể đó là những gì vỏ sử dụng làm tên cho đường ống. Bằng cách này, nó tránh được việc tạo một đường ống có tên thật với tên tệp tạm thời ngay trong hệ thống tập tin thực. Mặc dù /dev/fdmẹo là những gì đã được sử dụng để triển khai tính năng này khi nó xuất hiện ban đầu ksh, nhưng đó là một tối ưu hóa: trên các nền tảng không hỗ trợ điều này, một ống có tên thông thường trong hệ thống tệp thực được sử dụng như mô tả ở trên.

C NOTENG LƯU Ý : Để mô tả cú pháp như <<( ... )là sai. Trong thực tế <( ... ), nó được thay thế bằng tên của một đường ống và sau đó là <ký tự khác có tiền tố toàn bộ tách biệt với cú pháp này và đó là cú pháp nổi tiếng thông thường để chuyển hướng đầu vào từ một tệp.


1
thay thế quá trình xuất hiện trên ksh và đã sử dụng / dev / fd / n (tính năng SysV) từ đầu (và không khả dụng trên các hệ thống không hỗ trợ nó). bashzshthêm hỗ trợ cho các đường ống được đặt tên cho các hệ thống thiếu / dev / fd / n sau này.
Stéphane Chazelas

@ StéphaneChazelas ah, đó là lịch sử thú vị, cảm ơn. Vì vậy, ban đầu nó là một kshtính năng, không phải là tính năng POSIX tiêu chuẩn! Chắc chắn, dashkhông xuất hiện để hỗ trợ nó. Đó là một lý do nữa để tránh điều đó khi có sẵn một giải pháp thay thế tương đương bằng các tính năng POSIX cơ bản.
Celada

Đó không chỉ là SysV (SysV thường đề cập đến việc triển khai tham chiếu và cả một họ hệ điều hành) và Linux. Solaris, tất cả các BSD hiện đại và có lẽ nhiều người khác hỗ trợ / dev / fd / n. (trên thực tế, Linux khác biệt đáng kể so với các phiên bản khác khi nó được triển khai dưới dạng liên kết tượng trưng với tài nguyên gốc trong khi ở Unices / dev / fd / n khác là các thiết bị đặc biệt khi mở, hoạt động như dup (đó là lý do tôi nói trên Linux, và chỉ Linux, / dev / fd / n trên một đường ống hoạt động như một đường ống có tên)).
Stéphane Chazelas

1
Tôi nghĩ rằng sự thay thế quá trình < <(command)là thích hợp hơn đối với đường ống |. Như được giải thích bởi gnouc, |yêu cầu mỗi lệnh chạy trong một lớp con trong khi quá trình thay thế làm cho đường ống mở và cung cấp lệnh từ thiết bị xuất chuẩn. Có nhiều hơn ở đây: wiki.bash-hackers.org/syntax/Exansion/proc_substmywiki.wooledge.org/ProcessSubstlation
Valentin Bajrami

@ val0x00ff Việc command | commandxây dựng đường ống có hiệu quả như bạn có thể nhận được. Shell sử dụng fork()để tạo mỗi quy trình mới trong một đường ống; nhưng sau đó fork()được sử dụng phổ biến để tạo ra các quy trình mới - đó không được coi là một nhánh con.
Celada

7

Sự khác biệt giữa | và << ()?

Có một sự khác biệt giữa chúng:

  • | làm cho mỗi lệnh chạy trong một subshell riêng biệt.

  • <() chạy lệnh, được thay thế trong nền.

Đối với hai câu hỏi tiếp theo, chúng tôi sẽ làm một số strace:

pipe:

$ strace -fc bash -c 'tac /usr/share/dict/american-english | grep qwerty'
$ time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00    0.008120        2707         3         1 wait4
  0.00    0.000000           0       352           read
  0.00    0.000000           0       229           write
  0.00    0.000000           0        20         2 open
  0.00    0.000000           0        29         2 close
  0.00    0.000000           0        40        17 stat
  0.00    0.000000           0        19           fstat
  0.00    0.000000           0       117           lseek
  0.00    0.000000           0        38           mmap
  0.00    0.000000           0        18           mprotect
  0.00    0.000000           0         6           munmap
  0.00    0.000000           0        25           brk
  0.00    0.000000           0        22           rt_sigaction
  0.00    0.000000           0        18           rt_sigprocmask
  0.00    0.000000           0         1           rt_sigreturn
  0.00    0.000000           0         3         2 ioctl
  0.00    0.000000           0        24        12 access
  0.00    0.000000           0         1           pipe
  0.00    0.000000           0         2           dup2
  0.00    0.000000           0         1           getpid
  0.00    0.000000           0         1         1 getpeername
  0.00    0.000000           0         2           clone
  0.00    0.000000           0         3           execve
  0.00    0.000000           0         1           uname
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0        13           getuid
  0.00    0.000000           0        13           getgid
  0.00    0.000000           0        13           geteuid
  0.00    0.000000           0        13           getegid
  0.00    0.000000           0         1           getppid
  0.00    0.000000           0         1           getpgrp
  0.00    0.000000           0         3           arch_prctl
  0.00    0.000000           0         1           time
------ ----------- ----------- --------- --------- ----------------
100.00    0.008120                  1034        37 total

Process Substitution:

$ strace -fc bash -c 'grep qwerty < <(tac /usr/share/dict/american-english)'
$ time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 99.14    0.016001        4000         4         2 wait4
  0.46    0.000075           0       229           write
  0.24    0.000038           0       341           read
  0.16    0.000026           1        24           brk
  0.00    0.000000           0        21         2 open
  0.00    0.000000           0        27           close
  0.00    0.000000           0        40        17 stat
  0.00    0.000000           0        19           fstat
  0.00    0.000000           0       117           lseek
  0.00    0.000000           0        38           mmap
  0.00    0.000000           0        18           mprotect
  0.00    0.000000           0         6           munmap
  0.00    0.000000           0        35           rt_sigaction
  0.00    0.000000           0        24           rt_sigprocmask
  0.00    0.000000           0         2           rt_sigreturn
  0.00    0.000000           0         3         2 ioctl
  0.00    0.000000           0        24        12 access
  0.00    0.000000           0         1           pipe
  0.00    0.000000           0         3           dup2
  0.00    0.000000           0         1           getpid
  0.00    0.000000           0         1         1 getpeername
  0.00    0.000000           0         3           clone
  0.00    0.000000           0         3           execve
  0.00    0.000000           0         1           uname
  0.00    0.000000           0         1         1 fcntl
  0.00    0.000000           0         2           getrlimit
  0.00    0.000000           0        13           getuid
  0.00    0.000000           0        13           getgid
  0.00    0.000000           0        13           geteuid
  0.00    0.000000           0        13           getegid
  0.00    0.000000           0         1           getppid
  0.00    0.000000           0         1           getpgrp
  0.00    0.000000           0         3           arch_prctl
  0.00    0.000000           0         1           time
------ ----------- ----------- --------- --------- ----------------
100.00    0.016140                  1046        37 total

Tại sao một cái gì đó nhanh hơn so với khác?

Và cái gì thực sự nhanh hơn?

Bạn có thể thấy, process substitutionchậm hơn pipetrong trường hợp này, vì nó sử dụng nhiều cuộc gọi hệ thống hơn. Cả hai đều dành nhiều thời gian để chờ đợi các tiến trình con, nhưng process substitutionsử dụng nhiều wait4()tòa nhà hơn và sử dụng nhiều thời gian hơn cho mỗi cuộc gọi hơn pipe.

Tại sao không ai đề nghị xargs?

Tôi không nghĩ xargscó thể giúp được gì ở đây, đây không phải là công việc của nó.

Cập nhật

Như đề xuất của @ Gilles, tôi thực hiện kiểm tra với tệp lớn hơn, 2GB dữ liệu ngẫu nhiên được tạo từ /dev/urandom. Nó cho thấy rằng pipethực sự nhanh hơn process substitution.

pipe:

$ strace -fc bash -c 'tac sample.txt | grep qwerty'
$ time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 81.15    8.284959     2761653         3         1 wait4
 17.89    1.825959           2    780959           read
  0.91    0.092708           0    524286           write
  0.05    0.005364           0    262146           lseek
  0.00    0.000000           0        20         2 open
  0.00    0.000000           0        29         2 close
  0.00    0.000000           0        40        17 stat
  0.00    0.000000           0        19           fstat
  0.00    0.000000           0        38           mmap
  0.00    0.000000           0        18           mprotect
  0.00    0.000000           0         6           munmap
  0.00    0.000000           0        25           brk
  0.00    0.000000           0        22           rt_sigaction
  0.00    0.000000           0        18           rt_sigprocmask
  0.00    0.000000           0         1           rt_sigreturn
  0.00    0.000000           0         3         2 ioctl
  0.00    0.000000           0        24        12 access
  0.00    0.000000           0         1           pipe
  0.00    0.000000           0         2           dup2
  0.00    0.000000           0         1           getpid
  0.00    0.000000           0         1         1 getpeername
  0.00    0.000000           0         2           clone
  0.00    0.000000           0         3           execve
  0.00    0.000000           0         1           uname
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0        13           getuid
  0.00    0.000000           0        13           getgid
  0.00    0.000000           0        13           geteuid
  0.00    0.000000           0        13           getegid
  0.00    0.000000           0         1           getppid
  0.00    0.000000           0         1           getpgrp
  0.00    0.000000           0         3           arch_prctl
  0.00    0.000000           0         1           time
------ ----------- ----------- --------- --------- ----------------
100.00   10.208990               1567727        37 total

process substitution:

$ strace -fc bash -c 'grep qwerty < <(tac sample.txt)'
$ time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 99.51   13.912869     3478217         4         2 wait4
  0.38    0.053373           0    655269           read
  0.09    0.013084           0    524286           write
  0.02    0.002454           0    262146           lseek
  0.00    0.000030           1        38           mmap
  0.00    0.000024           1        24        12 access
  0.00    0.000000           0        21         2 open
  0.00    0.000000           0        27           close
  0.00    0.000000           0        40        17 stat
  0.00    0.000000           0        19           fstat
  0.00    0.000000           0        18           mprotect
  0.00    0.000000           0         6           munmap
  0.00    0.000000           0        24           brk
  0.00    0.000000           0        35           rt_sigaction
  0.00    0.000000           0        24           rt_sigprocmask
  0.00    0.000000           0         2           rt_sigreturn
  0.00    0.000000           0         3         2 ioctl
  0.00    0.000000           0         1           pipe
  0.00    0.000000           0         3           dup2
  0.00    0.000000           0         1           getpid
  0.00    0.000000           0         1         1 getpeername
  0.00    0.000000           0         3           clone
  0.00    0.000000           0         3           execve
  0.00    0.000000           0         1           uname
  0.00    0.000000           0         1         1 fcntl
  0.00    0.000000           0         2           getrlimit
  0.00    0.000000           0        13           getuid
  0.00    0.000000           0        13           getgid
  0.00    0.000000           0        13           geteuid
  0.00    0.000000           0        13           getegid
  0.00    0.000000           0         1           getppid
  0.00    0.000000           0         1           getpgrp
  0.00    0.000000           0         3           arch_prctl
  0.00    0.000000           0         1           time
------ ----------- ----------- --------- --------- ----------------
100.00   13.981834               1442060        37 total

1
các đòn bẩy thay thế quá trình /dev/fd/n, nó không sử dụng các ống có tên (mặc dù trên Linux và chỉ Linux / dev / fd / n hoạt động giống như các ống được đặt tên khi n là một mô tả tệp cho một ống (có tên hoặc không)). Trên các hệ thống không hỗ trợ / dev / fd / n, một số shell rơi trở lại sử dụng các đường ống được đặt tên.
Stéphane Chazelas

Đã thêm một ghi chú. Nhưng có vẻ như quy trình thuê bao chỉ hỗ trợ khi hệ thống hỗ trợ đường ống có tên: gnu.org/software/bash/manual/html_node/ . Và từ đây: vi.wikipedia.org/wiki/Process_substlation , nó cho biết sử dụng thay thế quá trình có tên ống?
cuonglm

Chà, wikipedia sai ở đây (và bash-centric). Đơn giản chỉ cần chạy ls -l <(:)để xác minh rằng nó không sử dụng đường ống có tên.
Stéphane Chazelas

1
@Gnouc một ống có tên là một đường ống được kết nối với một tên trong hệ thống tập tin. Nó được tạo ra bằng cách sử dụng mkfifonhư được mô tả trong bài viết Wikipedia. Một ống có tên là một trong những loại tệp "đặc biệt" mà đôi khi bạn có thể thấy trong hệ thống tệp, như ổ cắm tên miền UNIX, thiết bị ký tự và thiết bị khối.
Celada

1
Khi chạy lệnh strace nhiều lần, tôi nhận thấy rằng đầu ra thay đổi một chút và cả hai giải pháp đều tương đương nhau. Điều này càng trở nên rõ ràng hơn trên một tệp lớn (vài MB).
vinc17

1

Tôi đã không thể sao chép các kết quả được hiển thị bởi cuonglm . Ngay cả với tệp 2GB, trong Bash 5 trên MacOS Mojave, tôi thấy thời gian rất giống nhau giữa quá trình thay thế và đường ống. Điều này có ý nghĩa với tôi vì chi phí liên quan đến cuộc gọi sẽ tối thiểu so với xử lý thực tế của cuộc gọi đó đối với tệp 2GB, do đó, việc chạy một lần lặp sử dụng thay thế quy trình so với đường ống sẽ giảm xuống mức ngẫu nhiên / lệnh nào chạy đầu tiên để lưu trữ nội dung tập tin.

Tôi đã có thể sao chép những phát hiện trong câu hỏi này cho thấy sự thay thế quá trình nhanh hơn so với hàng ngàn trường hợp thực hiện các cuộc gọi này.

Dưới đây là các lệnh tôi đã chạy và đầu ra:

ống.sh :

shopt -s lastpipe
for i in {1..5000}; do
    echo foo bar |
    while read; do
        echo $REPLY >/dev/null
    done
done

Proc-sub.sh :

for i in {1..5000}; do
    while read; do
        echo $REPLY >/dev/null
    done < <(echo foo bar)
done

pipe-no-lastpipe.sh :

for i in {1..5000}; do
    echo foo bar |
    while read; do
        echo $REPLY >/dev/null
    done
done

Kiểm tra :

time ./proc-sub.sh

real    0m9.505s
user    0m1.875s
sys     0m10.705s

time ./pipe.sh

real    0m14.036s
user    0m4.583s
sys     0m14.193s

time ./pipe-no-lastpipe.sh

real    0m16.696s
user    0m3.055s
sys     0m18.057s
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.