grep은 어떻게 그렇게 빨리 실행됩니까?


113

저는 쉘에서 GREP의 기능에 정말 놀랐습니다. 이전에는 Java에서 하위 문자열 메서드를 사용했지만 이제는 GREP를 사용하고 몇 초 만에 실행되며 제가 작성했던 Java 코드보다 엄청나게 빠릅니다. (내 경험에 따르면 나는 틀릴 수도 있습니다)

나는 그것이 어떻게 일어나고 있는지 알 수 없었다고 말하고 있습니까? 웹상에서도 많이 볼 수 없습니다.

누구든지 이것으로 나를 도울 수 있습니까?


5
오픈 소스이므로 직접 살펴볼 수 있습니다. gnu.org/software/grep/devel.html
driis

6
Ridiculous Fish는 귀하의 질문에 정확히 답하는 훌륭한 글을 가지고 있습니다 : ridiculousfish.com/blog/posts/old-age-and-treachery.html
David

@WilliamPursell 실행 시간이 초 단위로 진행될 때 JIT는 아마도 워밍업을했을 것이고, 마음이 마비 된 차이는 (1) grep이하는 일에 대해 믿을 수 없을 정도로 똑똑하고 (2) Java 코드가 꽤 나쁜 알고리즘 선택을하기 때문입니다. 특정 문제에 대해 grep이 중점을 둡니다.

3
Java 구현은 JVM을 시작하는 데 얼마나 많은 시간을 소비하고 실제로 코드를 실행하는 데 얼마나 많은 시간을 소비합니까? 또는 Java 코드에서 사용한 알고리즘의 문제 일 수 있습니다. O (N ^ 2) 알고리즘은 모든 언어에서 느릴 수 있습니다.
Keith Thompson

답변:


169

귀하의 질문이 GNU grep구체적으로 관련되어 있다고 가정합니다 . 다음은 저자 Mike Haertel의 메모입니다.

GNU grep은 모든 입력 바이트를 보지 않기 때문에 빠릅니다.

그것은 각 바이트 거의 명령을 실행하기 때문에 GNU 그렙은 빠르게 수행 에 모습을.

GNU grep은 잘 알려진 Boyer-Moore 알고리즘을 사용합니다.이 알고리즘은 대상 문자열의 마지막 문자를 먼저 찾고, 일치하지 않는 문자를 찾을 때마다 입력에서 얼마나 빨리 건너 뛸 수 있는지 알려주기 위해 조회 테이블을 사용합니다.

GNU grep은 또한 Boyer-Moore의 내부 루프를 풀고, 풀린 모든 단계에서 루프 종료 테스트를 수행 할 필요가없는 방식으로 Boyer-Moore 델타 테이블 항목을 설정합니다. 그 결과 한도 내에서 GNU grep은 실제로 보는 각 입력 바이트에 대해 실행되는 x86 명령어가 평균 3 개 미만이며 많은 바이트를 완전히 건너 뜁니다.

GNU grep은 원시 Unix 입력 시스템 호출을 사용하고 데이터를 읽은 후 복사하는 것을 방지합니다. 또한 GNU grep은 입력을 줄로 끊는 것을 방지합니다. 줄 바꿈을 찾으려면 grep이 몇 배 정도 느려질 것입니다. 줄 바꿈을 찾으려면 모든 바이트를 살펴 봐야하기 때문입니다!

따라서 라인 지향 입력을 사용하는 대신 GNU grep은 원시 데이터를 큰 버퍼로 읽고 Boyer-Moore를 사용하여 버퍼를 검색하며 일치하는 항목을 찾은 경우에만 경계 줄 바꿈을 찾습니다 (다음과 같은 특정 명령 줄 옵션). n이 최적화를 비활성화합니다.)

이 답변은 여기 에서 가져온 정보의 하위 집합입니다 .


41

Steve의 탁월한 답변에 추가합니다.

널리 알려지지는 않았지만 , 긴 패턴에서 Boyer-Moore 는 더 나은 서브 선형 속도 를 달성하기 위해 더 긴 스트라이드에서 앞으로 건너 뛸 수 있기 때문에 짧은 패턴보다 패턴 스트링 을 찾을 때 grep이 거의 항상 더 빠릅니다 .

예:

# after running these twice to ensure apples-to-apples comparison
# (everything is in the buffer cache) 

$ time grep -c 'tg=f_c' 20140910.log
28
0.168u 0.068s 0:00.26

$ time grep -c ' /cc/merchant.json tg=f_c' 20140910.log
28
0.100u 0.056s 0:00.17

긴 형태는 35 % 더 빠릅니다!

어째서? Boyer-Moore 는 패턴 문자열에서 건너 뛰기 테이블을 구성하고 불일치가있을 때마다 입력의 단일 문자를 건너 뛰기 테이블의 문자와 비교하기 전에 가능한 가장 긴 건너 뛰기를 선택합니다 (마지막 문자에서 처음으로).

다음은 Boyer Moore를 설명하는 비디오입니다 (kommradHomer에 대한 크레딧).

(GNU 그렙에 대한) 또 다른 일반적인 오해는 즉 fgrep보다 더 빨리이다 grep. fin fgrep은 'fast'를 의미하지 않고 'fixed'를 의미하며 (man page 참조) 둘 다 동일한 프로그램이고 둘 다 Boyer-Moore를 사용하므로 fixed- 를 검색 할 때 속도 차이가 없습니다. 정규 표현식 특수 문자가없는 문자열. 내가 사용하는 유일한 이유 fgrep는 정규 표현식 특수 문자 (예 .: [], 또는 *) 가있을 때 그 자체로 해석되기를 원하지 않기 때문입니다. 그리고 심지어는 다음의 이식성 / 표준 양식은 grep -F이상이 바람직하다 fgrep.


3
긴 패턴이 더 빠르다는 것은 직관적입니다. 패턴이 1 바이트이면 grep은 모든 바이트를 확인해야합니다. 패턴이 4 바이트이면 4 바이트 건너 뛰기를 만들 수 있습니다. 패턴이 텍스트만큼 길다면 grep은 한 단계 만 수행합니다.
noel 2014

12
예, Boyer-Moore의 작동 방식을 이해한다면 직관적입니다.
arielf 2014

2
그렇지 않더라도 직관적입니다. 짧은 바늘보다 건초 더미에서 긴 바늘을 찾는 것이 더 쉬울 것입니다
RajatJ

2
"더 길어질수록 더 빨라진다"에 대한 반대의 예는 실패하기 전에 많은 테스트를 수행해야하고 어쨌든 앞으로 나아갈 수없는 경우입니다. 파일 xs.txt에 100000000 'x'가 포함되어 있다고 가정 grep yx xs.txt하면. 그러면 실제로 일치하는 항목을 찾는 것보다 빨리 찾지 못합니다 grep yxxxxxxxxxxxxxxxxxxx xs.txt. Boyer-Moore-Horspool의 Boyer-Moore-Horspool 개선은이 경우 Skip-ahead에서 향상되지만 일반적인 경우에는 기계 명령이 세 개만있는 것은 아닙니다.
lrn

2
@Tino 감사합니다. 예, (GNU) grep/fgrep/egrep가 동일한 실행 파일에 대한 모든 하드 링크 였던 시절 은 지나간 것 같습니다. 그것들 (및 z*grep bz*grep즉석에서 압축을 푸는 유틸리티 와 같은 다른 확장 )은 이제 grep. 단일 실행 파일과 쉘 래퍼 사이의 전환에 대한 흥미로운 역사적 코멘트는이 커밋에서 찾을 수 있습니다 : git.savannah.gnu.org/cgit/grep.git/commit/…
arielf
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.