[c#] C # 대 C-큰 성능 차이

C anc C #에서 유사한 코드간에 엄청난 성능 차이를 찾고 있습니다.

C 코드는 다음과 같습니다.

#include <stdio.h>
#include <time.h>
#include <math.h>

main()
{
    int i;
    double root;

    clock_t start = clock();
    for (i = 0 ; i <= 100000000; i++){
        root = sqrt(i);
    }
    printf("Time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);

}

그리고 C # (콘솔 앱)은 다음과 같습니다.

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            DateTime startTime = DateTime.Now;
            double root;
            for (int i = 0; i <= 100000000; i++)
            {
                root = Math.Sqrt(i);
            }
            TimeSpan runTime = DateTime.Now - startTime;
            Console.WriteLine("Time elapsed: " + Convert.ToString(runTime.TotalMilliseconds/1000));
        }
    }
}

위 코드를 사용하면 C #이 0.328125 초 (릴리스 버전)에 완료되고 C가 실행되는 데 11.14 초가 걸립니다.

c는 mingw를 사용하여 실행 가능한 Windows로 컴파일됩니다.

저는 항상 C / C ++가 더 빠르거나 적어도 C # .net과 비슷하다는 가정하에있었습니다. C가 30 배 이상 느리게 실행되는 원인은 정확히 무엇입니까?

편집 : C # 최적화 프로그램이 사용되지 않았기 때문에 루트를 제거하는 것처럼 보입니다. 루트 할당을 루트 + =로 변경하고 끝에 합계를 인쇄했습니다. 또한 최대 속도로 설정된 / O2 플래그와 함께 cl.exe를 사용하여 C를 컴파일했습니다.

결과는 다음과 같습니다. C의 경우 3.75 초 C #의 경우 2.61 초

C는 여전히 더 오래 걸리지 만 이것은 허용됩니다



답변

‘root’를 사용하지 않았으므로 컴파일러가 메서드를 최적화하기 위해 호출을 제거했을 수 있습니다.

제곱근 값을 누산기에 누적하고 메서드의 끝에서 인쇄하고 무슨 일이 일어나는지 볼 수 있습니다.

편집 : 아래 Jalf의 답변 참조


답변

디버그 빌드를 비교해야합니다. 방금 C 코드를 컴파일하고

Time elapsed: 0.000000

최적화를 활성화하지 않으면 수행하는 벤치마킹은 완전히 가치가 없습니다. (최적화를 활성화하면 루프가 최적화됩니다. 따라서 벤치마킹 코드에도 결함이 있습니다. 일반적으로 결과 또는 유사한 결과를 합산하고 마지막에 출력하여 루프를 실행하도록 강제해야합니다.)

당신이 측정하는 것은 기본적으로 “어떤 컴파일러가 가장 많은 디버깅 오버 헤드를 삽입하는지”인 것 같습니다. 그리고 답은 C입니다. 그러나 그것은 어떤 프로그램이 가장 빠른지 알려주지 않습니다. 속도를 원할 때 최적화를 활성화하기 때문입니다.

그건 그렇고, 언어가 서로 “빠르다”는 개념을 버리면 장기적으로 많은 두통을 피할 수 있습니다. C #은 영어보다 속도가 빠릅니다.

C 언어에는 최적화되지 않은 순진한 컴파일러에서도 효율적인 특정 사항이 있으며 모든 것을 최적화하기 위해 컴파일러에 크게 의존하는 다른 것들이 있습니다. 물론 C #이나 다른 언어도 마찬가지입니다.

실행 속도는 다음에 의해 결정됩니다.

  • 실행중인 플랫폼 (OS, 하드웨어, 시스템에서 실행되는 기타 소프트웨어)
  • 컴파일러
  • 당신의 소스 코드

좋은 C # 컴파일러는 효율적인 코드를 생성합니다. 잘못된 C 컴파일러는 느린 코드를 생성합니다. C # 코드를 생성 한 다음 C # 컴파일러를 통해 실행할 수있는 C 컴파일러는 어떻습니까? 얼마나 빨리 실행 될까요? 언어에는 속도가 없습니다. 당신의 코드는 그렇습니다.


답변

간략하게 설명하겠습니다. 이미 답변으로 표시되어 있습니다. C #은 잘 정의 된 부동 소수점 모델을 갖는 큰 장점이 있습니다. 이는 x86 및 x64 프로세서에서 설정된 FPU 및 SSE 명령어의 기본 작동 모드와 일치합니다. 우연이 없습니다. JITter는 Math.Sqrt ()를 몇 가지 인라인 명령어로 컴파일합니다.

네이티브 C / C ++는 수년간의 이전 버전과의 호환성을 갖추고 있습니다. / fp : precise, / fp : fast 및 / fp : strict 컴파일 옵션이 가장 잘 보입니다. 따라서 sqrt ()를 구현하는 CRT 함수를 호출하고 선택한 부동 소수점 옵션을 확인하여 결과를 조정해야합니다. 느립니다.


답변

저는 C ++ 및 C # 개발자입니다. .NET 프레임 워크의 첫 번째 베타 이후 C # 응용 프로그램을 개발했으며 C ++ 응용 프로그램 개발에 20 년 이상의 경험을 가지고 있습니다. 첫째, C # 코드는 C ++ 응용 프로그램보다 빠르지는 않지만 관리 코드, 작동 방식, 상호 운영 계층, 메모리 관리 내부, 동적 유형 시스템 및 가비지 수집기에 대해 긴 논의를 거치지 않을 것입니다. 그럼에도 불구하고 여기에 나열된 벤치 마크가 모두 잘못된 결과를 생성한다고 말하면서 계속하겠습니다.

설명해 드리겠습니다. 가장 먼저 고려해야 할 것은 C # 용 JIT 컴파일러 (.NET Framework 4)입니다. 이제 JIT는 다양한 최적화 알고리즘 (Visual Studio와 함께 제공되는 기본 C ++ 최적화 프로그램보다 더 공격적인 경향이 있음)을 사용하여 CPU 용 네이티브 코드를 생성하고 .NET JIT 컴파일러에서 사용하는 명령 집합은 실제 CPU를 더 가깝게 반영합니다. 따라서 기계 코드에서 특정 대체를 수행하여 클럭 사이클을 줄이고 CPU 파이프 라인 캐시의 적중률을 개선하고 분기 예측과 관련된 명령 재정렬 및 ​​개선과 같은 추가 하이퍼 스레딩 최적화를 생성 할 수 있습니다.

이것이 의미하는 바는 RELEASE 빌드 (DEBUG 빌드가 아님)에 대해 올바른 매개 변수를 사용하여 C ++ 애플리케이션을 컴파일하지 않으면 C ++ 애플리케이션이 해당 C # 또는 .NET 기반 애플리케이션보다 느리게 수행 될 수 있다는 것입니다. C ++ 애플리케이션에서 프로젝트 속성을 지정할 때 “전체 최적화”및 “빠른 코드 선호”를 활성화해야합니다. 64 비트 머신이있는 경우 x64를 대상 플랫폼으로 생성하도록 지정해야합니다. 그렇지 않으면 코드가 변환 하위 계층 (WOW64)을 통해 실행되어 성능이 크게 저하됩니다.

컴파일러에서 올바른 최적화를 수행하면 C ++ 애플리케이션의 경우 .72 초, C # 애플리케이션의 경우 1.16 초 (둘 다 릴리스 빌드에서)가됩니다. C # 애플리케이션은 매우 기본적이며 힙이 아닌 스택의 루프에서 사용되는 메모리를 할당하기 때문에 실제로 개체, 무거운 계산 및 더 큰 데이터 세트와 관련된 실제 애플리케이션보다 훨씬 더 나은 성능을 발휘합니다. 따라서 제공된 수치는 C # 및 .NET 프레임 워크에 편향된 낙관적 인 수치입니다. 이러한 편견에도 불구하고 C ++ 애플리케이션은 동등한 C # 애플리케이션보다 절반이 조금 넘는 시간에 완료됩니다. 필자가 사용한 Microsoft C ++ 컴파일러에는 올바른 파이프 라인 및 하이퍼 스레딩 최적화가 없었습니다 (WinDBG를 사용하여 어셈블리 지침보기).

이제 Intel 컴파일러 (AMD / Intel 프로세서에서 고성능 응용 프로그램을 생성하는 업계 비밀)를 사용하면 동일한 코드가 C ++ 실행 파일의 경우 .54 초, Microsoft Visual Studio 2010을 사용하는 경우 .72 초만에 실행됩니다. 결국 최종 결과는 C ++의 경우 .54 초, C #의 경우 1.16 초입니다. 따라서 .NET JIT 컴파일러에 의해 생성되는 코드는 C ++ 실행 파일보다 214 % 더 오래 걸립니다. .54 초에 소요 된 대부분의 시간은 루프 자체가 아닌 시스템에서 시간을 얻는 데있었습니다!

통계에서 누락 된 것은 타이밍에 포함되지 않은 시작 및 정리 시간입니다. C # 애플리케이션은 C ++ 애플리케이션보다 시작 및 종료에 더 많은 시간을 소비하는 경향이 있습니다. 그 이유는 복잡하고 메모리 할당과 가비지를 최적화하기 위해 프로그램의 시작 (결과적으로 끝)에서 많은 작업을 수행하는 .NET 런타임 코드 유효성 검사 루틴 및 메모리 관리 하위 시스템과 관련이 있습니다. 수집기.

C ++ 및 .NET IL의 성능을 측정 할 때 어셈블리 코드를 살펴보고 모든 계산이 있는지 확인하는 것이 중요합니다. 내가 찾은 것은 C #에 추가 코드를 넣지 않고 위의 예제에있는 대부분의 코드가 실제로 바이너리에서 제거되었다는 것입니다. 인텔 C ++ 컴파일러와 함께 제공되는 최적화 프로그램과 같은보다 공격적인 최적화 프로그램을 사용할 때 C ++에서도 마찬가지입니다. 위에서 제공 한 결과는 100 % 정확하고 어셈블리 수준에서 검증되었습니다.

많은 초보자들이 기술을 이해하지 않고 Microsoft 마케팅 선전을 듣고 C #이 C ++보다 빠르다는 잘못된 주장을하는 인터넷의 많은 포럼의 주요 문제입니다. 이론적으로 C #은 JIT 컴파일러가 CPU 용 코드를 최적화 할 수 있기 때문에 C ++보다 빠르다는 것입니다. 이 이론의 문제점은 성능을 저하시키는 .NET 프레임 워크에 존재하는 많은 배관이 있다는 것입니다. C ++ 애플리케이션에 존재하지 않는 배관. 또한 숙련 된 개발자는 주어진 플랫폼에 사용할 올바른 컴파일러를 알고 애플리케이션을 컴파일 할 때 적절한 플래그를 사용할 것입니다. Linux 또는 오픈 소스 플랫폼에서는 소스를 배포하고 적절한 최적화를 사용하여 코드를 컴파일하는 설치 스크립트를 만들 수 있으므로 이는 문제가되지 않습니다. Windows 또는 폐쇄 된 소스 플랫폼에서는 각각 특정 최적화가 적용된 여러 실행 파일을 배포해야합니다. 배포 될 Windows 바이너리는 msi 설치 프로그램이 감지 한 CPU를 기반으로합니다 (사용자 지정 작업 사용).


답변

내 첫 번째 추측은 루트를 사용하지 않기 때문에 컴파일러 최적화입니다. 할당 한 다음 반복해서 덮어 씁니다.

편집 : 젠장, 9 초로 이길!


답변

루프가 최적화되고 있는지 확인하려면 코드를

root += Math.Sqrt(i);

C 코드에서 비슷하게 ans를 입력 한 다음 루프 외부에 루트 값을 인쇄합니다.


답변

아마도 C # 컴파일러는 루트를 어디에도 사용하지 않는다는 것을 알아 차렸 기 때문에 전체 for 루프를 건너 뜁니다. 🙂

그렇지 않을 수도 있지만 원인이 무엇이든 컴파일러 구현에 따라 다릅니다. 최적화 및 릴리스 모드를 사용하여 Microsoft 컴파일러 (cl.exe, win32 sdk의 일부로 사용 가능)로 C 프로그램을 컴파일 해보십시오. 다른 컴파일러보다 성능이 향상 될 것입니다.

편집 : Math.Sqrt ()에 부작용이 없다는 것을 알아야하기 때문에 컴파일러가 for 루프를 최적화 할 수 있다고 생각하지 않습니다.