Dlaczego echo> plik używa więcej czasu rzeczywistego niż echo | sed> plik?

28

Poniższy przykład mnie zaskoczył. Wydaje się to sprzeczne z intuicją ... poza tym, że jest więcej czasu na echo | sedkombo dla użytkownika.

Dlaczego echozużywa tyle czasu sys, gdy działa sam, a może powinno być pytanie, jak sedzmienia się stan gry? Wydaje się, że echobyłoby potrzeby, aby zrobić to samo echo-ing w obu przypadkach ...

time echo -n a\ {1..1000000}\ c$'\n' >file

# real    0m9.481s
# user    0m5.304s
# sys     0m4.172s

time echo -n a\ {1..1000000}\ c$'\n' |sed s/^\ // >file

# real    0m5.955s
# user    0m5.488s
# sys     0m1.580s
Peter.O
źródło
1
Moja reakcja jelitowa polega na buforowaniu.
bahamat
1
@bahamat Myślę, że masz rację. Echo wykonuje osobne write () dla każdego argumentu. Sed je buforuje. Tak więc pierwsza wersja ma milion zapisów do zwykłego pliku, przechodząc przez sterownik systemu plików do warstwy urządzenia blokowego, a druga wersja ma milion zapisów przechodzących do potoku i nieco mniej zapisów przechodzi przez droższe warstwy kodu jądra.
Alan Curry,
@bahamat Zdecydowanie buforowanie. time echo ... |cat >filea nawet time echo ... |perl -ne 'print'są podobne czasy do sedwersji.
StarNamer,
4
Dziękuję wszystkim za dobre wyjaśnienia ... Tak więc dla dużych wieloliniowych zapisów (w bash) kot zyskał punkt Useful Use of Cat :)
Peter.O

Odpowiedzi:

29

bahamat i Alan Curry mają rację: wynika to ze sposobu, w jaki twoja powłoka buforuje wyjście echo. W szczególności twoja powłoka jest bash i wydaje jedno writewywołanie systemowe na linię. Stąd pierwszy fragment powoduje, że 1000000 zapisuje do pliku dyskowego, podczas gdy drugi fragment powoduje, że 1000000 zapisuje do potoku, a sed (w dużej mierze równolegle, jeśli masz wiele procesorów), robi znacznie mniejszą liczbę zapisów do pliku dyskowego ze względu na jego wynik buforowanie

Możesz obserwować, co się dzieje, biegając strace .

$ strace -f -e write bash -c 'echo -n a\ {1..2}\ c$'\'\\n\'' >file'
write(1, "a 1 c\n", 6)                  = 6
write(1, " a 2 c\n", 7)                 = 7
$ strace -f -e write bash -c 'echo -n a\ {1..2}\ c$'\'\\n\'' | sed "s/^ //" >file'
Process 28052 attached
Process 28053 attached
Process 28051 suspended
[pid 28052] write(1, "a 1 c\n", 6)      = 6
[pid 28052] write(1, " a 2 c\n", 7)     = 7
Process 28051 resumed
Process 28052 detached
Process 28051 suspended
[pid 28053] write(1, "a 1 c\na 2 c\n", 12) = 12
Process 28051 resumed
Process 28053 detached
--- SIGCHLD (Child exited) @ 0 (0) ---

Inne powłoki, takie jak ksh buforują dane wyjściowe echonawet wtedy, gdy są wielowierszowe, więc nie zobaczysz żadnej różnicy.

$ strace -f -e write ksh -c 'echo -n a\ {1..2}\ c$'\'\\n\'' >file'
write(1, "a 1 c\n a 2 c\n", 13)         = 13
$ strace -f -e write ksh -c 'echo -n a\ {1..2}\ c$'\'\\n\'' | sed "s/^ //" >file'
Process 28058 attached
[pid 28058] write(1, "a 1 c\n a 2 c\n", 13) = 13
Process 28058 detached
--- SIGCHLD (Child exited) @ 0 (0) ---
write(1, "a 1 c\na 2 c\n", 12)          = 12

Dzięki bash mam podobne stosunki czasowe. Z ksh widzę, że drugi fragment działa wolniej.

ksh$ time echo -n a\ {1..1000000}\ c$'\n' >file

real    0m1.44s
user    0m1.28s
sys     0m0.06s
ksh$ time echo -n a\ {1..1000000}\ c$'\n' | sed "s/^ //" >file

real    0m2.38s
user    0m1.52s
sys     0m0.14s
Gilles „SO- przestań być zły”
źródło
Dzięki ... Ten ostatni kshprzykład jest bardziej
zgodny