tail file | tr(파이프)이 여러 줄 sed 또는 perl보다 빠른 이유는 무엇입니까?

tail file | tr(파이프)이 여러 줄 sed 또는 perl보다 빠른 이유는 무엇입니까?

다음과 같은 약 백만 줄의 파일이 있습니다.

"ID" "1" "2"
"00000687" 0 1
"00000421" 1 0
"00000421" 1 0
"00000421" 1 0

마지막 줄은 백만 번 이상 반복되었습니다. ~에서 영감을 얻다이 문제, 제안된 솔루션 중 일부를 시도하여 어느 것이 더 빠른지 확인했습니다. 단 하나의 프로세스만 사용하는 솔루션은 파이프가 있는 솔루션보다 더 빠를 것으로 기대합니다. 그러나 이것은 내 테스트 결과입니다.

  • tail -n +2 file.txt | tr -d \"

    $ time tail -n +2 file.txt | tr -d \" 1> /dev/null
    
    real    0m0,032s
    user    0m0,020s
    sys     0m0,028s
    
  • sed '1d;s/"//g' file.txt

    $ time sed '1d;s/"//g' file.txt 1> /dev/null
    
    real    0m0,410s
    user    0m0,399s
    sys     0m0,011s
    
  • perl -ne ' { s/"//g; print if $. > 1 }' file.txt

    $ time perl -ne ' { s/"//g; print if $. > 1 }' file.txt 1> /dev/null
    
    real    0m0,379s
    user    0m0,367s
    sys     0m0,013s
    

여러 번 테스트를 반복했는데 항상 비슷한 수치가 나왔습니다. 보시다시피 tail -n +2 file.txt | tr -d \"그렇습니다훨씬 더 빨리다른 사람들보다. 왜?

답변1

이는 수행되는 작업의 양에 따라 결정됩니다.

귀하의 tail | tr명령은 다음과 같이 종료됩니다.

  • 존재하다tail:
    • 줄 바꿈까지 읽습니다.
    • 개행 여부에 관계없이 남은 내용을 모두 출력합니다.
  • in 은 tr읽고 개행 문자를 신경 쓰지 않으며 '"'(고정 문자)를 제외한 모든 것을 출력합니다.

sed주어진 스크립트를 해석한 후 명령은 다음을 수행하게 됩니다.

  • 개행까지 읽고 입력을 누적합니다.
  • 첫 번째 줄이면 삭제하세요.
  • 정규식을 해석한 후 모든 큰따옴표를 빈 공백으로 바꿉니다.
  • 처리된 라인을 출력합니다.
  • 파일 끝까지 반복합니다.

주어진 스크립트를 해석한 후 Perl 명령은 다음을 수행하게 됩니다.

  • 개행까지 읽고 입력을 누적합니다.
  • 정규식을 해석한 후 모든 큰따옴표를 빈 공백으로 바꿉니다.
  • 첫 번째 줄이 아닌 경우 처리된 줄을 출력합니다.
  • 파일 끝까지 반복합니다.

많은 양의 입력으로 인해 개행 문자를 찾는 데 비용이 많이 듭니다.

답변2

주로 Perl과 sed가 각 라인을 개별적으로 처리하기 때문입니다.

Perl이 입력을 더 큰 덩어리로 처리하고 약간 단순화하면(주석 참조) 더 빠르게 만들 수 있지만 tr만큼 빠르지는 않습니다.

time perl -ne ' { s/"//g; print if $. > 1 }' file.txt 1> /dev/null

real    0m0.617s
user    0m0.612s
sys     0m0.005s

time perl -pe 'BEGIN{<>;$/=\40960} s/"//g' file.txt >/dev/null

real    0m0.186s
user    0m0.177s
sys     0m0.009s

time tail -n +2 file.txt | tr -d \" 1> /dev/null

real    0m0.033s
user    0m0.031s
sys     0m0.023s

perl -ne '... if $. > 1'참고: 또는 를 사용하지 마십시오 awk 'NR == 1 { ... } /foo/ { ... }'.

BEGIN{<>}대신 및 를 사용하세요 BEGIN{getline}.

첫 번째 줄을 읽고 나면 다음 줄이 더 이상 첫 번째 줄이 아니라는 것을 확신할 수 있습니다. 다시 확인할 필요가 없습니다.

답변3

tail.c의 tail_lines():

      /* Use file_lines only if FD refers to a regular file for
         which lseek (... SEEK_END) works.  */

      if ( ! presume_input_pipe
           && S_ISREG (stats.st_mode)
           && (start_pos = lseek (fd, 0, SEEK_CUR)) != -1
           && start_pos < (end_pos = lseek (fd, 0, SEEK_END)))

여기서 end_pos = lseek (fd, 0, SEEK_END)파일 내용을 건너뜁니다. file_lines()에는 개행 수를 계산하는 역방향 스캔이 있습니다.

lseek()는 읽기/쓰기를 위해 파일 오프셋을 재배치하는 데 사용되는 매우 간단한 시스템 호출입니다.


아, 질문의 미묘함을 놓친 것 같습니다. ;) 한 줄씩 읽는 것과 한 블록씩 읽는 것이 전부입니다. 여러 채널을 하나의 복잡한 채널로 결합하는 것이 가장 좋은 경우가 많습니다. 그러나 여기의 알고리즘에는 첫 번째 개행 문자만 필요합니다.

Ole의 두 부분으로 구성된 Perl 스크립트는 sysread()첫 번째 개행 문자 검색에서 가장 큰 덩어리 읽기로 전환하는 방법을 보여줍니다.

정상적으로 뒤로 작업할 때 tail마지막 블록을 읽고 줄 바꿈을 계산합니다. 거기에서 두 번째부터 마지막 ​​블록까지 인쇄하거나 읽습니다.

답변4

꼭 써보고 싶은데 perl너무 느리네요.

perl는 일반적인 도구이지만 다음과 같이 tr더 가까이 다가갈 수 있습니다.

$ tail -n +2 file.txt | tr -d \" >/dev/null;
real    0m0.040s
user    0m0.030s
sys     0m0.032s

$ perl -e 'while(sysread(STDIN,$b,1)) {$b eq "\n" and last}
           while(sysread(STDIN,$b,131072)) {
             $b=~tr/\"//d; print $b
           }' < file.txt > /dev/null;
real    0m0.049s
user    0m0.045s
sys     0m0.004s

피하고 tail더 빨리 갈 수 있습니다.

$ time (read; tr -d \") < file.txt >/dev/null
real    0m0.033s
user    0m0.021s
sys     0m0.012s

관련 정보