JIT를 사용하는 C ++이 JVM 또는 CLR보다 빠를 수 있다는 주장을 뒷받침하는 것은 무엇입니까? [닫은]


119

SE에서 반복되는 주제는 많은 질문에서 C ++이 Java와 같은 고급 언어보다 더 빠르고 효율적이라는 지속적인 주장입니다. 반대 의견은 현대의 JVM 또는 CLR이 JIT 덕분에 효율성이 높아지고 점점 더 많은 작업을 수행 할 수 있으며 C ++ 은 현재 수행중인 작업과 특정 방식으로 일을하는 경우에만 훨씬 효율적이라는 것입니다 성능이 향상됩니다. 그것은 명백하고 완벽합니다.

C ++에서 특정 작업이 JVM 또는 CLR보다 그리고 어떻게 더 빠르 는지에 대한 기본 설명 (이러한 경우 ...)을 알고 싶습니다 . JVM 또는 CLR이 여전히 런타임에 JIT 컴파일의 처리 오버 헤드를 가지고있는 반면 C ++은 머신 코드로 컴파일 되었기 때문입니까?

주제를 연구하려고 할 때 C ++이 고성능 컴퓨팅에 어떻게 활용 될 수 있는지 정확하게 이해하기 위해 자세한 정보없이 위에서 설명한 것과 동일한 주장입니다.


성능은 또한 프로그램의 복잡성에 달려 있습니다.
pandu

23
"C ++은 현재하고있는 작업과 특정 방식으로 작업을 수행하면 성능이 향상되는 이유를 알고있는 경우에만 훨씬 효율적입니다." 지식의 문제 일뿐만 아니라 개발자 시간의 문제이기도합니다. 최적화를 극대화하는 것이 항상 효율적인 것은 아닙니다. 프로그래머가 고도로 조정 된 최적화를 희생하면서 주어진 작업을 수행하기 위해 프로그래밍에 소비하는 시간을 줄이기 위해 Java 및 Python과 같은 고급 언어가 존재하는 이유가 여기에 있습니다.
Joel Cornett

4
@Joel Cornett : 전적으로 동의합니다. 나는 C ++보다 Java에서 확실히 더 생산적이며 정말 빠른 코드를 작성해야 할 때만 C ++을 고려합니다. 반면에 나는 C ++ 코드가 잘못 작성된 것을 보았습니다. C ++은 숙련되지 않은 프로그래머에게 유용하지 않습니다.
Giorgio

3
JIT에서 생성 할 수있는 모든 컴파일 출력은 C ++에서 생성 할 수 있지만 C ++에서 생성 할 수있는 코드가 JIT에서 생성 될 필요는 없습니다. 따라서 C ++의 기능과 성능 특성은 모든 고급 언어의 기능과 성능보다 우수합니다. QED
tylerl

1
@Doval 기술적으로 사실이지만, 일반적으로 프로그램 성능에 영향을 줄 수있는 런타임 요소를 계산할 수 있습니다. 일반적으로 두 개 이상의 손가락을 사용하지 않습니다. 최악의 경우 여러 바이너리를 제공합니다 ... 잠재적 인 속도 향상이 무시할 수 있기 때문에 그렇게 할 필요가 없다는 것을 제외하고는 아무도 신경 쓰지 않습니다.
tylerl

답변:


200

JIT가 아닌 메모리에 관한 것입니다. JIT의 'C의 이점'은 대부분 CPU BTB가 이미 열심히 노력하고있는 인라인을 통해 가상 또는 비가 상 호출을 최적화하는 것으로 제한됩니다.

현대 기계에서, RAM 액세스하는 것은 정말 (더 적은 메모리를 사용하는 경우 더 쉽게하는) 가능한 한 많은 캐시를 사용하는 응용 프로그램을 의미합니다 (CPU가하는 것을 비교) 될 수 있습니다 느린 속도 수백 번까지 보다 그 하지마 Java가 C ++보다 많은 메모리를 사용하는 방법에는 여러 가지가 있으며 캐시를 완전히 활용하는 응용 프로그램을 작성하기가 어렵습니다.

  • 각 객체에 대해 최소 8 바이트의 메모리 오버 헤드가 있으며 프리미티브 대신 객체를 사용해야하거나 많은 장소 (표준 컬렉션)에서 선호됩니다.
  • 문자열은 두 개의 객체로 구성되며 오버 헤드는 38 바이트입니다.
  • UTF-16은 내부적으로 사용됩니다. 즉, 각 ASCII 문자에는 1 개 대신 2 바이트가 필요합니다 (Oracle JVM은 최근 순수 ASCII 문자열에 대해이를 피하기 위해 최적화를 도입했습니다).
  • 집계 참조 유형 (예 : 구조체)이 없으며, 집계 참조 유형의 배열이 없습니다. Java 오브젝트 또는 Java 오브젝트 배열은 C-struct 및 배열에 비해 L1 / L2 캐시 위치가 매우 열악합니다.
  • Java 제네릭은 형식 삭제와 비교하여 캐시 위치가 열악한 형식 삭제를 사용합니다.
  • 객체 할당은 불투명하며 각 객체에 대해 개별적으로 수행되어야하므로 애플리케이션이 데이터를 캐시 친화적 인 방식으로 의도적으로 배치하고 여전히 구조화 된 데이터로 취급하는 것은 불가능합니다.

캐시와 관련이없는 다른 메모리 :

  • 스택 할당이 없으므로 작업하는 기본이 아닌 모든 데이터는 힙에 있어야하며 가비지 수집을 거쳐야합니다 (일부 최신 JIT는 경우에 따라 배후에서 스택 할당을 수행함).
  • 집계 참조 유형이 없으므로 집계 참조 유형의 스택 전달이 없습니다. (Vector 인수의 효율적인 전달을 생각하십시오)
  • 가비지 콜렉션은 L1 / L2 캐시 컨텐츠를 손상시킬 수 있으며, GC 세계 일주 일시 정지는 상호 작용을 손상시킵니다.
  • 데이터 유형간에 변환하려면 항상 복사가 필요합니다. 소켓에서 가져온 많은 바이트에 대한 포인터를 가져 와서 부동 소수점으로 해석 할 수 없습니다.

이러한 것 중 일부는 트레이드 오프 (수동 메모리 관리를 수행하지 않아도 대부분의 사람들에게 많은 성능을 포기할 가치가 있음 )이며, 일부는 아마도 Java를 단순하게 유지하려는 노력의 결과 일 수 있으며 일부는 설계 실수입니다 (후방에서만 가능할 수도 있음) 즉, UTF-16은 Java가 작성 될 때 고정 길이 인코딩이므로이를 선택하는 결정이 훨씬 이해하기 쉬워집니다).

이러한 트레이드 오프의 많은 부분이 Java / JVM의 경우 C # / CIL의 경우와는 매우 다릅니다. .NET CIL에는 참조 유형 구조체, 스택 할당 / 통과, 압축 된 구조체 배열 및 형식 기반 제네릭이 있습니다.


37
+1-전반적으로 좋은 답변입니다. 그러나 "스택 할당이 없습니다"글 머리 기호가 완전히 정확한지 확실하지 않습니다. Java JIT는 가능한 경우 스택 할당을 허용하기 위해 이스케이프 분석을 수행합니다. 아마도 Java 언어는 프로그래머가 객체가 스택 할당 대 힙 할당 시점을 결정할 수 없다는 것입니다. 또한 모든 최신 JVM이 사용하는 세대 가비지 수집기가 사용중인 경우 "힙 할당"은 C ++ 환경에서와 완전히 다른 것 (성능이 완전히 다른 것)을 의미합니다.
Daniel Pryden

5
나는 다른 두 가지가 있다고 생각하지만 대부분 훨씬 높은 수준의 물건으로 작업하므로 내가 틀렸다고 말하십시오. 실제로 메모리에서 일어나는 일과 머신 코드가 실제로 어떻게 작동하는지에 대한 일반적인 인식을 개발하지 않으면 C ++을 작성할 수 없으며 스크립팅 또는 가상 머신 언어는주의를 기울이지 않는 모든 것을 추상화합니다. 또한 VM 또는 해석 언어에서는 작업이 작동하는 방식을 훨씬 더 세밀하게 제어하여 핵심 라이브러리 작성자가 지나치게 구체적인 시나리오에 최적화했을 수 있습니다.
Erik Reppen

18
+1. 추가 할 한 가지 더 (하지만 새로운 답변을 제출하지 않으려 고 함) Java의 배열 색인에는 항상 경계 검사가 포함됩니다. C 및 C ++에서는 그렇지 않습니다.
riwalk

7
Java의 힙 할당은 내부 풀링 및 사물로 인해 C ++가있는 순진 버전보다 훨씬 빠르지 만 C ++의 메모리 할당 은 수행중인 작업을 알고 있으면 훨씬 더 나을 있습니다.
Brendan Long

10
@BrendanLong, true .. 그러나 메모리가 깨끗한 경우에만-일단 앱이 잠시 실행되면 GC가 필요하기 때문에 메모리 할당이 느려집니다. 콤팩트. 벤치 마크에는 이점이 있지만 (IMHO) 전체적으로 앱 속도가 느려지는 단점이 있습니다.
gbjbaanb

67

단순히 C ++이 어셈블리 / 기계 코드로 컴파일되는 반면 Java / C #은 여전히 ​​런타임에 JIT 컴파일의 처리 오버 헤드를 가지고 있기 때문입니까?

부분적으로, 그러나 일반적으로 환상적인 JIT 컴파일러를 가정 할 때 적절한 C ++ 코드는 여전히 두 가지 주요 이유로 Java 코드보다 더 나은 성능을 보이는 경향이 있습니다.

1) C ++ 템플릿은 일반 적이고 효율적인 코드 작성을위한 더 나은 기능을 제공 합니다 . 템플릿은 C ++ 프로그래머에게 ZERO 런타임 오버 헤드가있는 매우 유용한 추상화를 제공합니다. (템플릿은 기본적으로 컴파일 타임 덕 타이핑입니다.) 반대로 Java 제네릭을 사용하면 가장 좋은 것은 기본적으로 가상 함수입니다. 가상 함수는 항상 런타임 오버 헤드를 가지며 일반적으로 인라인 될 수 없습니다.

일반적으로 Java, C # 및 C를 포함한 대부분의 언어를 사용하면 효율성과 일반성 / 추상화 중에서 선택할 수 있습니다. C ++ 템플릿은 컴파일 시간이 길어질 수 있습니다.

2) C ++ 표준이 컴파일 된 C ++ 프로그램의 이진 레이아웃에 대해 말할 것도 많지 않다는 사실은 C ++ 컴파일러에게 Java 컴파일러보다 훨씬 많은 여유를 부여하여 더 나은 최적화를 허용합니다 (때로는 디버깅이 더 어려워집니다). 실제로 Java 언어 사양의 특성상 특정 영역에서 성능 저하가 발생합니다. 예를 들어 Java에서 연속적인 객체 배열을 가질 수 없습니다. 연속 된 객체 포인터 배열 만 가질 수 있습니다(참조), Java에서 배열을 반복하면 항상 간접 비용이 발생합니다. 그러나 C ++의 값 의미론은 연속 배열을 가능하게합니다. 또 다른 차이점은 C ++은 스택에 객체를 할당 할 수 있지만 Java는 실제로 대부분의 C ++ 프로그램이 스택에 객체를 할당하는 경향이 있기 때문에 할당 비용이 종종 0에 가깝다는 것을 의미하지는 않습니다.

C ++이 Java보다 뒤처 질 수있는 영역 중 하나는 많은 작은 개체를 힙에 할당해야하는 상황입니다. 이 경우 Java GC가 대량 할당 해제를 가능하게하기 때문에 Java의 가비지 수집 시스템이 표준 newdeleteC ++ 보다 성능이 향상 될 수 있습니다. 그러나 C ++ 프로그래머는 메모리 풀 또는 슬랩 할당자를 사용하여이를 보완 할 수 있지만, Java 프로그래머는 Java 런타임에 최적화되지 않은 메모리 할당 패턴에 직면 할 때 도움이되지 않습니다.

또한 이 주제에 대한 자세한 내용 은 이 훌륭한 답변 을 참조하십시오.


6
좋은 대답이지만 한 가지 사소한 점 : "C ++ 템플릿을 사용하면 컴파일 시간이 길어집니다."프로그램 크기가 더 커집니다. 항상 문제가되는 것은 아니지만 모바일 장치 용으로 개발하는 경우 문제가 될 수 있습니다.
Leo

9
@luiscubal : 아니요, 이런 점에서 C # 제네릭은 자바와 매우 유사합니다 (어떤 유형이 전달 되더라도 동일한 "일반"코드 경로가 사용됩니다). C ++ 템플릿에 대한 요령은 그것이 적용되는 모든 유형. 그래서 std::vector<int>설계된 동적 배열입니다 단지 의 int를 위해, 그리고 컴파일러는 적절하게 최적화 할 수 있습니다. AC # List<int>은 여전히 ​​단지입니다 List.
jalf

12
@jalf C 번호는 List<int>를 사용 int[],하지 Object[]같은 자바는 않습니다. stackoverflow.com/questions/116988/…
luiscubal

5
@ luiscubal : 용어가 명확하지 않습니다. JIT는 내가 "컴파일 타임"이라고 생각한대로 행동하지 않습니다. 물론, 당신은 충분히 영리하고 공격적인 JIT 컴파일러를 고려할 때 옳습니다. 그러나 C ++ 에는 이 동작이 필요 합니다. 또한 C ++ 템플릿을 사용하면 프로그래머가 명시 적 전문화를 지정하여 해당되는 경우 추가 명시 적 최적화를 수행 할 수 있습니다. C #에는 이와 동등한 기능이 없습니다. 예를 들어, C ++에, 나는 정의 할 수 vector<N>의 특정 경우에, 어디에 vector<4>사용되어야한다, 내 손으로 코딩 SIMD 구현
jalf

5
@ 레오 : 템플릿을 통한 코드 팽창은 15 년 전에 문제였습니다. 강력한 템플릿 화 및 인라이닝과 이후에 동일한 인스턴스 접기와 같은 기능 컴파일러 를 사용하여 오늘날 템플릿을 통해 많은 코드가 작아 집니다.
sbi

46

다른 답변 (6까지)은 언급하지 않았지만 이것에 대답하기 위해 매우 중요하다고 생각하는 것은 Stroustrup이 # 1 일부터 공식화하고 사용 한 C ++의 매우 기본적인 디자인 철학 중 하나입니다.

사용하지 않는 것에 대해서는 비용을 지불하지 않습니다.

C ++를 크게 형성 한 다른 중요한 기본 설계 원칙이 있지만 (특정 패러다임에 강요해서는 안 됨) 사용 하지 않는 것에 대한 비용을 지불하지 않는 것이 가장 중요한 것입니다.


Stroustrup은 자신의 저서 C ++의 디자인과 진화 (일반적으로 [D & E]라고 함)에서 C ++을 처음부터 만들게했던 필요성을 설명합니다. 내 자신의 말로 : 박사 학위 논문 (네트워크 시뮬레이션, IIRC와 관련이있는 것)을 위해 SIMULA에서 시스템을 구현했습니다.이 언어는 그가 생각을 코드로 직접 표현할 수있어서 매우 좋았 기 때문에 많은 것을 좋아했습니다. 그러나 결과 프로그램은 너무 느리게 실행되었고 학위를 받기 위해 C의 전신 인 BCPL로 다시 작성했습니다. BCPL에서 코드를 작성하는 것은 고통 스럽지만 결과 프로그램은 충분히 빠릅니다. 그는 박사 학위를 마칠 수있었습니다.

그 후, 그는 실제 문제를 가능한 한 직접 코드로 변환 할 수 있고 코드의 효율성을 높일 수있는 언어를 원했습니다.
이를 위해 그는 나중에 C ++가 될 것을 만들었습니다.


그래서 위에 인용 목표는 그것은 아주 가까이, 몇 가지 기본적인 기본 설계 원칙 중 단지 하나가 아닌 존재 이유 C에 대한 ++. 또한 언어의 어느 곳에서나 찾을 수 있습니다. 함수는 virtual원하는 때에 만 (가상 함수를 호출하면 약간의 오버 헤드가 발생하기 때문에) POD는 명시 적으로 요청할 때만 자동으로 초기화됩니다. 스택 프레임의 설정 / 정리를 매우 저렴하게하는 것이 명백한 디자인 목표 인 반면, GC는 필요할 때마다 실행되지 않습니다.

C 명시 적으로 당신에게 편의를 제공하지 않는 것을 선택 ++ ( "내가 여기에이 방법이 가상 수 있도록해야합니까?") 성능 교환 ( "아니, 내가하지, 지금은 컴파일러가 수에 inline그것과는 밖으로 지옥을 최적화 놀랍지 않게도, 이것은 더 편리한 언어에 비해 성능이 향상되었습니다.


4
사용하지 않는 것에 대해서는 비용을 지불하지 않습니다. => 그리고 그들은 RTTI를 추가했습니다 :(
Matthieu M.

11
@Matthieu : 귀하의 감정을 이해하는 동안, 도움이 될 수는 있지만 실적과 관련하여 추가 된 내용도 있습니다. RTTI는 가상 테이블을 사용하여 구현할 수 있도록 지정되므로 사용하지 않으면 오버 헤드가 거의 발생하지 않습니다. 다형성을 사용하지 않으면 비용이 전혀 들지 않습니다. 뭔가 빠졌습니까?
sbi

9
@Matthieu : 물론 이유가 있습니다. 그러나이 이유는 합리적입니까? 내가 볼 수 있듯이 "RTTI 비용"은 사용하지 않을 경우 모든 다형성 클래스의 가상 테이블에 추가 포인터로, 어딘가에 정적으로 할당 된 일부 RTTI 객체를 가리 킵니다. 토스터에 칩을 프로그래밍하고 싶지 않다면 어떻게 관련이 있을까요?
SBI

4
@Aaronaught : 나는 그것에 대답하는 것에 대해 상실했습니다. Stroustrup et al이 이러한 방식과 기능을 개별적으로 나열하는 대신 성능을 허용하는 방식으로 기능을 추가하게 한 기본 철학을 지적했기 때문에 내 대답을 정말로 무시 했습니까?
sbi

9
@Aaronaught : 당신은 내 동정심이 있습니다.
sbi

29

해당 주제에 대한 Google 연구 논문 을 알고 있습니까?

결론에서 :

우리는 성능과 관련하여 C ++이 큰 차이로이기는 것을 발견했습니다. 그러나 가장 광범위한 튜닝 노력이 필요했으며,이 중 대부분은 일반 프로그래머가 사용할 수없는 수준의 정교함에서 수행되었습니다.

이것은 "실제 C ++ 컴파일러는 경험적인 측정에 의해 Java 컴파일러보다 더 빠른 코드를 생성하기 때문"이라는 의미에서 적어도 부분적으로 설명입니다.


4
메모리 및 캐시 사용량 차이 외에도 가장 중요한 것 중 하나는 수행 된 최적화의 양입니다. Java HotSpot 컴파일러와 관련하여 GCC / LLVM (및 아마도 Visual C ++ / ICC)의 최적화 횟수를 비교하십시오. JIT 컴파일러는 일반적으로 이러한 적극적인 최적화 시간이 없으며 사용 가능한 런타임 정보를 사용하여 더 잘 구현할 수 있다고 생각했습니다.
Gratian Lup

2
@GratianLup : LTO가 (아직도) 사실인지 궁금합니다.
중복 제거기

2
@GratianLup : C ++에 대한 프로파일 가이드 최적화를 잊지 말자 ...
Deduplicator

23

이것은 귀하의 질문과 중복되지는 않지만 허용되는 답변은 대부분의 질문에 대한 답변 입니다. Java에 대한 최신 검토

요약하면 :

기본적으로 Java의 의미는 C ++보다 느린 언어임을 나타냅니다.

따라서 C ++을 비교하는 다른 언어에 따라 같은 대답을 얻거나 얻지 못할 수 있습니다.

C ++에는 다음이 있습니다.

  • 스마트 한 인라인 기능
  • 지역성이 강한 일반 코드 생성 (템플릿)
  • 가능한 작고 컴팩트 한 데이터
  • 간접적 인 지시를 피할 수있는 기회
  • 예측 가능한 메모리 동작
  • 높은 수준의 추상화 (템플릿)를 사용하기 때문에 컴파일러 최적화 가능

다음은 언어 정의의 기능 또는 부작용으로 다음과 같은 언어보다 메모리 및 속도에서 이론적으로 더 효율적입니다.

  • 간접적으로 대량 사용 ( "모든 것이 관리되는 참조 / 포인터"언어 임) : 간접이라는 의미는 필요한 데이터를 얻기 위해 CPU가 메모리에서 점프해야 함을 의미하며 CPU 캐시 실패를 증가시켜 처리 속도를 느리게합니다. 작은 데이터를 C ++로 가질 수있는 경우에도 많이;
  • 구성원이 간접적으로 액세스하는 큰 크기의 객체를 생성합니다. 이것은 기본적으로 참조를 갖는 결과이며, 구성원은 포인터이므로 회원을 얻을 때 부모 객체의 코어에 가까운 데이터를 얻지 못하고 캐시 누락을 다시 트리거 할 수 있습니다.
  • 가버 지 컬렉터 사용 : 성능 예측 가능성을 의도적으로 불가능하게합니다 (설계 상).

컴파일러의 C ++ 공격적인 인라이닝은 많은 간접적 인 명령을 줄이거 나 없앱니다. 작은 압축 데이터 세트를 생성하는 기능은 이러한 데이터를 함께 묶지 않고 메모리 전체에 분산시키지 않으면 캐시 친화적입니다 (두 가지 모두 가능합니다. C ++로 선택 가능). RAII는 C ++ 메모리 동작을 예측 가능하게하여 고속이 필요한 실시간 또는 반 실시간 시뮬레이션의 경우 많은 문제를 제거합니다. 지역성 문제는 일반적으로 다음과 같이 요약 할 수 있습니다. 프로그램 / 데이터가 작을수록 실행 속도가 빠릅니다. C ++은 데이터가 원하는 위치 (풀, 배열 또는 기타)에 있고 컴팩트하도록 다양한 방법을 제공합니다.

분명히 똑같이 할 수있는 다른 언어가 있지만 C ++만큼 많은 추상화 도구를 제공하지 않기 때문에 널리 사용되지 않으므로 많은 경우에 덜 유용합니다.


7

그것은 약간의 JIT 비효율이 추가 된 메모리 (Michael Borgwardt가 말했듯이)에 관한 것입니다.

언급되지 않은 것은 캐시입니다. 캐시를 완전히 사용하려면 데이터를 연속적으로 (즉, 모두 함께) 배치해야합니다. 이제 GC 시스템을 사용하면 메모리가 GC 힙에 할당되는데 이는 빠르지 만 메모리가 사용됨에 따라 GC는 정기적으로 시작하여 더 이상 사용되지 않는 블록을 제거한 다음 나머지를 함께 압축합니다. 이제 사용 된 블록을 함께 이동시키는 속도가 느려지는 것을 제외하고는 사용중인 데이터가 서로 붙어 있지 않을 수 있습니다. 1000 개의 요소 배열이있는 경우 한 번에 모든 요소를 ​​할당 한 다음 (새 끝에 작성하여 새 요소를 작성하지 않고 내용을 업데이트하지 않은 경우) 힙 전체에 흩어집니다. 따라서 여러 개의 메모리 적중이 CPU 캐시로 모두 읽히도록 요구합니다. AC / C ++ 앱은 이러한 요소에 메모리를 할당 한 다음 데이터로 블록을 업데이트합니다. (OK, GC 메모리 할당과 비슷하게 동작하는 목록과 같은 데이터 구조가 있지만 사람들은 벡터보다 느리다는 것을 알고 있습니다).

StringBuilder 객체를 String으로 바꾸면 간단히 작동 상태를 확인할 수 있습니다. Stringbuilders는 메모리를 미리 할당하고 채워서 작동하며, Java / .NET 시스템에 알려진 성능 트릭입니다.

'오래된 파일을 삭제하고 새 사본을 할당하십시오'패러다임은 Java / C #에서 매우 많이 사용된다는 것을 잊지 마십시오. 사람들은 GC로 인해 메모리 할당이 실제로 빠르다는 사실을 듣고, 흩어져있는 메모리 모델은 모든 곳에서 사용됩니다. 물론 문자열 빌더를 제외하고) 모든 라이브러리는 메모리를 낭비하고 많은 메모리를 사용하는 경향이 있으며 그중 어느 것도 연속성의 이점을 얻지 못합니다. 이것 때문에 GC 주위의 과대 광고를 비난하십시오-그들은 당신에게 기억이 자유 롭다고 말했습니다.

GC 자체는 분명히 또 다른 perf 적중입니다-실행될 때 힙을 스윕해야 할뿐만 아니라 사용되지 않은 모든 블록을 해제해야하며 종료자를 실행해야합니다 (이것은 별도로 수행해야했지만) 다음에 응용 프로그램이 중단 된 상태로) 블록의 새 위치에 대한 참조를 업데이트합니다. 보시다시피, 많은 작업!

C ++ 메모리에 대한 성능 저하는 메모리 할당으로 귀결됩니다. 새로운 블록이 필요할 때 힙이 깊게 조각난 힙으로 충분히 큰 다음 여유 공간을 찾으십시오. 이것은 GC만큼 빠르지 않습니다. '끝에 다른 블록을 할당하십시오.'하지만 GC 압축이 수행하는 모든 작업만큼 느리지는 않으며 여러 고정 크기 블록 힙 (메모리 풀이라고도 함)을 사용하여 완화 할 수 있다고 생각합니다.

보안 검사가 필요한 GAC에서 어셈블리를로드하고 프로브 경로 ( sxstrace를 켜고 상황을 확인하십시오!)와 java / .net에서 훨씬 더 인기가있는 일반적인 다른 오버 엔지니어링과 같은 것이 더 있습니다. C / C ++보다.


2
당신이 쓰는 많은 것들이 현대 세대 가비지 수집가에게는 사실이 아닙니다.
Michael Borgwardt

3
@MichaelBorgwardt와 같은? "GC가 정기적으로 실행된다"고 말하고 "힙을 압축합니다". 나머지 답변은 응용 프로그램 데이터 구조가 메모리를 사용하는 방법에 관한 것입니다.
gbjbaanb

6

"C ++이 어셈블리 / 머신 코드로 컴파일되는 반면 Java / C #은 여전히 ​​런타임에 JIT 컴파일의 처리 오버 헤드를 가지고 있기 때문입니까?" 기본적으로 그렇습니다!

그러나 Java는 JIT 컴파일보다 많은 오버 헤드를 가지고 있습니다. 예를 들어, 훨씬 더 (이 같은 일을 어떻게하는 당신을 위해 확인 않습니다 ArrayIndexOutOfBoundsExceptionsNullPointerExceptions). 가비지 수집기는 또 다른 중요한 오버 헤드입니다.

여기에 꽤 자세한 비교가 있습니다 .


2

다음은 기본 컴파일과 JIT 컴파일의 차이점 만 비교하며 특정 언어 또는 프레임 워크의 세부 사항은 다루지 않습니다. 이를 넘어서 특정 플랫폼을 선택해야하는 합당한 이유가있을 수 있습니다.

네이티브 코드가 더 빠르다고 주장 할 때 네이티브 컴파일 된 코드와 JIT 컴파일 된 코드 의 일반적인 사용 사례 에 대해 이야기하고 있습니다. 여기서 JIT 컴파일 된 응용 프로그램의 일반적인 사용은 사용자가 즉시 수행해야합니다 (예 : 먼저 컴파일러를 기다리는 중). 이 경우 JIT 컴파일 코드가 네이티브 코드와 일치하거나 이길 수 있다고 누군가가 똑바로 주장 할 수 있다고 생각하지 않습니다.

어떤 언어 X로 작성된 프로그램이 있다고 가정하고 네이티브 컴파일러로 컴파일하고 다시 JIT 컴파일러로 컴파일 할 수 있다고 가정 해 봅시다. 각 작업 흐름에는 동일한 단계가 있으며 (Code-> Intermediate Representation-> Machine Code-> Execution)으로 일반화 할 수 있습니다. 두 가지의 큰 차이점은 사용자가 보는 단계와 프로그래머가 보는 단계입니다. 기본 컴파일을 사용하면 프로그래머는 실행 단계를 제외한 모든 것을 볼 수 있지만 JIT 솔루션을 사용하면 실행 외에도 머신 코드에 대한 컴파일이 사용자에게 표시됩니다.

A가 B보다 빠르다는 주장 은 사용자가 본 것처럼 프로그램을 실행하는 데 걸린 시간을 말합니다 . Execution 단계에서 두 코드가 동일하게 수행된다고 가정하면 JIT 작업 흐름이 사용자에게 느리다고 가정해야합니다. 머신 코드에 대한 컴파일 시간 T도 T> 0이어야합니다. , JIT 작업 흐름이 기본 작업 흐름과 동일하게 수행 될 가능성이있을 경우, 사용자는 코드 실행 시간을 줄여서 실행 + 머신 코드로의 컴파일이 실행 단계보다 낮도록해야합니다. 기본 작업 흐름 즉, JIT 컴파일에서 원시 컴파일보다 코드를 더 잘 최적화해야합니다.

그러나 실행 속도를 높이기 위해 필요한 최적화를 수행하기 때문에 기계 코드 단계로 컴파일하는 데 더 많은 시간을 소비해야하므로 최적화 된 코드의 결과로 절약 할 때마다 실제로 손실됩니다. 컴파일에 추가합니다. 즉, JIT 기반 솔루션의 "느림"은 JIT 컴파일 시간이 추가 된 것이 아니라 해당 컴파일에서 생성 된 코드가 기본 솔루션보다 느리게 수행됩니다.

예제를 사용하겠습니다 : 레지스터 할당. 메모리 액세스는 레지스터 액세스보다 수천 배 느리므로 가능한 한 레지스터를 사용하고 가능한 한 적은 메모리 액세스를 원하지만 레지스터 수가 제한되어 있으며 필요할 때 상태를 메모리에 넘겨야합니다 레지스터. 계산에 200ms가 걸리는 레지스터 할당 알고리즘을 사용하면 결과적으로 2ms의 실행 시간을 절약 할 수 있습니다. JIT 컴파일러에 시간을 최대한 활용하지는 않습니다. 고도로 최적화 된 코드를 생성 할 수있는 Chaitin 알고리즘과 같은 솔루션은 적합하지 않습니다.

JIT 컴파일러의 역할은 컴파일 시간과 생성 된 코드의 품질 사이에서 최상의 균형을 유지하는 것입니다. 그러나 사용자를 기다리지 않기 때문에 빠른 컴파일 시간에 큰 편향이 있습니다. JIT의 경우 코드 최적화에서 시간이 지남에 따라 원시 컴파일러가 바인딩되지 않기 때문에 실행중인 코드의 성능이 느려집니다. 따라서 최상의 알고리즘을 자유롭게 사용할 수 있습니다. JIT 컴파일러에 대한 전체 컴파일 + 실행이 기본적으로 컴파일 된 코드의 실행 시간 만 이길 수있는 가능성은 사실상 0입니다.

그러나 VM은 단순히 JIT 컴파일에만 국한되지 않습니다. Ahead-of-time 컴파일 기술, 캐싱, 핫 스와핑 및 적응 형 최적화를 사용합니다. 따라서 성능이 사용자가 보는 것임을 주장을 수정하고 프로그램을 실행하는 데 걸리는 시간으로 제한합니다 (AOT가 컴파일되었다고 가정). 우리는 실행 코드를 네이티브 컴파일러와 동등하게 만들 수 있습니다. VM에 대한 큰 주장은 특정 함수가 얼마나 자주 실행될 수 있는지와 같은 실행중인 프로세스의 정보에 더 많은 정보에 액세스 할 수 있기 때문에 원시 컴파일러보다 우수한 품질의 코드를 생성 할 수 있다는 것입니다. 그런 다음 VM은 핫 스와핑을 통해 가장 필수적인 코드에 적응 형 최적화를 적용 할 수 있습니다.

이 인수에는 문제가 있습니다. 프로파일 가이드 최적화 등은 VM 고유의 것으로 가정하지만 사실이 아닙니다. 프로파일 링을 사용하여 응용 프로그램을 컴파일하고 정보를 기록한 다음 해당 프로파일을 사용하여 응용 프로그램을 다시 컴파일하여 원시 컴파일에도 적용 할 수 있습니다. 코드 핫 스와핑은 JIT 컴파일러 만이 할 수있는 것이 아니며, 네이티브 코드에 대해서도 할 수 있다는 점을 지적 할 가치가 있습니다. JIT 기반 솔루션은 더 쉽게 사용할 수 있고 개발자에게는 훨씬 쉽습니다. 가장 큰 질문은 VM이 네이티브 컴파일이 할 수없는 정보를 제공 할 수 있습니까? 그러면 코드 성능이 향상 될 수 있습니까?

나는 그것을 스스로 볼 수 없다. 프로세스가 더 복잡하지만 일반적인 VM의 기술 대부분을 네이티브 코드에도 적용 할 수 있습니다. 마찬가지로 AOT 컴파일 또는 적응 형 최적화를 사용하는 VM에 기본 컴파일러의 최적화를 다시 적용 할 수 있습니다. 실제로는 기본적으로 실행되는 코드와 VM에서 실행되는 코드의 차이가 생각보다 크지 않다는 것입니다. 그들은 궁극적으로 같은 결과를 가져 오지만 거기에 도달하기 위해 다른 접근법을 취합니다. VM은 반복적 인 접근 방식을 사용하여 최적화 된 코드를 생성하며, 네이티브 컴파일러는 처음부터이를 기대하고 반복적 접근 방식으로 개선 할 수 있습니다.

C ++ 프로그래머는 시작부터 최적화가 필요하다고 주장 할 수 있으며 VM이이를 수행하는 방법을 알아낼 때까지 기다리지 말아야합니다. VM의 현재 최적화 수준이 네이티브 컴파일러가 제공 할 수있는 것보다 열등하기 때문에 현재 기술에서는 유효 할 수 있습니다. 그러나 VM의 AOT 솔루션이 개선되는 경우에는 항상 그렇지는 않습니다.


0

이 기사 는 고성능 코드를 얻기 위해 c ++ 대 c #의 속도와 두 언어 모두에서 극복해야하는 문제를 비교하려는 블로그 게시물 세트를 요약 한 것입니다. 요약은 '당신의 라이브러리는 무엇보다 중요하지만 c ++에 있다면 그것을 극복 할 수 있습니다.' 또는 '현대 언어는 더 나은 라이브러리를 가지고 있으므로 철학적 인 기울기에 따라 더 적은 노력으로 더 빠른 결과를 얻습니다'.


0

여기서 실제 질문은 "어느 쪽이 더 빠르지?"라고 생각하지 않습니다. "고성능을위한 최고의 잠재력은 무엇입니까?" 이러한 용어로 볼 때 C ++은 분명히 승리합니다-네이티브 코드로 컴파일되고 JITting이 없으며 추상화 수준이 낮습니다.

그것은 전체 이야기와는 거리가 멀다.

C ++는 컴파일되므로 컴파일시 모든 컴파일러 최적화를 수행해야하며 한 시스템에 적합한 컴파일러 최적화가 다른 시스템에 대해 완전히 틀릴 수 있습니다. 또한 모든 글로벌 컴파일러 최적화가 특정 알고리즘이나 코드 패턴을 다른 알고리즘보다 선호 할 수 있으며 선호 할 것입니다.

반면에 JITted 프로그램은 JIT 시간에 최적화되므로 사전 컴파일 된 프로그램이 실제로 실행중인 시스템과 실제로 실행중인 코드에 대해 매우 구체적인 최적화를 수행 할 수없는 몇 가지 트릭을 가져올 수 있습니다. JIT의 초기 오버 헤드를 지나면 경우에 따라 더 빠를 수도 있습니다.

두 경우 모두 알고리즘의 현명한 구현과 어리석지 않은 프로그래머의 다른 인스턴스가 훨씬 더 중요한 요소가 될 것입니다. 해석 된 스크립팅 언어


3
"한 기계에 적합한 컴파일러 최적화는 다른 기계에 대해 완전히 틀릴 수 있습니다"글쎄, 그건 언어를 비난하지 않습니다. 실제로 성능에 중요한 코드는 실행될 각 컴퓨터마다 별도로 컴파일 할 수 있습니다. 이는 소스 ( -march=native) 에서 로컬로 컴파일하는 경우에는 쉬운 일이 아닙니다 . — "낮은 수준의 추상화"는 사실이 아닙니다. C ++은 자바처럼 높은 수준의 추상화를 사용합니다 (또는 실제로는 높은 수준의 함수 : 함수형 프로그래밍? 템플릿 메타 프로그래밍?). Java보다 추상화를 덜 "깨끗하게"구현합니다.
leftaroundabout

"실제로 성능이 중요한 코드는 실행될 각 머신에 대해 개별적으로 컴파일 할 수 있습니다. 소스에서 로컬로 컴파일 할 경우 당연한 일입니다."-최종 사용자도 프로그래머라는 기본 가정 때문에 실패합니다.
Maximus Minimus

반드시 최종 사용자 일 필요는 없으며 프로그램 설치 책임자 만 있습니다. 데스크톱 및 모바일 장치에서 그것은 일반적 입니다 최종 사용자 만이이있다 확실히 가장 성능이 중요한 사람 만 응용 프로그램이 아니다. 또한 모든 훌륭한 무료 / 공개 소프트웨어 프로젝트와 같이 빌드 스크립트를 올바르게 작성했다면 소스에서 프로그램을 빌드하기 위해 프로그래머 가 될 필요는 없습니다 .
leftaroundabout

1
이론적으로는 JIT가 정적 컴파일러보다 더 많은 트릭을 끌어낼 수 있지만 실제로는 .NET의 경우 적어도 Java를 알지 못하지만 실제로는 아무것도하지 않습니다. 최근에 .NET JIT 코드를 여러 번 해체했으며 .NET JIT가 단순히 수행하지 않는 루프에서 코드 게양, 데드 코드 제거 등과 같은 모든 종류의 최적화가 있습니다. 마이크로 소프트의 윈도우즈 팀이 수년 동안 .NET을 죽이려고했기 때문에 숨
Orion Edwards

-1

JIT 컴파일은 실제로 성능에 부정적인 영향을 미칩니다. "완벽한"컴파일러와 "완벽한"JIT 컴파일러를 설계하면 첫 번째 옵션이 항상 성능면에서 우수합니다.

Java와 C #은 중간 언어로 해석 된 다음 런타임시 기본 코드로 컴파일되므로 성능이 저하됩니다.

그러나 C #의 차이점은 분명하지 않습니다. Microsoft CLR은 CPU마다 다른 고유 코드를 생성하므로 컴퓨터에서 실행하는 코드를 더 효율적으로 만들 수 있습니다. 이는 항상 C ++ 컴파일러에서 수행하는 것은 아닙니다.

PS C #은 매우 효율적으로 작성되었으며 추상화 계층이 많지 않습니다. Java의 경우에는 그렇지 않으며 그렇게 효율적이지 않습니다. 따라서이 경우 CLR을 사용하면 C # 프로그램이 C ++ 프로그램보다 성능이 더 좋습니다. .Net 및 CLR에 대한 자세한 내용은 Jeffrey Richter의 "CLR via C #"을 참조하십시오 .


8
JIT가 실제로 성능에 부정적인 영향을 미친다면 반드시 사용되지 않습니까?
Zavior

2
@ Zavior-귀하의 질문에 대한 좋은 대답을 생각할 수 없지만 JIT가 성능 오버 헤드를 추가 할 수없는 방법을 보지 못합니다-JIT는 런타임에 완료 해야하는 추가 프로세스로 필요한 리소스가 필요합니다. 완전히 컴파일 된 언어는 '준비가되었습니다'.
익명

3
JIT는 컨텍스트에 넣으면 성능에 부정적인 영향이 아니라 성능에 긍정적 인 영향을 미칩니다 . 실행하기 전에 바이트 코드를 기계 코드로 컴파일 합니다. 결과를 캐싱하여 해석되는 동등한 바이트 코드보다 빠르게 실행할 수 있습니다.
Casey Kuball 14

3
JIT (또는 바이트 코드 접근 방식)는 성능이 아니라 편의를 위해 사용됩니다. 각 플랫폼 (또는 각 플랫폼에 대해 차선책 인 공통 서브 세트)에 대한 사전 빌드 바이너리 대신, 절반 만 컴파일하고 JIT 컴파일러가 나머지를 수행하게하십시오. '한 번만 쓰고 어디서나 배포하십시오'가 이런 식으로 된 이유입니다. 편의 그냥 바이트 코드 인터프리터가있을 수 있지만, 미리 컴파일 된 솔루션을 이길 반드시 충분히 빨리 비록 JIT는 (원시 인터프리터보다 더 빨리 그것을 만드는 않습니다 JIT 컴파일이 수행 시간을, 그 결과는 항상하지 않습니다 그것을 위해).
tdammers

4
@Tdammmers, 실제로 성능 구성 요소도 있습니다. java.sun.com/products/hotspot/whitepaper.html을 참조하십시오 . 최적화에는 분기 예측 및 캐시 적중을 개선하기위한 동적 조정, 동적 인라이닝, 가상화 해제, 경계 검사 비활성화 및 루프 언 롤링과 같은 것들이 포함될 수 있습니다. 많은 경우 JIT 비용을 지불하는 것 이상을 주장 할 수 있습니다.
찰스 E. 그랜트
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.