다음과 같은 약 백만 줄의 파일이 있습니다.
"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