`yes`는 어떻게 그렇게 빨리 파일에 기록합니까?


58

예를 들어 보겠습니다.

$ timeout 1 yes "GNU" > file1
$ wc -l file1
11504640 file1

$ for ((sec0=`date +%S`;sec<=$(($sec0+5));sec=`date +%S`)); do echo "GNU" >> file2; done
$ wc -l file2
1953 file2

여기서 당신은 명령을 볼 수 있습니다 yes쓴다 11504640내가에만 쓸 수 있지만 두 번째에 라인을 1953배쉬의 사용 오초에 라인 forecho.

의견에서 제안했듯이, 더 효율적으로 만드는 여러 가지 트릭이 있지만 다음과 같은 속도에 근접하지는 않습니다 yes.

$ ( while :; do echo "GNU" >> file3; done) & pid=$! ; sleep 1 ; kill $pid
[1] 3054
$ wc -l file3
19596 file3

$ timeout 1 bash -c 'while true; do echo "GNU" >> file4; done'
$ wc -l file4
18912 file4

이것들은 1 초에 최대 2 만 라인을 쓸 수 있습니다. 그리고 다음과 같이 더 향상 될 수 있습니다.

$ timeout 1 bash -c 'while true; do echo "GNU"; done >> file5' 
$ wc -l file5
34517 file5

$ ( while :; do echo "GNU"; done >> file6 ) & pid=$! ; sleep 1 ; kill $pid
[1] 5690
$ wc -l file6
40961 file6

이것들은 1 초에 최대 4 만 라인을 제공합니다. 더 낫지 만 여전히 yes1,100 만 줄을 쓸 수있는 외침 이 있습니다!

그렇다면 파일에 이렇게 빨리 쓰는 방법은 yes무엇입니까?



9
두 번째 예에서는 루프의 모든 반복에 대해 두 개의 외부 명령 호출 date이 있으며 다소 무겁고, 쉘은 echo모든 루프 반복 에 대해 출력 스트림을 다시 열어야합니다 . 첫 번째 예에서는 단일 출력 리디렉션으로 단일 명령 호출 만 있으며 명령은 매우 가볍습니다. 이 둘은 결코 비교할 수 없습니다.
CVn

@ MichaelKjörling 당신이 맞아도 date무거울 수 있습니다. 내 질문에 대한 편집을 참조하십시오.
Pandya

1
timeout 1 $(while true; do echo "GNU">>file2; done;)명령 대체가 완료된 후에 만 ​​명령이 시작 timeout 되므로 잘못된 사용법 timeout입니다. 사용하십시오 timeout 1 sh -c 'while true; do echo "GNU">>file2; done'.
muru

1
답변 요약 : write(2)첫 번째 예제 ( date파일에 인쇄 된 모든 줄 을 실행하고 대기하는)에서 다른 시스템 콜의 보트로드, 쉘 오버 헤드 또는 프로세스 생성에 CPU 시간을 소비하여 시스템 호출 에만 소비 합니다 . RAM이 많은 최신 시스템에서는 1 초의 쓰기로 CPU / 메모리가 아닌 디스크 I / O에서 병목 현상이 발생할 수 있습니다. 더 오래 달릴 수 있다면 차이는 더 작아 질 것입니다. (bash 구현의 나쁜 정도와 CPU 및 디스크의 상대 속도에 따라 bash로 디스크 I / O를 포화시키지 않을 수도 있습니다).
Peter Cordes

답변:


65

간단히 말해서 :

yesstdio 를 통해 libC에 의해 버퍼링 된 출력 으로 FILE STREAM에 일반적으로 쓰는 대부분의 다른 표준 유틸리티와 유사한 동작을 나타냅니다 . 이들은 약 4kb (16kb 또는 64kb)마다 또는 출력 블록 BUFSIZ 가 무엇이든 syscall 만 수행합니다 . A는 당이 . 즉, A의 로트모드 전환 (a 같은 고가로서 명백하게 아니다 문맥 전환 ) .write()echowrite()GNU

그리고 초기 최적화 루프 외에도 yes매우 간단하고 작고 컴파일 된 C 루프이며 쉘 루프는 컴파일러 최적화 프로그램과 비교할 수 없습니다.


그러나 나는 틀렸다 :

내가 yesstdio 를 사용 하기 전에 말했을 때 , 그것은 그것과 비슷하게 행동하기 때문에 그렇게했다고 가정했습니다. 이것은 정확하지 않습니다. 단지 이런 식으로 그들의 행동을 모방합니다. 무엇 실제로하는 일은 매우 내가 쉘 아래에했던 일에 아날로그 같다 : 먼저 인수를 conflate하는 루프 (또는 y없음 경우) 가 초과하지 않고 더 이상 성장하지 수까지 BUFSIZ.

관련 루프 상태 바로 앞에 있는 소스 의 주석 for:

/* Buffer data locally once, rather than having the
large overhead of stdio buffering each item.  */

yeswrite()그 후 자체적으로 수행합니다 .


침략 :

(원래 질문에 포함되어 있으며 이미 여기에 기록 된 유익한 설명과 관련하여 유지되었습니다) :

시도 timeout 1 $(while true; do echo "GNU">>file2; done;)했지만 루프를 멈출 수 없습니다.

timeout나는 지금 그것을 얻을 생각하고, 그것을 중단하지 않는 이유를 설명 할 수 - 문제는 당신이 명령 치환에 있습니다. timeout명령 줄이 실행되지 않으므로 시작되지 않습니다. 쉘은 자식 쉘을 포크하고 stdout에서 파이프를 열고 읽습니다. 아이가 종료되면 읽기를 중단 한 다음, $IFS맹 글링 및 글로브 확장을 위해 쓴 모든 아이를 해석 하고 그 결과로 $(일치하는 모든 것을 대체 합니다 ).

아이가 파이프에 기록하지 무한 루프가 있다면, 그때 아이는 루핑을 중지하지 않으며, 결코 timeout의 명령 줄은 전에 완료되지 않습니다 (I 추측으로) 당신이 할 CTRL-C과 아이 루프를 죽일. 따라서 루프를 시작하기 전에 완료해야하는 루프를 종료 timeout할 수 없습니다 .


다른 사람 timeout:

... 쉘 프로그램이 출력을 처리하기 위해 사용자 모드와 커널 모드 사이를 전환하는 데 소요되는 시간만큼 성능 문제와 관련이 없습니다. timeout그러나 쉘이 이런 목적을위한 것만 큼 유연하지는 않다. 쉘 엑셀이 인수를 다루고 다른 프로세스를 관리하는 능력에있다.

다른 곳에서 알 수 있듯이, [fd-num] >> named_file루프 오버 된 명령에 대한 출력을 지시하지 않고 단순히 루프의 출력 대상으로 리디렉션을 이동 하면 성능을 크게 향상시킬 수 있습니다. 최소한 open()syscall은 한 번만 수행하면 되기 때문입니다 . |내부 루프에 대한 출력으로 파이프 가 지정된 경우에도 아래에서 수행됩니다 .


직접 비교 :

당신은 좋아할 것입니다 :

for cmd in  exec\ yes 'while echo y; do :; done'
do      set +m
        sh  -c '{ sleep 1; kill "$$"; }&'"$cmd" | wc -l
        set -m
done

256659456
505401

이는 종류의 일어나기 전 설명 명령 하위 관계처럼, 그러나 거기에는 파이프가 없습니다 그것은 부모를 죽이고 때까지 아이가 백그라운드로한다. 에서 yes경우 아이가 양산 된 이후 부모는 실제로 대체되었지만, 쉘 호출 yes새와 함께 자체 프로세스를 중첩하여 그래서 PID는 동일하게 유지하고 좀비 아이가 아직 결국 죽일 사람을 알고있다.


더 큰 버퍼 :

이제 셸 write()버퍼 증가에 대해 살펴 보겠습니다 .

IFS="
";    set y ""              ### sets up the macro expansion       
until [ "${512+1}" ]        ### gather at least 512 args
do    set "$@$@";done       ### exponentially expands "$@"
printf %s "$*"| wc -c       ### 1 write of 512 concatenated "y\n"'s  

1024

1kb보다 긴 출력 문자열이 나에게 분리되어 있기 때문에 그 숫자를 선택했습니다 write(). 그리고 여기 루프가 다시 있습니다 :

for cmd in 'exec  yes' \
           'until [ "${512+:}" ]; do set "$@$@"; done
            while printf %s "$*"; do :; done'
do      set +m
        sh  -c $'IFS="\n"; { sleep 1; kill "$$"; }&'"$cmd" shyes y ""| wc -l
        set -m
done

268627968
15850496

이 테스트에서 마지막 시간과 같은 시간에 쉘이 쓴 데이터 양의 300 배입니다. 너무 초라하지 않습니다. 그러나 그렇지 않습니다 yes.


관련 :

요청에 따라이 링크 에서 수행되는 작업에 대한 단순한 코드 주석보다 더 자세한 설명 있습니다.


@heemayl-아마도? 내가 당신이 무엇을 요구하는지 완전히 확신하지 못합니까? 프로그램이 stdio를 사용하여 출력을 쓰면 버퍼링 (기본적으로 stderr과 같은) 또는 라인 버퍼링 (기본적으로 터미널에) 또는 블록 버퍼링 (기본적으로 대부분의 다른 것들이 기본적 으로이 방법으로 설정 됨)없이 수행 됩니다. 출력 버퍼의 크기를 설정하는 것이 무엇인지는 확실하지 않지만 일반적으로 4kb입니다. stdio lib 함수는 전체 블록을 작성할 수있을 때까지 출력을 수집합니다. dd예를 들어 stdio를 사용하지 않는 표준 도구 중 하나입니다. 대부분의 사람들은 그렇게합니다.
mikeserv 2016 년

3
쉘 버전은 open(기존) writeAND close(여전히 플러시를 기다리고 있다고 생각합니다) date하고 각 프로세스에 대해 새 프로세스를 만들고 실행 합니다.
dave_thompson_085

@ dave_thompson_085- / dev / chat으로 이동하십시오 . 거기에서 볼 수 있듯이 당신이 말하는 것은 반드시 사실이 아닙니다. 예를 들어, 그 일 wc -l에 루프 bash나를 출력의 1 / 5 도착을 위해 sh- 루프 수행을 bash100,000 이상 조금 관리 writes()dash의 50 만.
mikeserv

내가 애매하게해서 미안하다. 나는 질문에 쉘 버전을 의미했는데, 나는 읽었을 때 for((sec0=`date +%S`;...시간을 제어하고 루프에서 리디렉션을 제어 하는 원래 버전 만 후속 개선 사항이 없었습니다.
dave_thompson_085

@ dave_thompson_085-괜찮습니다. 대답은 어쨌든 몇 가지 근본적인 요점에 대해서는 틀렸다.
mikeserv

20

더 좋은 질문은 왜 쉘이 파일을 이렇게 천천히 쓰는가하는 것입니다. 한 번에 모든 문자를 플러시하지 않고 파일 쓰기 syscalls를 사용하는 자체 포함 컴파일 프로그램은 합리적으로 빠릅니다. 당신이하고있는 일은 해석 언어 (쉘)로 줄을 쓰는 것 외에도 불필요한 입력 출력 작업을 많이 수행합니다. 무엇을 하는가 yes:

  • 쓰기 위해 파일을 엽니 다
  • 스트림에 쓰기위한 최적화되고 컴파일 된 함수 호출
  • 스트림이 버퍼링되므로 큰 청크에서 syscall (커널 모드로의 비싼 전환)이 거의 발생하지 않습니다.
  • 파일을 닫는다

스크립트가하는 일 :

  • 한 줄의 코드로 읽습니다.
  • 코드를 해석하여 실제로 입력을 구문 분석하고 수행 할 작업을 파악하기 위해 많은 추가 작업을 수행합니다.
  • while 루프의 각 반복에 대해 (아마 해석되는 언어에서는 저렴하지 않음) :
    • date외부 명령을 호출 하고 출력을 저장하십시오 (원본 버전에서만-수정 된 버전에서는 이것을하지 않으면 10 배가됩니다)
    • 루프의 종료 조건이 충족되는지 테스트
    • 추가 모드에서 파일을 엽니 다
    • echo명령을 구문 분석 하고 (일부 패턴 일치 코드로) 쉘 내장으로 인식하고, 인수 "GNU"에서 매개 변수 확장 및 기타 모든 것을 호출하고 마지막으로 열린 파일에 행을 작성하십시오.
    • 파일을 다시 닫습니다
    • 과정을 반복

비싼 부분 : 전체 해석은 매우 비쌉니다 (bash는 모든 입력에 대해 엄청나게 많은 전처리를 수행합니다-문자열에는 변수 대체, 프로세스 대체, 괄호 확장, 이스케이프 문자 등이 포함될 수 있음), 내장의 모든 호출은 아마도 내장 기능을 다루는 함수로 리디렉션되는 switch 문일 것입니다. 매우 중요한 것은 각 출력 라인마다 파일을 열고 닫는 것입니다. >> filewhile 루프 외부에 배치 하여 훨씬 빠르게 만들 수 있지만 여전히 통역 언어로되어 있습니다. 당신은 아주 운이 좋은echo는 외부 명령이 아닌 쉘 내장입니다. 그렇지 않으면 루프마다 모든 반복마다 새 프로세스 (포크 및 exec)를 생성해야합니다. 프로세스가 중단 date되는 것은 루프 에서 명령을 받았을 때 비용이 얼마나 드는지를 보았습니다 .


11

다른 답변은 주요 요점을 해결했습니다. 참고로 계산이 끝날 때 출력 파일에 쓰면 while 루프의 처리량을 높일 수 있습니다. 비교:

$ i=0;time while  [ $i -le 1000 ]; do ((++i)); echo "GNU" >>/tmp/f; done;

real    0m0.080s
user    0m0.032s
sys     0m0.037s

$ i=0;time while  [ $i -le 1000 ]; do ((++i)); echo "GNU"; done>>/tmp/f;

real    0m0.030s
user    0m0.019s
sys     0m0.011s

예, (AT-이상)이 문제와 쓰기 속도는 내 경우에는 두 배로
디야
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.