티가 없는 것보다 빠르다

티가 없는 것보다 빠르다

이 간단한 bash 스크립트가 있습니다.

#!/bin/bash

for i in {1..1000000}
do
    echo "hello ${i}"
done

그러면 메시지가 백만 번 인쇄됩니다.

tee모든 출력을 단일 파일로 덤프하는 것과 분할 출력을 두 파일로 사용하는 성능을 비교하려고 합니다.

$ time ./run.sh > out1.txt

real    0m9.535s
user    0m6.678s
sys     0m2.803s
$ 
$ time ./run.sh | tee out2.txt > out1.txt

real    0m6.705s
user    0m6.895s
sys     0m5.214s

두 개의 파일에 동시에 쓰는 것이 하나의 파일에만 쓰는 것보다 더 빠른 것으로 나타났습니다(이러한 결과는 여러 실행에서 일관됩니다).

이것이 어떻게 가능한지 설명할 수 있는 사람이 있나요? 또한 sum 의 출력을 user어떻게 해석해야 합니까 ? 왜 사용하는 데 시간이 더 오래 걸립니까 ?systimesystee

답변1

먼저 몇 가지 사항을 명확히 해보겠습니다.

  • 홍조

echo쉘은 이 예 와 같이 각 내장 명령 후에 출력을 플러시합니다 .

그래야 합니다. 이는 마치 외부 명령인 것처럼 동작을 모방해야 합니다(여기서는 /bin/echo성능상의 이유로 순전히 외부 명령이 아닌 내장 셸 명령입니다 ). 외부 명령은 종료 시 출력을 플러시(또는 무시)해야 합니다. 내장 함수는 echo동일한 방식으로 동작해야 합니다.

이는 -ed 데이터가 이미 출력에 사용 가능한 경우 예상되는 동작입니다( echo스크립트에서 오랜 대기 또는 계산이 뒤따르는 경우) .echo

이 동작은 표준 출력이 연결된 위치와 무관합니다. 터미널, 파이프, 파일일 수 있지만 상관없습니다. 각각은 echo독립적으로 플러시됩니다. (비교하지 마세요.기본보시다시피 표준 출력이 터미널로 전송되는지 여부에 따라 동작이 달라지는 libc의 동작최대cat, grep, 등과 같은 표준 유틸리티는 head물론 tee. 쉘은 libc의 기본 버퍼링에 의존하지 않고 각 내장 명령 후에 명시적으로 플러시됩니다. )

셸에서 실행된 백만 건의 호출을 strace ./run.sh > out1.txt보는 데 사용됩니다 .write()

  • 멀티코어

시스템에 여러 개의 CPU 코어가 있고 다른 프로세스에서 상당한 로드가 없다고 가정합니다. 이 설정에서는 코어가 bash run.sh하나의 코어에 할당된 다음 tee다른 코어에 할당됩니다. 이렇게 하면 무거운 프로세스 전환이 발생하지 않으며 실제로 모두 실행 중인 경우 동시에 실행됩니다.

아마도 두 프로세스를 단일 코어로 제한하면(명령을 사용하여 이 작업을 수행할 수 있다고 생각합니다 taskset. 시도해 보겠습니다.) 매우 다른 결과를 얻게 되어 tee프로세스 속도가 크게 느려질 것입니다. 직렬로 실행되고 인터리브되어야 하는 추가 프로세스일 뿐만 아니라 run.sh커널도 두 프로세스 사이를 여러 번 전환해야 하며 이 전환 자체의 비용이 상당히 높습니다.

  • time명령

time전체 파이프, run.sh결합 tee파이프를 측정합니다. 명령 중 하나만 측정하려면 time하위 셸에서 호출하세요. 예를 들면 다음과 같습니다.

$ ( time ./run.sh ) | tee out2.txt > out1.txt

$ ./run.sh | ( time tee out2.txt ) > out1.txt

시간 은 real벽시계에 경과 시간을 인쇄합니다. 즉, 말 그대로 파이프 전후의 타임스탬프를 인쇄하여 차이를 계산하거나 외부 스톱워치를 사용한 것과 같습니다. 두 개의 프로세스가 파이프라인에서 실행되고 각각이 10초 동안 하나의 CPU 코어를 회전하고 둘 다 완전히 병렬로 영원히 실행될 수 있다면 실제 시간은 10초가 됩니다.

usersys그러나 시간은 CPU 코어 전체에 걸쳐 누적됩니다. 각각 자체 CPU 코어에 있는 두 개의 병렬 프로세스가 CPU를 최대 10초(방금 본 것처럼 실제 시간은 10초)까지 회전시키면 사용자 시간은 20초가 됩니다.


이제 이것을 정리해보자:

대답해야 할 질문은 하나뿐입니다. 파일보다 파이프에 작은 데이터 덩어리를 쓰는 것이 왜 더 빠른가요?

나는 이것에 대한 직접적인 대답을 가지고 있지 않습니다. 나는 단지 거꾸로 작업하여 측정한 타이밍 결과를 바탕으로 결론을 내립니다.이어야 한다파이프에 쓰는 것이 파일에 쓰는 것보다 빠릅니다. 다음은 약간의 추측이지만 합리적이길 바랍니다.

파이프의 크기는 고정되어 있습니다(내 생각에는 64kB). 파이프를 생성할 때 전체 크기를 할당하기가 쉽기 때문에 더 이상 커널에서 동적 할당이 발생하지 않습니다. (크기에 도달하면 판독기가 공간을 확보할 때까지 쓰기 측이 차단됩니다.) 그러나 파일의 경우 이러한 제한이 없습니다. 사용자 공간에서 커널로 전달되는 모든 내용은 거기에 복사되어야 합니다(데이터가 실제로 디스크에 기록되기 전에 기록기를 차단하는 것은 불가능합니다). 그래서 나는 이것이 아마도 일종의 동적 메모리 할당이라는 것을 알아냈습니다.가능한파일에 쓰는 동안 발생하므로 이 부분의 비용이 더 많이 듭니다.

파이프를 사용하면 커널이 수행해야 할 유일한 추가 작업은 방금 실행할 수 있었던 프로세스를 깨우는 것입니다. 즉, 데이터가 파이프에 나타날 때까지 기다리는 것입니다. 파일의 경우 커널은 파일의 메모리 내 메타데이터(크기, 수정 시간)를 업데이트하고 타이머를 시작(또는 기존 타이머 업데이트)하여 이 데이터를 디스크에 최종적으로 기록하도록 예약해야 합니다.

파일에 쓰는 것이 중요하다는 엄격한 규칙은 없습니다.~해야 한다측정한 숫자에서 알 수 있듯이 파이프에 쓰는 것보다 비용이 더 많이 듭니다.

을 추가하면 이제 백만 초가 더 저렴해지기 때문에 tee필요한 작업이 줄어듭니다 . 이를 통해 전체 시스템이 더 빠르게 실행되고 결과적으로 벽시계 시간이 단축됩니다.run.shwrite()run.sh

대부분 병렬로 실행되고 작업량이 더 적은 두 번째 프로세스를 추가합니다. 두 출력 파일 모두 버퍼링을 사용합니다 write(). 즉, 버퍼링되지 않은 경우에 비해 시스템 호출이 몇 개만 있습니다. 입력을 위해 백만 개의 작은 read()작업을 수행할 수 있지만추측하다시간의 무작위성 및 기타 이유로 인해 많은 bashs가 write()결합되어 단일 s로 도착할 수 있으므로 read()백만 개 미만의 read()s가 필요할 수 있습니다. ( read()몇 번이나 실행되었는지 확인하면 좋을 것입니다. strace"ing"은 측정 자체가 타이밍을 크게 변경하므로 옵션이 아닙니다. tee각 카운터에 대해 카운터를 증가시키고 read()끝에 해당 숫자를 덤프하도록 패치할 것입니다. 나는 그것을 독자 여러분의 연습 문제로 남겨두겠습니다).

따라서 파이프라인 완료를 지연시키지 않으므로 tee속도가 더 빠릅니다 . run.sh그러나 사용자 및 시스템 시간에 자체 공유를 추가하여 이전보다 더 크게 만듭니다.


고쳐 쓰다:

궁금해서 tee몇 번이나 만지작거려 봤습니다 read().

데스크탑에 단말기가 1개만 있다면 66만 ~ 67만 정도 됩니다. 백그라운드에서 브라우저를 열고 한두 페이지를 표시하면 약 500,000~600,000 정도입니다. 브라우저가 방금 시작되었으며(추가 작업) 약 400,000개입니다. 이는 의미가 있습니다. 수행해야 하는 다른 작업이 많을수록 tee해당 데이터가 즉시 읽혀지지 않고 일부 bash' write()'가 축적될 가능성이 높아집니다. 아이디어를 얻었고 이제는 대략적인 숫자를 얻었습니다. 물론 컴퓨터마다 크게 다를 수 있습니다.

관련 정보