Ruby의 성능은 버전이 거듭될수록 많이 개선되고 있습니다. Ruby 개발 팀은 Ruby를 더욱 빠르게 만들기 위해 모든 노력을 기울이고 있습니다!
이러한 노력 중 하나가 3×3 프로젝트입니다.
목표?
Ruby 3.0은 Ruby 2.0보다 3배 빠릅니다. .
이 프로젝트의 일부는 이 기사의 주제인 새로운 MJIT 컴파일러입니다.
MJIT 설명
MJIT는 "Method Based Just-in-Time Compiler"의 약자입니다.
그게 무슨 뜻인가요?
Ruby는 코드를 YARV 명령어로 컴파일합니다. , 이 지침은 Ruby 가상 머신에 의해 실행됩니다.
JIT는 여기에 다른 레이어를 추가합니다.
자주 사용되는 명령어를 컴파일합니다. 바이너리 코드로.
그 결과 코드를 더 빠르게 실행하는 최적화된 바이너리가 생성됩니다.
작동 방식
MJIT가 더 잘 이해하기 위해 어떻게 작동하는지 살펴보겠습니다.
Ruby 2.6 및 --jit
로 JIT를 활성화할 수 있습니다. 옵션.
좋아요 :
ruby --jit app.rb
Ruby 2.6에는 작동 방식을 정확히 찾는 데 도움이 되는 일련의 JIT 관련 옵션이 있습니다. ruby --help
를 실행하여 이러한 옵션을 볼 수 있습니다. .
옵션 목록은 다음과 같습니다.
- -잠깐
- -지트 장황함
- –jit-save-temps
- –jit-max-cache
- -jit-min-calls
이 장황한 옵션이 좋은 출발점인 것 같습니다!
우리는 또한 --jit-wait
를 사용할 것입니다. , 이렇게 하면 Ruby가 JIT 코드 컴파일이 완료될 때까지 기다렸다가 실행합니다.
정상 작동 중 JIT는 작업자 스레드에서 코드를 컴파일 &완료될 때까지 기다리지 않습니다.
이를 테스트하기 위해 실행할 수 있는 명령은 다음과 같습니다.
ruby --disable-gems --jit --jit-verbose=1 --jit-wait -e "4.times { 123 }"
인쇄물 :
Successful MJIT finish
글쎄요, 별로 흥미롭지 않죠?
JIT는 아무것도 하지 않습니다.
왜?
기본적으로 JIT는 메서드가 5번 호출될 때만 작동하기 때문입니다. (jit-min-calls
) 이상입니다.
이것을 실행하면:
ruby --disable-gems --jit --jit-verbose=1 --jit-wait -e "5.times { 123 }"
이제 흥미로운 것을 얻을 수 있습니다. :
JIT success (32.1ms): block in <main>@-e:1 -> /tmp/_ruby_mjit_p13921u0.c에서 차단
이것은 무엇을 의미합니까?
JIT는 블록을 5번 호출했기 때문에 블록을 컴파일했으며 다음을 알 수 있습니다.
- 컴파일에 걸린 시간 (
32.1ms
), - 정확히 컴파일된 내용 (
block in <main>
) - 생성된 파일(
/tmp/_ruby_mjit_p13921u0.c
) 이 편집의 소스로
이 파일은 오브젝트 파일(.o
)로 컴파일된 C 소스 코드입니다. ) 다음 공유 라이브러리 파일(.so
).
--jit-save-temps
를 추가하면 이러한 파일에 액세스할 수 있습니다. 옵션.
예시 :
이것이 JIT의 작동 방식에 대한 나의 현재 이해입니다. :
- 메소드 호출 수
- 한 메소드가 5번 호출될 때(
jit-min-calls
의 기본값 ) JIT 트리거 - 이 방법에 대한 지침이 포함된 C 파일이 생성됩니다(YARV 지침이지만 인라인됨)
- 컴파일은 백그라운드에서 발생합니다(
--jit-wait
제외). ) GCC와 같은 일반 C 컴파일러 사용 - 컴파일이 완료되면 이 메서드가 호출될 때 결과 공유 라이브러리 파일이 사용됩니다.
이것이 얼마나 효과적인지 봅시다.
MJIT 테스트:정말 더 빠릅니까?
MJIT의 목표는 Ruby를 더 빠르게 만드는 것입니다.
지금 그렇게 하는 것이 얼마나 좋은가요?
알아보자!
첫째, 마이크로벤치마크:
벤치마크 | 결과(JIT가 없는 Ruby 2.6과 비교) |
---|---|
동안 | 8배 빨라짐 |
문자열 추가가 있는 동안 | 10% 더 빨라짐 |
곱하는 동안(정수) | 4배 빨라짐 |
곱하는 동안(Bignum) | 20% 느려짐 |
문자열 대문자 | 10% 더 빨라짐 |
문자열 일치 | 2% 느려짐 |
문자열 일치? | 10% 더 빨라짐 |
10,000개의 난수로 구성된 배열 | 20% 더 빨라짐 |
성능이 여기저기서 나오는 것 같지만, 이것에서 유추할 수 있는 것이 있습니다...
MJIT는 루프를 정말 좋아합니다!
그러나 더 복잡한 응용 프로그램에서는 어떻게 될까요?
간단한 Sinatra 앱으로 시도해 보겠습니다. :
require 'sinatra' get '/' do "apples, oranges & bananas" end
별 것 아닌 것 같지만 이 작은 코드는 500가지가 넘는 다양한 방법을 실행합니다. JIT가 할 일을 주기에 충분합니다!
<블록 인용>정확히 말하면 Sinatra 2.0.4 with Thin 1.7.2입니다.
다음 명령으로 벤치마크를 실행할 수 있습니다(아파치 벤치):
ab -c 20 -t 10 https://localhost:4567/
결과입니다 :
Ruby 2.6이 2.5보다 빠르지만 JIT를 활성화하면 Sinatra가 11% 느려집니다. !
왜?
잘 모르겠습니다. JIT에 의해 오버헤드가 발생하거나 코드가 제대로 최적화되지 않았기 때문일 수 있습니다.
C 프로파일러(callgrind)로 테스트한 결과 JIT 최적화 코드(이전에 발견한 컴파일된 C 파일)의 사용이 Sinatra에서 매우 낮습니다(2% 미만). ), 하지만 매우 높습니다(24.22% ) 8배 속도 향상을 가져오는 while 문에 대해.
JIT를 사용한 while 벤치마크 결과 :
JIT를 사용한 Sinatra 벤치마크 결과 :
제가 컴파일러 전문가가 아니라서 어떤 결론도 내릴 수 없는 이유 중 하나일 수 있습니다.
요약
MJIT는 Ruby 2.6에서 사용할 수 있는 "Just-in-Time 컴파일러"이며 --jit
로 활성화할 수 있습니다. 깃발. MJIT는 유망하며 일부 소규모 프로그램의 속도를 높일 수 있지만 아직 해야 할 일이 많습니다!
이 기사가 마음에 들면 Ruby 친구들과 공유하는 것을 잊지 마세요 🙂
읽어주셔서 감사합니다.