programing

C#과 C - 퍼포먼스의 큰 차이

firstcheck 2022. 8. 1. 21:32
반응형

C#과 C - 퍼포먼스의 큰 차이

C와 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배 이상 느리게 실행되는 원인은 정확히 무엇입니까?

EDIT: C# 옵티마이저는 루트를 사용하지 않았기 때문에 루트를 삭제하고 있었던 것 같습니다.을 트루루 the the로 했습니다.root +=마지막에 총계를 출력해 주셨어요.하여 /max cl.exe에

결과는 다음과 같습니다.C#의 경우 C2.61초 동안 3.75초입니다.

C는 아직 시간이 더 걸리지만 이는 허용됩니다.

간단하게 말씀드리겠습니다, 이미 답변이 되어 있습니다.C#에는 부동소수점 모델이 잘 정의되어 있다는 큰 이점이 있습니다.이는 x86 및 x64 프로세서에서 설정된 FPU 및 SSE 명령의 네이티브 동작 모드와 일치합니다.우연은 없어JITter는 수학을 컴파일합니다.Sqrt()를 몇 개의 인라인 명령으로 지정합니다.

Native C/C++에는 수년간 하위 호환성이 부여되어 있습니다./fp:precision, /fp:fast 및 /fp:strict 컴파일 옵션이 가장 잘 표시됩니다.따라서 sqrt()를 구현하는 CRT 함수를 호출하고 선택한 부동소수점 옵션을 체크하여 결과를 조정해야 합니다.느리다.

디버깅 빌드를 비교해야 합니다.방금 당신의 C코드를 컴파일해서

Time elapsed: 0.000000

최적화를 활성화하지 않으면 벤치마킹은 전혀 의미가 없습니다(또한 최적화를 활성화하면 루프가 최적화되어 없어집니다).따라서 벤치마크 코드에도 결함이 있습니다.보통 결과나 유사한 결과를 요약하여 마지막에 출력함으로써 강제로 루프를 실행해야 합니다.)

기본적으로 "어떤 컴파일러가 가장 많은 디버깅 오버헤드를 삽입하는지"를 측정하는 것 같습니다.정답은 C입니다.하지만 어떤 프로그램이 가장 빠른지는 알 수 없습니다.왜냐하면 속도를 원한다면 최적화가 가능하기 때문입니다.

그나저나, 언어가 서로 '빠르다'는 생각을 버려야 장기적으로 많은 두통을 덜 수 있을 것이다.C#는 영어만큼 스피드가 없습니다.

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

실행 속도는 다음과 같이 결정됩니다.

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

좋은 C# 컴파일러는 효율적인 코드를 생성합니다.C 컴파일러가 불량하면 느린 코드가 생성됩니다.C# 컴파일러를 통해 실행할 수 있는 C# 코드를 생성한 C 컴파일러는 어떻습니까?얼마나 빨리 달릴 수 있을까요?언어에는 속도가 없습니다.당신의 암호는 그렇습니다.

저는 C++로 C#개발자입니다.의 첫 번째 베타판부터 C# 어플리케이션을 개발했습니다.NET 프레임워크와 저는 C++ 어플리케이션 개발에 20년 이상의 경험이 있습니다.첫째, C# 코드는 C++ 어플리케이션보다 빠르지 않지만 관리 코드, 동작 방식, 상호 운용 계층, 메모리 관리 내부, 동적 유형 시스템 및 가비지 컬렉터에 대해서는 장황하게 설명하지 않습니다.그럼에도 불구하고, 여기에 나열된 벤치마크는 모두 잘못된 결과를 낳는다고 계속 말씀드리겠습니다.

설명하겠습니다.가장 먼저 고려해야 할 것은 C#용 JIT 컴파일러입니다.NET Framework 4).이제 JIT는 다양한 최적화 알고리즘(Visual Studio에 부속된 기본 C++ 옵티마이저보다 공격적인 경향이 있음)과 에서 사용되는 명령 세트를 사용하여 CPU의 네이티브코드를 생성합니다.NET JIT 컴파일러는 머신상의 실제 CPU를 보다 가깝게 반영하고 있기 때문에 머신 코드의 특정 치환을 통해 CPU 파이프라인 캐시의 클럭 사이클을 줄이고 히트 레이트를 개선하며 명령 순서 변경 및 분기 예측과 관련된 개선과 같은 추가 하이퍼스레딩 최적화를 생성할 수 있습니다.

즉, (DEBUG 빌드가 아닌) RELEASE 빌드의 올바른 파라메터를 사용하여 C++ 애플리케이션을 컴파일하지 않는 한, C++ 애플리케이션의 동작이 대응하는 C# 또는 보다 느려질 수 있습니다.NET 기반 응용 프로그램C++ 애플리케이션에서 프로젝트 속성을 지정할 때는 "전체 최적화" 및 "기본 코드"를 활성화해야 합니다.64비트 머신을 사용하는 경우 타겟 플랫폼으로 x64를 생성하도록 지정해야 합니다.그렇지 않으면 변환 서브레이어(WOW64)를 통해 코드가 실행되므로 성능이 크게 저하됩니다.

컴파일러에서 올바른 최적화를 실행하면 C++ 어플리케이션의 경우 0.72초, C# 어플리케이션의 경우 1.16초(릴리스 빌드의 경우 모두)가 표시됩니다.C# 어플리케이션은 매우 기본적이며 루프에서 사용되는 메모리를 힙이 아닌 스택에 할당하기 때문에 실제로는 객체, 대량의 계산 및 대규모 데이터 세트에 관련된 실제 어플리케이션보다 훨씬 더 뛰어난 성능을 발휘합니다.따라서 제시된 수치는 C# 및 에 편향된 낙관적인 수치입니다.NET 프레임워크이러한 편견에도 불구하고 C++ 어플리케이션은 동등한 C# 어플리케이션보다 절반 이상 짧은 시간에 완료됩니다.사용하고 있는 Microsoft C++ 컴파일러는 올바른 파이프라인과 하이퍼스레딩 최적화가 되어 있지 않은 것에 주의해 주세요(WinDBG를 사용하여 어셈블리 명령을 표시).

인텔의 컴파일러(AMD/Intel 프로세서에서 고성능 애플리케이션을 생성하는 업계 비밀)를 사용하는 경우, Microsoft Visual Studio 2010을 사용하는 경우와 비교하여 C++ 실행 파일의 경우 0.54초 이내에 동일한 코드가 실행됩니다.최종 결과는 C++의 경우 0.54초, C#의 경우 1.16초입니다.따라서 코드는 에 의해 생성됩니다.NET JIT 컴파일러는 C++ 실행 파일보다 214% 더 오래 걸립니다..54초 동안 대부분의 시간은 루프 자체가 아니라 시스템에서 시간을 얻는 데 소요되었습니다.

이 통계 정보에는 타이밍에 포함되지 않은 시작 시간과 청소 시간도 포함되어 있지 않습니다.C# 어플리케이션은 C++ 어플리케이션보다 부팅 및 종료에 더 많은 시간을 소비하는 경향이 있습니다.그 이유는 복잡하고와 관련이 있습니다.NET 런타임 코드 검증 루틴과 메모리 관리 서브시스템은 메모리 할당과 가비지 컬렉터를 최적화하기 위해 프로그램의 시작 부분(결과적으로 끝 부분)에서 많은 작업을 수행합니다.

C++ 및 퍼포먼스를 측정하는 경우.NetIL, 어셈블리 코드를 확인하여 모든 계산이 이루어졌는지 확인하는 것이 중요합니다.제가 발견한 것은 C#에 추가 코드를 추가하지 않고 위의 예에 있는 대부분의 코드가 실제로 바이너리에서 제거되었다는 것입니다.인텔 C++ 컴파일러에 부속되어 있는 옵티마이저 등 보다 적극적인 옵티마이저를 사용한 경우에도 마찬가지입니다.위의 결과는 100% 정확하며 어셈블리 레벨에서 검증되었습니다.

인터넷상의 많은 포럼의 주된 문제점은 많은 초보자들이 마이크로소프트의 마케팅 선전을 기술을 이해하지 못한 채 듣고 C#이 C++보다 빠르다는 잘못된 주장을 한다는 것입니다.JIT 컴파일러가 CPU에 맞게 코드를 최적화할 수 있기 때문에 이론적으로는 C#이 C++보다 빠르다고 주장합니다.이 이론의 문제는 에는 많은 배관이 존재한다는 것입니다.퍼포먼스를 저하시키는 NET 프레임워크, C++ 어플리케이션에는 존재하지 않는 배관.또한 경험이 풍부한 개발자는 특정 플랫폼에 적합한 컴파일러를 알고 애플리케이션을 컴파일할 때 적절한 플래그를 사용합니다.Linux 또는 오픈소스 플랫폼에서는 적절한 최적화를 사용하여 소스를 배포하고 코드를 컴파일하는 설치 스크립트를 작성할 수 있으므로 문제가 되지 않습니다.윈도 또는 클로즈드소스 플랫폼에서는 각각 특정 최적화가 있는 여러 실행 파일을 배포해야 합니다.전개되는 윈도 바이너리는 (커스텀액션을 사용하여) msi instra에서 검출된 CPU를 기반으로 합니다.

root'을 사용하지 않기 때문에 컴파일러가 메서드를 최적화하기 위해 호출을 삭제했을 수 있습니다.

제곱근 값을 누적기에 누적하여 메서드의 마지막에 출력하여 무슨 일이 일어나는지 확인할 수 있습니다.

편집: 아래 Jalf의 답변을 참조하십시오.

내 첫 번째 추측은 root을 사용하지 않기 때문에 컴파일러 최적화입니다.할당하고 다시 덮어쓰기만 하면 됩니다.

편집: 젠장, 9초 차이로 쳐!

루프가 최적화되어 있는지 여부를 확인하려면 코드를 다음과 같이 변경해 보십시오.

root += Math.Sqrt(i);

C 코드에서도 마찬가지로 루트의 값을 루프 외부에 인쇄합니다.

c# 컴파일러는 루트를 사용하지 않는 것을 알고 있기 때문에 전체를 건너뛰어 루프를 찾습니다.:)

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

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

시간이 어떻게 다르든 간에, 그 "유효한 시간"은 유효하지 않습니다.두 프로그램이 정확히 동일한 조건에서 실행되도록 보장할 수 있는 경우에만 유효합니다.

한 번 이겨보는 게 좋을 것 같네요.$/usr/bin/time my_cprog;/usr/bin/time my_crog에 상당합니다.

C와 C#에서 비교 가능한 테스트를 2개 더 준비했습니다.이 2개는 계수 연산자를 사용하여 인덱스에 작은 어레이를 씁니다(오버헤드가 약간 증가하지만 성능 비교는 (대략적인 수준에서) 시도하고 있습니다.

C 코드:

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

void main()
{
    int count = (int)1e8;
    int subcount = 1000;
    double* roots = (double*)malloc(sizeof(double) * subcount);
    clock_t start = clock();
    for (int i = 0 ; i < count; i++)
    {
        roots[i % subcount] = sqrt((double)i);
    }
    clock_t end = clock();
    double length = ((double)end - start) / CLOCKS_PER_SEC;
    printf("Time elapsed: %f\n", length);
}

C#의 경우:

using System;

namespace CsPerfTest
{
    class Program
    {
        static void Main(string[] args)
        {
            int count = (int)1e8;
            int subcount = 1000;
            double[] roots = new double[subcount];
            DateTime startTime = DateTime.Now;
            for (int i = 0; i < count; i++)
            {
                roots[i % subcount] = Math.Sqrt(i);
            }
            TimeSpan runTime = DateTime.Now - startTime;
            Console.WriteLine("Time elapsed: " + Convert.ToString(runTime.TotalMilliseconds / 1000));
        }
    }
}

이러한 테스트에서는 어레이에 데이터를 씁니다(따라서).어레이의 사이즈가 큰폭으로 작아져도(과다한 메모리를 사용하고 싶지 않다) NET 런타임에서는 sqrt op을 삭제할 수 없습니다.릴리스 컨피규레이션으로 컴파일하여 (VS에서 시작하는 것이 아니라) 콘솔창에서 실행합니다.

내 컴퓨터에서는 C# 프로그램이 6.2초에서 6.9초 사이인데 반해 C 버전은 6.9초에서 7.1초 사이입니다.

제곱근 루틴을 밟는 것을 포함하여 어셈블리 수준에서 코드를 한 단계만 밟으면 질문에 대한 답을 얻을 수 있을 것입니다.

교양있는 추측은 필요 없습니다.

여기서 문제가 될 수 있는 또 다른 요인은 C 컴파일러는 타깃이 되는 프로세서패밀리의 범용 네이티브코드로 컴파일되지만 C# 코드를 컴파일할 때 생성된 MSIL은 가능한 모든 최적화로 완전한 프로세서를 타깃으로 하기 위해 컴파일되는 것입니다.따라서 C#에서 생성된 네이티브코드가 C보다 상당히 빠를 수 있습니다.

이것은 언어 자체와는 관계가 없고 제곱근 함수의 다른 구현과 관련이 있는 것으로 보입니다.

사실 루프가 최적화되어 있지 않습니다.John의 코드를 컴파일하여 .exe를 조사했습니다.루프의 내장은 다음과 같습니다.

 IL_0005:  stloc.0
 IL_0006:  ldc.i4.0
 IL_0007:  stloc.1
 IL_0008:  br.s       IL_0016
 IL_000a:  ldloc.1
 IL_000b:  conv.r8
 IL_000c:  call       float64 [mscorlib]System.Math::Sqrt(float64)
 IL_0011:  pop
 IL_0012:  ldloc.1
 IL_0013:  ldc.i4.1
 IL_0014:  add
 IL_0015:  stloc.1
 IL_0016:  ldloc.1
 IL_0017:  ldc.i4     0x5f5e100
 IL_001c:  ble.s      IL_000a

실행 시 루프가 아무것도 하지 않고 건너뛰지 않는 한?

편집: C# 변경:

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

결과적으로 (머신의) 경과시간이 0.047에서 2.17로 바뀝니다.하지만 이것이 1억 명의 연산자를 추가하는 데 드는 비용일까요?

언급URL : https://stackoverflow.com/questions/686483/c-sharp-vs-c-big-performance-difference

반응형