인라인 어셈블리 언어가 네이티브 C ++ 코드보다 느립니까?


183

인라인 어셈블리 언어와 C ++ 코드의 성능을 비교하려고했기 때문에 크기가 2000 인 두 배열을 100000 회 추가하는 함수를 작성했습니다. 코드는 다음과 같습니다.

#define TIMES 100000
void calcuC(int *x,int *y,int length)
{
    for(int i = 0; i < TIMES; i++)
    {
        for(int j = 0; j < length; j++)
            x[j] += y[j];
    }
}


void calcuAsm(int *x,int *y,int lengthOfArray)
{
    __asm
    {
        mov edi,TIMES
        start:
        mov esi,0
        mov ecx,lengthOfArray
        label:
        mov edx,x
        push edx
        mov eax,DWORD PTR [edx + esi*4]
        mov edx,y
        mov ebx,DWORD PTR [edx + esi*4]
        add eax,ebx
        pop edx
        mov [edx + esi*4],eax
        inc esi
        loop label
        dec edi
        cmp edi,0
        jnz start
    };
}

여기 있습니다 main():

int main() {
    bool errorOccured = false;
    setbuf(stdout,NULL);
    int *xC,*xAsm,*yC,*yAsm;
    xC = new int[2000];
    xAsm = new int[2000];
    yC = new int[2000];
    yAsm = new int[2000];
    for(int i = 0; i < 2000; i++)
    {
        xC[i] = 0;
        xAsm[i] = 0;
        yC[i] = i;
        yAsm[i] = i;
    }
    time_t start = clock();
    calcuC(xC,yC,2000);

    //    calcuAsm(xAsm,yAsm,2000);
    //    for(int i = 0; i < 2000; i++)
    //    {
    //        if(xC[i] != xAsm[i])
    //        {
    //            cout<<"xC["<<i<<"]="<<xC[i]<<" "<<"xAsm["<<i<<"]="<<xAsm[i]<<endl;
    //            errorOccured = true;
    //            break;
    //        }
    //    }
    //    if(errorOccured)
    //        cout<<"Error occurs!"<<endl;
    //    else
    //        cout<<"Works fine!"<<endl;

    time_t end = clock();

    //    cout<<"time = "<<(float)(end - start) / CLOCKS_PER_SEC<<"\n";

    cout<<"time = "<<end - start<<endl;
    return 0;
}

그런 다음 프로그램을 다섯 번 실행하여 프로세서 사이클을 얻습니다. 이는 시간으로 볼 수 있습니다. 위에서 언급 한 함수 중 하나만 호출 할 때마다.

그리고 여기에 결과가옵니다.

어셈블리 버전의 기능 :

Debug   Release
---------------
732        668
733        680
659        672
667        675
684        694
Average:   677

C ++ 버전의 기능 :

Debug     Release
-----------------
1068      168
 999      166
1072      231
1002      166
1114      183
Average:  182

릴리스 모드의 C ++ 코드는 어셈블리 코드보다 거의 3.7 배 빠릅니다. 왜?

필자가 작성한 어셈블리 코드가 GCC에서 생성 한 어셈블리 코드만큼 효과적이지 않은 것 같습니다. 저와 같은 일반적인 프로그래머는 컴파일러가 생성 한 상대보다 코드를 더 빨리 작성하기가 어렵습니다. 즉, 필자가 작성한 어셈블리 언어의 성능을 믿지 말고 C ++에 집중하고 어셈블리 언어를 잊어 버리지 않아야합니까?


29
거의 요 수동 코딩 된 어셈블리는 일부 상황에서는 적합하지만,보다 높은 수준의 언어로 얻을 수있는 것보다 어셈블리 버전이 실제로 더 빠르도록주의를 기울여야합니다.
Magnus Hoff

161
컴파일러가 생성 한 코드를 연구하고 어셈블리 버전보다 빠른 이유를 이해하는 것이 도움이 될 수 있습니다.
Paul R

34
예, 컴파일러가 당신보다 asm을 작성하는 것이 더 나은 것 같습니다. 현대 컴파일러는 정말 좋습니다.
David Heffernan

20
GCC가 생산 한 어셈블리를 보셨습니까? 가능한 GCC는 MMX 명령어를 사용했습니다. 함수는 매우 병렬 적입니다. N 프로세서를 사용하여 시간의 1 / N에 합계를 계산할 수 있습니다. 병렬화에 대한 희망이없는 기능을 사용해보십시오.
Chris

11
흠, 나는 ~ 100000 배 더 빠른 이것을하기 위해 좋은 컴파일러를 기대했을 것이다 ...
PlasmaHH

답변:


261

예, 대부분입니다.

우선 낮은 수준의 언어 (이 경우 어셈블리)가 항상 높은 수준의 언어 (이 경우 C ++ 및 C)보다 빠른 코드를 생성한다는 잘못된 가정에서 시작합니다. 그것은 사실이 아닙니다. C 코드는 항상 Java 코드보다 빠릅니까? 프로그래머라는 또 다른 변수가 있기 때문에 아니요. 아키텍처 세부 사항에 대한 코드와 지식을 작성하는 방식은 성능에 큰 영향을 미칩니다 (이 경우 참조).

당신은 할 수 항상 손으로 만든 어셈블리 코드가 잘 컴파일 된 코드보다 예를 생산하지만, 일반적으로 는 가상의 예 또는 단일 루틴이 아닌의 진정한 C ++ 코드의 500.000+ 라인의 프로그램). 나는 컴파일러는 더 나은 어셈블리 코드 95 %의 시간을 생산하고 생각 때로는, 일부 희귀 한 번, 당신이 조립 몇 짧은에 대한 코드를 작성해야 할 수도 있습니다 매우 사용 , 성능 중요한 루틴 또는 액세스해야 할 때 당신의 마음에 드는 높은 수준의 언어 기능 노출하지 않습니다. 이 복잡한 작업을 원하십니까? 이 멋진 답변을 여기에서 읽으십시오 .

왜 이런가요?

우선 컴파일러는 상상조차 할 수없는 최적화를 수행 할 수 있기 때문에 ( 이 짧은 목록 참조 ) 몇 초 만에 수행 할 것입니다 ( 일이 필요할 때 ).

어셈블리를 코딩 할 때는 잘 정의 된 호출 인터페이스를 사용하여 잘 정의 된 기능을 만들어야합니다. 그러나 레지스터 할당 , 상수 전파 , 공통 하위 식 제거 , 명령어 스케줄링 및 기타 복잡한 ( 예 : Polytope 모델 ) 과 같은 전체 프로그램 최적화절차 간 최적화 를 고려할 수 있습니다 . 에 RISC 아키텍처들 (예를 들어, 매우 어려운 명령 스케줄링이 몇 년 전 걱정 중지 손으로 조정 )과 현대 CISC의 CPU는 매우 긴이 파이프 라인을 너무.

일부 복잡한 마이크로 컨트롤러의 경우 컴파일러가 더 나은 (그리고 유지하기 쉬운) 최종 코드를 생성하기 때문에 시스템 라이브러리 조차 어셈블리 대신 C로 작성됩니다.

컴파일러는 때때로 자체적으로 일부 MMX / SIMDx 명령어자동으로 사용할 수 있으며 ,이를 사용하지 않으면 단순히 비교할 수 없습니다 (다른 답변은 이미 어셈블리 코드를 잘 검토했습니다). 루프의 경우 이것은 컴파일러에서 일반적으로 확인 하는 루프 최적화짧은 목록입니다 (C # 프로그램에 대한 일정이 결정되었을 때 혼자서 할 수 있다고 생각하십니까?) 어셈블리에 무언가를 쓰는 경우, 최소한 간단한 최적화 를 고려해야한다고 생각합니다 . 배열에 대한 교과서 예제 는 사이클언 롤링하는 것입니다 (컴파일 타임에 크기가 알려짐). 그것을하고 테스트를 다시 실행하십시오.

요즘에는 다른 이유로 CPU많은 다른 어셈블리 언어를 사용해야하는 경우가 드물다 . 당신은 그들 모두를 지원 하시겠습니까? 각각에는 특정 마이크로 아키텍처특정 명령어 세트가 있습니다. 그것들은 서로 다른 수의 기능 유닛을 가지고 있으며 그것들을 모두 바쁘게 유지하기 위해 조립 지침을 마련해야합니다 . C로 작성하면 PGO를 사용할 수 있지만 조립에서는 해당 특정 아키텍처에 대한 지식이 필요합니다 ( 다른 아키텍처에 대한 모든 내용을 재고하고 다시 실행 ). 소규모 작업의 경우 일반적으로 컴파일러 가 더 잘 수행하며 복잡한 작업의 경우 일반적 으로 작업이 상환되지 않습니다 (그리고컴파일러 어쨌든 더 잘 할 수 있습니다 ).

앉아서 코드를 살펴보면 어셈블리로 변환하는 것보다 알고리즘을 다시 디자인하는 데 더 많은 것을 얻을 수 있음을 알 수 있습니다 (이 위대한 게시물을 여기에서 읽으십시오 ), 고급 최적화가 있습니다 (및 컴파일러에 대한 힌트) 어셈블리 언어에 의존하기 전에 효과적으로 적용 할 수 있습니다. 종종 내장 함수를 사용하면 원하는 성능을 얻을 수 있으며 컴파일러는 여전히 대부분의 최적화를 수행 할 수 있습니다.

이 모든 것에서 5 ~ 10 배 빠른 어셈블리 코드를 생성 할 수 있더라도 고객에게 일주일의 시간지불 것인지 50 달러 빠른 CPU구매할 것인지 물어보아야합니다 . 대부분의 경우, 특히 LOB 응용 프로그램에서보다 더 극단적 인 최적화가 필요하지 않습니다.


9
당연히 아니지. 99 %의 시간에 95 %의 사람들이 더 낫다고 생각합니다. 때로는 복잡한 수학 때문에 비용이 많이 들거나 시간 이 많이 걸리기 때문에 (다시 비용이 많이 들기 때문에 ) 때때로 우리는 단순히 최적화를 잊어 버렸기 때문에 ...
Adriano Repetti

62
@ ja72-아니요, 코드 작성이 낫지 않습니다 . 코드 를 최적화하는 것이 좋습니다 .
Mike Baranczak

14
당신이 그것을 정말로 고려할 때까지 반 직관적입니다. 같은 방식으로 VM 기반 머신은 컴파일러가 정보를 가지고 있지 않은 런타임 최적화를 시작합니다.
Bill K

6
@ M28 : 컴파일러는 동일한 지침을 사용할 수 있습니다. 물론, 이진 크기로 지불하면됩니다 (명령이 지원되지 않는 경우 대체 경로를 제공해야하기 때문에). 또한, 추가 될 "새로운 명령어"는 대부분 SMID 명령어이며 VM과 컴파일러 모두 활용하기가 끔찍합니다. VM은 시작시 코드를 컴파일해야한다는 점에서이 기능에 대한 비용을 지불합니다.
Billy ONeal

9
@ BillK : PGO는 컴파일러에 대해서도 동일한 작업을 수행합니다.
Billy ONeal

194

어셈블리 코드가 차선책이며 개선 될 수 있습니다.

  • 내부 루프에서 레지스터 ( EDX )를 밀고 터 뜨리고 있습니다. 루프 밖으로 이동해야합니다.
  • 루프를 반복 할 때마다 배열 포인터를 다시로드합니다. 루프 밖으로 이동해야합니다.
  • 당신은 loop지시를 사용합니다. 가장 현대적인 CPU에서 죽은 느린 것으로 알려져 (고대 조립 책 *을 사용 가능하게 결과를)
  • 수동 루프 언 롤링의 이점은 없습니다.
  • 사용 가능한 SIMD 명령어를 사용하지 않습니다 .

따라서 어셈블러와 관련된 기술을 크게 향상시키지 않으면 성능을 위해 어셈블러 코드를 작성하는 것이 적합하지 않습니다.

* 물론 loop고대 어셈블리 북에서 실제로 교훈을 받았는지 모르겠습니다 . 그러나 실제 컴파일러에서는 거의 볼 수 없습니다. 모든 컴파일러는 방출 할만 큼 똑똑하지 않기 loop때문에 IMHO 나쁘고 오래된 책에서만 볼 수 있습니다.


loop크기에 맞게 최적화하면 컴파일러에서 여전히 "더 이상 사용되지 않는"명령어가 생성 될 수 있습니다.
phuclv

1
@ phuclv는 그렇습니다.하지만 원래 질문은 크기가 아니라 속도에 관한 것입니다.
IGR94

60

어셈블리를 탐구하기 전에도 더 높은 수준의 코드 변환이 있습니다.

static int const TIMES = 100000;

void calcuC(int *x, int *y, int length) {
  for (int i = 0; i < TIMES; i++) {
    for (int j = 0; j < length; j++) {
      x[j] += y[j];
    }
  }
}

루프 회전 을 통해 변환 할 수 있습니다 .

static int const TIMES = 100000;

void calcuC(int *x, int *y, int length) {
    for (int j = 0; j < length; ++j) {
      for (int i = 0; i < TIMES; ++i) {
        x[j] += y[j];
      }
    }
}

메모리 로컬 리티가있는 한 훨씬 좋습니다.

이것은 더 최적화 될 수 있습니다 a += b.X 시간을하는 a += X * b것은 우리가 얻는 것과 같습니다 .

static int const TIMES = 100000;

void calcuC(int *x, int *y, int length) {
    for (int j = 0; j < length; ++j) {
      x[j] += TIMES * y[j];
    }
}

그러나 내가 좋아하는 최적화 프로그램 (LLVM) 이이 변환을 수행하지 않는 것 같습니다.

[편집] 그리고 restrict한정자를 xand로 사용 하면 변환이 수행된다는 것을 알았습니다 y. 실제로 이러한 제한없이, x[j]그리고 y[j]수 변환이 오류하게 동일한 위치 별명. [편집 종료]

어쨌든 이것은 최적화 된 C 버전이라고 생각합니다. 이미 훨씬 간단합니다. 이를 바탕으로 ASM에서의 균열은 다음과 같습니다 (Clang이 생성하도록하고, 쓸모가 없습니다).

calcuAsm:                               # @calcuAsm
.Ltmp0:
    .cfi_startproc
# BB#0:
    testl   %edx, %edx
    jle .LBB0_2
    .align  16, 0x90
.LBB0_1:                                # %.lr.ph
                                        # =>This Inner Loop Header: Depth=1
    imull   $100000, (%rsi), %eax   # imm = 0x186A0
    addl    %eax, (%rdi)
    addq    $4, %rsi
    addq    $4, %rdi
    decl    %edx
    jne .LBB0_1
.LBB0_2:                                # %._crit_edge
    ret
.Ltmp1:
    .size   calcuAsm, .Ltmp1-calcuAsm
.Ltmp2:
    .cfi_endproc

나는 그 모든 지침이 어디에서 왔는지 이해하지 못하지만, 항상 재미 있고 비교할 수있는 방법을 볼 수는 있지만 코드에서 어셈블리 버전보다는 최적화 된 C 버전을 계속 사용합니다. 훨씬 더 휴대용.


답을 주셔서 감사합니다. "컴파일러 원칙"이라는 클래스를 사용할 때 컴파일러가 여러 가지 방법으로 코드를 최적화한다는 것을 알게 되었기 때문에 조금 혼란 스럽습니다. 코드를 수동으로 최적화해야합니까? 컴파일러보다 더 나은 작업을 수행 할 수 있습니까? 그것이 항상 나를 혼동하는 질문입니다.
user957121

2
@ user957121 : 정보가 많을수록 더 잘 최적화 할 수 있습니다. 특히 여기에 어떤 컴파일러를 방해하는 것은 가능하다 앨리어싱 사이 xy. 즉, 컴파일러는 확실히 모든 것을 할 수 i,j있는 [0, length)우리가 x + i != y + j. 중복되는 경우 최적화가 불가능합니다. C 언어는 restrict두 개의 포인터가 별칭을 사용할 수 없다고 컴파일러에 알리기 위해 키워드를 도입 했지만 정확히 별칭이 아니더라도 겹칠 수 있기 때문에 배열에서는 작동하지 않습니다.
Matthieu M.

현재 GCC 및 Clang은 자동 벡터화됩니다 (생략하면 겹치지 않음을 확인한 후 __restrict). SSE2는 x86-64의 기준선이며 셔플 링을 사용하면 SSE2는 한 번에 2x 32 비트 곱셈을 수행 할 수 있습니다 (64 비트 제품을 생성하므로 셔플 링을 통해 결과를 다시 합치기). godbolt.org/z/r7F_uo . (SSE4.1은 pmulld32x32 => 32 비트 곱하기)에 필요합니다. GCC에는 상수 정수 승수를 시프트 / 더하기 (및 / 또는 빼기)로 바꾸는 깔끔한 트릭이 있습니다. 이는 비트 세트가 적은 승수에 좋습니다. Clang의 셔플이 많은 코드는 Intel CPU의 셔플 처리량에 병목 현상을 일으킬 것입니다.
Peter Cordes

41

짧은 대답 : 예.

긴 대답 : 예, 실제로 무엇을하고 있는지 알지 못하면 그렇게 할 이유가 없습니다.


3
인텔 칩용 vtune과 같은 어셈블리 레벨 프로파일 링 도구를 실행하여 개선 할 수있는 부분을 확인한 경우에만
Mark Mullin

1
이것은 기술적으로 질문에 대답하지만 완전히 쓸모가 없습니다. 나에게서 -1.
Navin

2
매우 긴 대답 : "예. 새로운 CPU가 사용될 때마다 전체 코드를 바꾸고 싶지 않다면 최상의 알고리즘을 선택하되 컴파일러가 최적화하도록하십시오"
Tommylee2k

35

내 asm 코드를 수정했습니다.

  __asm
{   
    mov ebx,TIMES
 start:
    mov ecx,lengthOfArray
    mov esi,x
    shr ecx,1
    mov edi,y
label:
    movq mm0,QWORD PTR[esi]
    paddd mm0,QWORD PTR[edi]
    add edi,8
    movq QWORD PTR[esi],mm0
    add esi,8
    dec ecx 
    jnz label
    dec ebx
    jnz start
};

출시 버전의 결과 :

 Function of assembly version: 81
 Function of C++ version: 161

릴리스 모드의 어셈블리 코드는 C ++보다 거의 2 배 빠릅니다.


18
이제 MMX 대신 SSE를 사용하기 시작하면 (등록 이름이 xmm0대신 mm0) 2 배 더
빨라집니다.

8
어셈블리 버전으로 41이 변경되었습니다. 그것은 4 배 빠릅니다 :)
sasha

3
모든 xmm 레지스터를 사용하면 최대 5 % 더 많이 얻을 수 있습니다
sasha

7
이제 실제로 소요 된 시간에 대해 생각한다면 조립, 약 10 시간 정도? C ++, 몇 분 정도 될까요? 성능에 중요한 코드가 아닌 한 여기에 확실한 승자가 있습니다.
Calimo

1
좋은 컴파일러는 이미로 자동 벡터화됩니다 paddd xmm( 와를 사용하지 않았기 때문에 x와 사이의 겹침을 확인한 후 ). 예를 들어 gcc는 godbolt.org/z/c2JG0-를 수행 합니다. 또는에 인라인 한 후에 는 할당을보고 중복되지 않음을 증명할 수 있으므로 오버랩을 확인할 필요가 없습니다. (그리고 일부 x86-64 구현에서도 16 바이트 정렬을 가정합니다. 이는 독립 실행 형 정의의 경우가 아닙니다.)으로 컴파일 하면 256 비트 또는 512 비트를 얻을 수 있습니다 벡터화. yint *__restrict xmaingcc -O3 -march=native
Peter Cordes 2016 년

24

내 손으로 쓴 어셈블리 언어의 성능을 신뢰해서는 안된다는 의미입니까?

예, 그것이 정확히 의미하는 바이며 모든 사람 에게 해당 됩니다 언어에 해당됩니다. 언어 X로 효율적인 코드를 작성하는 방법을 모르는 경우 X로 효율적인 코드를 작성하는 능력을 신뢰해서는 안됩니다. 따라서 효율적인 코드를 원한다면 다른 언어를 사용해야합니다.

어셈블리는 특히 이것에 민감합니다. 왜냐하면 당신이 보는 것이 당신이 얻는 것이기 때문입니다. CPU가 실행할 특정 명령어를 작성합니다. 고급 언어를 사용하면 코드를 변환하고 많은 비 효율성을 제거 할 수있는 컴파일러가 betweeen에 있습니다. 조립을 통해 당신은 스스로 할 수 있습니다.


2
특히 최신 x86 프로세서의 경우 파이프 라인, 여러 실행 단위 및 모든 코어 내부의 다른 특수 효과로 인해 효율적인 어셈블리 코드를 작성하는 것이 매우 어렵다고 생각합니다. 가장 빠른 실행 속도를 얻기 위해 이러한 모든 리소스의 사용 균형을 맞추는 코드를 작성하면 "전통적인"어셈블리 지혜에 따라 "빠르지 않아야"하는 논리가 불명확 한 코드가됩니다. 그러나 덜 복잡한 CPU의 경우 C 컴파일러의 코드 생성이 크게 향상 될 수있는 경험입니다.
Olof Forshell

4
최신 x86 CPU에서도 C 컴파일러 코드 개선 할 수 있습니다. 그러나 최신 x86 CPU로는 처리하기 어려운 CPU를 잘 이해해야합니다. 그게 내 요점이야 타겟팅하는 하드웨어를 이해하지 못하면 최적화 할 수 없습니다. 그리고 컴파일러는 더 나은 일을 할 것입니다
jalf

1
그리고 실제로 컴파일러를 날려 버리려면 컴파일러가 할 수없는 방식으로 창의적이고 최적화해야합니다. 시간 / 보상과의 상충이므로 C는 일부 언어의 스크립팅 언어이고 다른 언어의 경우 고급 언어의 중간 코드입니다. 나를 위해, 어셈블리는 재미를 위해 더 있습니다 :). grc.com/smgassembly.htm
Hawken

22

오늘날 어셈블리 언어를 사용하는 유일한 이유는 언어로 액세스 할 수없는 일부 기능을 사용하기위한 것입니다.

이것은 다음에 적용됩니다.

  • MMU와 같은 특정 하드웨어 기능에 액세스해야하는 커널 프로그래밍
  • 컴파일러에서 지원하지 않는 매우 특정한 벡터 또는 멀티미디어 명령어를 사용하는 고성능 프로그래밍.

그러나 현재 컴파일러는 매우 영리 d = a / b; r = a % b;합니다 .C에 그러한 연산자가없는 경우에도 분할을 계산하고 사용 가능한 경우 한 번에 나머지를 계산하는 단일 명령으로 두 개의 별도 명령문을 바꿀 수도 있습니다 .


10
ASM에는이 두 곳 외에 다른 곳이 있습니다. 즉, bignum 라이브러리는 플래그를 전달할 수 있고 곱셈의 윗부분 등으로 인해 C보다 ASM에서 훨씬 빠릅니다. 휴대용 C에서도 이러한 작업을 수행 할 수 있지만 속도는 매우 느립니다.
Mooing Duck

@MooingDuck 언어에서 직접 사용할 수없는 하드웨어 하드웨어 기능에 액세스하는 것으로 간주 될 수 있습니다 ... 그러나 고급 코드를 직접 수작업으로 번역 하는 한, 컴파일러가 당신을 이길 것입니다.
포트란

1
그것은 커널 프로그래밍이나 벤더 고유의 것이 아닙니다. 약간의 가공 변경 사항이 있지만 쉽게 두 범주에 속할 수 있습니다. C 매핑이없는 프로세서 명령의 성능을 원할 때 ASM을 추측하십시오.
Mooing Duck

1
@fortran 기본적으로 코드를 최적화하지 않으면 컴파일러가 최적화 한 코드만큼 빠르지 않습니다. 최적화는 처음부터 어셈블리를 작성하는 이유입니다. 번역을 최적화하고 최적화한다는 의미라면 어셈블리 최적화에 능숙하지 않으면 컴파일러가 당신을 이길 이유가 없습니다. 따라서 컴파일러를이기려면 컴파일러가 할 수없는 방식으로 최적화해야합니다. 꽤 자명하다. 어셈블리를 작성하는 유일한 이유는 컴파일러 / 인터프리터보다 나은 경우 입니다. 그것이 항상 어셈블리를 작성하는 실질적인 이유였습니다.
Hawken

1
Clang은 내장 함수를 통해 캐리 플래그, 128 비트 곱셈 등에 액세스 할 수 있습니다. 그리고이 모든 것을 일반적인 최적화 알고리즘에 통합 할 수 있습니다.
gnasher729

19

현대 컴파일러가 코드 최적화에서 놀라운 일을하는 것은 사실이지만, 여전히 어셈블리 학습을 계속하도록 권장합니다.

우선, 당신은 분명히 그것에 의해 위협받지 않습니다. 그것은 위대하고 큰 플러스입니다. 다음은 속도 가정을 검증하거나 버릴 수 있도록 프로파일 링 하여 올바른 길을 가고 있습니다. 경험이 많은 사람들의 의견을 요구 하고 있습니다. 인류에게 알려진 가장 최적화 된 도구 인 뇌 있습니다.

경험이 증가함에 따라 언제 어디에서 사용해야하는지 (일반적으로 알고리즘 수준에서 깊게 최적화 한 후 코드에서 가장 꽉 조여진 루프) 배우게됩니다.

영감을 얻으려면 Michael Abrash 의 기사 를 참조하는 것이 좋습니다 (그에게서 소식을 듣지 못한 경우 그는 최적화 전문가입니다. 그는 심지어 Quake 소프트웨어 렌더러의 최적화에서 John Carmack과 협력했습니다!)

"가장 빠른 코드는 없습니다"-Michael Abrash


2
Michael Abrash 서적 중 하나는 그래픽 프로그래밍 블랙 북이라고 생각합니다. 크리스 소이어는 조립을 사용하는 유일한 사람은 아니었다.
Hawken

14

asm 코드를 변경했습니다.

 __asm
{ 
    mov ebx,TIMES
 start:
    mov ecx,lengthOfArray
    mov esi,x
    shr ecx,2
    mov edi,y
label:
    mov eax,DWORD PTR [esi]
    add eax,DWORD PTR [edi]
    add edi,4   
    dec ecx 
    mov DWORD PTR [esi],eax
    add esi,4
    test ecx,ecx
    jnz label
    dec ebx
    test ebx,ebx
    jnz start
};

출시 버전의 결과 :

 Function of assembly version: 41
 Function of C++ version: 161

릴리스 모드의 어셈블리 코드는 C ++보다 거의 4 배 빠릅니다. IMHo, 어셈블리 코드의 속도는 프로그래머에 따라 다릅니다


예, 제 코드는 실제로 최적화되어야합니다.
user957121

5
당신은 단지 작업의 내무반을하기 때문에 그것은 네 배 빠른 :-) (가) shr ecx,2배열의 길이가 이미 부여되어 있기 때문에, 불필요 int바이트하지. 기본적으로 같은 속도를 얻습니다. 당신은 paddd해롤드 답변을 시도 할 수 있습니다. 이것은 실제로 더 빠를 것입니다.
Gunther Piez

13

매우 흥미로운 주제입니다!
Sasha 코드에서 SSE로 MMX를 변경했습니다
. 결과는 다음과 같습니다.

Function of C++ version:      315
Function of assembly(simply): 312
Function of assembly  (MMX):  136
Function of assembly  (SSE):  62

SSE를 사용한 어셈블리 코드는 C ++보다 5 배 빠릅니다.


12

대부분의 고급 언어 컴파일러는 매우 최적화되어 있으며 수행중인 작업을 알고 있습니다. 디스 어셈블 코드를 덤프하여 원시 어셈블리와 비교할 수 있습니다. 컴파일러에서 사용하는 몇 가지 유용한 트릭을 보게 될 것입니다.

예를 들어, 더 이상 확실하지 않다고해도 :) :

하기:

mov eax,0

보다 많은 사이클 비용

xor eax,eax

같은 일을합니다.

컴파일러는 이러한 모든 트릭을 알고 사용합니다.


4
여전히 그렇습니다 . stackoverflow.com/questions/1396527/…을 참조하십시오 . 사용 된주기 때문이 아니라 감소 된 메모리 풋 프린트 때문입니다.
Gunther Piez

10

컴파일러가 당신을 이겼습니다. 나는 그것을 시도 할 것이지만, 나는 어떤 보장도하지 않을 것이다. 나는 타임스에 의해 "곱셈은"그것은 더 관련 성능 테스트 할 의미가 있다고 가정하는 것입니다 yx16 정렬하고, 그 length4. 그의 아마 진정한 어쨌든의 비 - 제로 여러이다.

  mov ecx,length
  lea esi,[y+4*ecx]
  lea edi,[x+4*ecx]
  neg ecx
loop:
  movdqa xmm0,[esi+4*ecx]
  paddd xmm0,[edi+4*ecx]
  movdqa [edi+4*ecx],xmm0
  add ecx,4
  jnz loop

내가 말했듯이, 나는 보장하지 않습니다. 그러나 훨씬 더 빨리 수행 할 수 있다면 놀랄 것입니다-병목 현상은 모든 것이 L1 적중이라도 메모리 처리량입니다.


복잡한 주소 지정이 코드 속도를 늦추고 있다고 생각합니다. 코드를 변경 mov ecx, length, lea ecx,[ecx*4], mov eax,16... add ecx,eax한 다음 어디에서나 [esi + ecx]를 사용하면 명령 당 1 사이클 스톨을 피하여 루프 로트 속도를 높일 수 있습니다. (최신 Skylake가있는 경우 적용되지 않습니다). add reg, reg는 루프를 더 단단하게 만들어 도움이 될 수도 있고하지 않을 수도 있습니다.
Johan

@Johan은 실속해서는 안되며 여분의 사이클 대기 시간이 필요하지만 손상되지 않도록해야합니다. 나는 그 문제가없는 Core2 용이 코드를 작성했습니다. r + r도 "복잡"하지 않습니까?
harold

7

어셈블리에서 정확히 동일한 알고리즘, 명령어 별 명령어를 구현하는 것은 컴파일러가 할 수있는 것보다 느리게 보장 됩니다.

컴파일러가 수행하는 가장 작은 최적화조차도 전혀 최적화되지 않은 엄격한 코드보다 낫기 때문입니다.

물론 컴파일러를 이길 수 있습니다. 특히 코드의 지역화 된 작은 부분 인 경우에는 약을 얻기 위해 직접해야했습니다. 속도는 4 배 빨라지지만이 경우 하드웨어에 대한 지식이 풍부하고 직관적으로 보이는 수많은 직관적 인 트릭에 크게 의존해야합니다.


3
나는 이것이 언어와 컴파일러에 달려 있다고 생각합니다. 인간이 직접 작성하는 어셈블리를 통해 쉽게 출력을 얻을 수있는 매우 비효율적 인 C 컴파일러를 상상할 수 있습니다. 그리 많지 않은 GCC.
Casey Rodarmor 2016 년

C / ++ 컴파일러가 그런 일을하고 3 개의 주요 컴파일러 만 있으면서도 자신이하는 일에 다소 능숙합니다. 특정 상황에서 수작업으로 작성된 어셈블리가 더 빠를 수도 있습니다. 많은 수학 라이브러리가 여러 값을 처리하기 위해 asm으로 떨어집니다. 따라서 보장 된 것이 너무 강하지 만 가능성이 높습니다.
ssube

@ peachykeen : 어셈블리가 일반적으로 C ++보다 느리다는 것을 의미하지는 않았습니다. 나는 당신이 C ++ 코드를 가지고 있고 라인별로 어셈블리로 맹목적으로 번역하는 경우 "보증인"을 의미했다. 내 답변의 마지막 단락도 읽으십시오 :)
vsz

5

컴파일러는 루프를 고정 크기로 많은 실행 작업으로 대체합니다.

int a = 10;
for (int i = 0; i < 3; i += 1) {
    a = a + i;
}

생산할 것이다

int a = 10;
a = a + 0;
a = a + 1;
a = a + 2;

결국 "a = a + 0;" 쓸모가 없으므로이 줄을 제거합니다. 바라건대 머리 속에 무언가가 이제 의견으로 일부 최적화 옵션을 기꺼이 첨부 할 것입니다. 매우 효과적인 최적화는 컴파일 된 언어를 더 빠르게 만듭니다.


4
a휘발성이 아니라면 컴파일러가 int a = 13;처음부터 시작할 가능성이 높습니다 .
vsz


4

이 예제는 저수준 코드에 대한 중요한 교훈을 보여주기 때문에 좋아합니다. 예, C 코드만큼 빠른 어셈블리를 작성할 수 있습니다 . 이것은 사실적으로 사실이지만 반드시 의미가 있는 것은 아닙니다. 분명히 누군가가 할 수 있다면 그렇지 않으면 어셈블러는 적절한 최적화를 알지 못할 것입니다.

마찬가지로 언어 추상화의 계층 구조를 올라갈 때와 동일한 원칙이 적용됩니다. 그렇습니다. C에서 파서가 빠르고 펄 스크립트처럼 빠르면 많은 사람들이 파서를 작성할 수 있습니다 . 그러나 이것이 C를 사용했기 때문에 코드가 빠르다는 것을 의미하지는 않습니다. 대부분의 경우 고급 언어는 고려하지 않은 최적화를 수행합니다.


3

대부분의 경우 일부 작업을 수행하는 최적의 방법은 작업이 수행되는 컨텍스트에 따라 달라질 수 있습니다. 루틴이 어셈블리 언어로 작성된 경우 일반적으로 명령 순서가 컨텍스트에 따라 달라질 수 없습니다. 간단한 예로 다음과 같은 간단한 방법을 고려하십시오.

inline void set_port_high(void)
{
  (*((volatile unsigned char*)0x40001204) = 0xFF);
}

위의 32 비트 ARM 코드 컴파일러는 다음과 같이 렌더링 할 수 있습니다.

ldr  r0,=0x40001204
mov  r1,#0
strb r1,[r0]
[a fourth word somewhere holding the constant 0x40001204]

또는 아마도

ldr  r0,=0x40001000  ; Some assemblers like to round pointer loads to multiples of 4096
mov  r1,#0
strb r1,[r0+0x204]
[a fourth word somewhere holding the constant 0x40001000]

이는 다음과 같이 수동으로 조립 된 코드에서 약간 최적화 될 수 있습니다.

ldr  r0,=0x400011FF
strb r0,[r0+5]
[a third word somewhere holding the constant 0x400011FF]

또는

mvn  r0,#0xC0       ; Load with 0x3FFFFFFF
add  r0,r0,#0x1200  ; Add 0x1200, yielding 0x400011FF
strb r0,[r0+5]

수동 조립 방식 모두 16 바이트가 아닌 12 바이트의 코드 공간이 필요합니다. 후자는 "load"를 "add"로 대체하며, ARM7-TDMI에서는 두 사이클을 더 빠르게 실행합니다. r0이 모르거나 신경 쓰지 않는 컨텍스트에서 코드가 실행되면 어셈블리 언어 버전이 컴파일 된 버전보다 약간 낫습니다. 반면에 컴파일러가 일부 레지스터 [예 : r5]가 원하는 주소 0x40001204 [예 : 0x40001000]의 2047 바이트 내에있는 값을 보유하고 있고 다른 레지스터 [예 : r7]가 가고 있음을 더 알고 있다고 가정합니다. 낮은 비트가 0xFF 인 값을 보유합니다. 이 경우 컴파일러는 다음과 같이 간단하게 C 버전의 코드를 최적화 할 수 있습니다.

strb r7,[r5+0x204]

수동으로 최적화 된 어셈블리 코드보다 훨씬 짧고 빠릅니다. 또한 컨텍스트에서 set_port_high가 발생했다고 가정하십시오.

int temp = function1();
set_port_high();
function2(temp); // Assume temp is not used after this

임베디드 시스템을 코딩 할 때는 전혀 불가능합니다. 만약 네 개의 명령어 더 작고 더 빠른set_port_high어셈블리 코드로 작성된 , 컴파일러는 function1어셈블리 코드를 호출하기 전에 r0 (에서 반환 값을 보유 함 )을 다른 곳으로 이동 한 다음 나중에 r0으로 다시 이동해야합니다 (r0 function2의 첫 번째 매개 변수가 예상되므로). 따라서 "최적화 된"어셈블리 코드에는 5 개의 명령어가 필요합니다. 컴파일러가 주소 나 저장할 값을 보유한 레지스터를 알지 못하더라도 4 개의 명령어 버전 (r0 및 r1이 아닌 사용 가능한 레지스터를 사용하도록 조정할 수 있음)은 "최적화 된"어셈블리를 능가합니다. 언어 버전. 컴파일러가 앞에서 설명한 것처럼 r5와 r7에 필요한 주소와 데이터를 명령으로function1 해당 레지스터를 변경하지 않으므로 대체 할 수 있습니다.set_port_high 단일strb"수동으로 최적화 된"어셈블리 코드보다 .

프로그래머가 정확한 프로그램 흐름을 알고있는 경우 수동 최적화 된 어셈블리 코드가 종종 컴파일러보다 성능이 우수 할 수 있지만, 컨텍스트가 알려지기 전에 코드가 작성된 경우 또는 소스 코드가있는 경우 컴파일러가 빛을 발합니다. 여러 컨텍스트에서 호출 된 경우 [ set_port_high코드에서 50 개의 다른 위치에서 사용되는 경우 컴파일러는이를 확장하는 최선의 방법을 각 사용자에게 독립적으로 결정할 수 있음]

일반적으로 어셈블리 언어는 매우 제한된 수의 컨텍스트에서 각 코드 조각에 접근 할 수있는 경우 성능이 크게 향상 될 수 있고, 조각이있는 곳에서는 성능이 저하 될 수 있다고 제안합니다. 코드는 다양한 상황에서 접근 할 수 있습니다. 흥미롭고 (편리하게) 어셈블리가 성능에 가장 유리한 경우는 종종 코드가 가장 간단하고 읽기 쉬운 경우입니다. 어셈블리 언어 코드가 끈적 끈적한 혼란으로 변하는 곳은 종종 어셈블리로 작성하는 것이 가장 작은 성능상의 이점을 제공하는 곳입니다.

[사소한 참고 : 어셈블리 코드를 사용하여 과도하게 최적화 된 끈적 끈적한 혼란을 일으킬 수있는 곳이 있습니다. 예를 들어, ARM에 대해 한 코드 조각은 RAM에서 단어를 가져 와서 값의 상위 6 비트 (동일한 루틴에 매핑 된 많은 값)를 기준으로 약 12 ​​개의 루틴 중 하나를 실행해야했습니다. 나는 그 코드를 다음과 같이 최적화했다고 생각한다.

ldrh  r0,[r1],#2! ; Fetch with post-increment
ldrb  r1,[r8,r0 asr #10]
sub   pc,r8,r1,asl #2

레지스터 r8은 항상 주 디스패치 테이블의 주소를 유지했습니다 (코드가 98 %의 시간을 소비하는 루프 내에서 다른 용도로는 사용되지 않았습니다). 64 개의 모든 항목은 그 앞에있는 256 바이트의 주소를 나타냅니다. 기본 루프에는 대부분 약 60주기의 하드 실행 시간 제한이 있으므로 9주기 페치 및 디스패치는 해당 목표를 달성하는 데 매우 중요합니다. 256 개의 32 비트 주소 테이블을 사용하면 한주기 빨라졌지만 1KB의 매우 소중한 RAM이 늘어났습니다 (플래시는 둘 이상의 대기 상태를 추가했을 것입니다). 64 개의 32 비트 주소를 사용하려면 가져온 단어에서 일부 비트를 마스킹하는 명령을 추가해야했지만 실제로 사용한 테이블보다 192 바이트가 더 많았습니다. 8 비트 오프셋 표를 사용하면 매우 작고 빠른 코드가 생성됩니다. 그러나 컴파일러가 기대할 수있는 것은 아닙니다. 또한 컴파일러가 테이블 주소를 보유하기 위해 레지스터 "풀 타임"을 전용으로 사용하지 않을 것입니다.

위의 코드는 자체 포함 된 시스템으로 실행되도록 설계되었습니다. 주기적으로 C 코드를 호출 할 수 있지만, 통신하고 있던 하드웨어가 16ms마다 약 1 밀리 초 간격으로 "유휴"상태에 놓일 수있는 특정 시간에만 가능합니다.


2

최근에 내가 한 모든 속도 최적화는 뇌 손상 느린 코드를 합리적인 코드로 대체하는 것이 었습니다. 그러나 속도가 매우 중요하고 빠른 것을 만들기 위해 진지한 노력을 기울 였기 때문에 결과는 항상 반복적 인 프로세스였습니다. 각 반복은 문제에 대해 더 많은 통찰력을 제공하고 적은 작업으로 문제를 해결하는 방법을 찾았습니다. 최종 속도는 항상 문제에 대한 통찰력의 정도에 달려있었습니다. 어느 단계에서나 어셈블리 코드 또는 과도하게 최적화 된 C 코드를 사용하면 더 나은 솔루션을 찾는 프로세스가 어려워 져 최종 결과가 느려집니다.


2

C ++은 올바른 방법으로 더 깊은 지식을 가진 어셈블리 언어를 사용하지 않는 한 더 빠릅니다.

ASM으로 코딩 할 때 논리적으로 가능한 경우 CPU가 더 많은 명령을 병렬로 실행할 수 있도록 명령을 수동으로 재구성합니다. 예를 들어 ASM에서 코드를 작성할 때 RAM을 거의 사용하지 않습니다.

자체 수정 코드를 사용하지 않으면 서 코드와 동작을 자체 수정하기 위해 opcode의 중간을 뛰어 넘을 수 있습니다. 레지스터에 액세스하려면 CPU의 1 틱 (때로는 0.2525 틱)이 필요합니다.

마지막 ASM 모험에서는 한 번도 RAM을 사용하여 변수를 저장하지 않았습니다 (수천 줄의 ASM). ASM은 C ++보다 상상할 수 없을 정도로 빠를 수 있습니다. 그러나 다음과 같은 많은 변수에 달려 있습니다.

1. I was writing my apps to run on the bare metal.
2. I was writing my own boot loader that was starting my programs in ASM so there was no OS management in the middle.

생산성 문제를 깨달았 기 때문에 이제 C #과 C ++을 배우고 있습니다 !! 자유 시간에 순수한 ASM 만 사용하여 가장 빠르게 상상할 수있는 프로그램을 시도 할 수 있습니다. 그러나 무언가를 생산하려면 고급 언어를 사용하십시오.

예를 들어, 마지막으로 코딩 한 프로그램은 JS와 GLSL을 사용하는 것이 었으며 JS에 대해 말하는 심지어 성능 문제는 전혀 발견하지 못했습니다. 이는 3D 용 GPU를 프로그래밍한다는 단순한 개념이 명령을 GPU로 보내는 언어의 속도를 거의 무관하기 때문입니다.

베어 메탈에서의 어셈블러 단독 속도는 반박 할 수 없습니다. C ++에서 더 느려질 수 있습니까? -어셈블러를 사용하지 않는 컴파일러로 어셈블리 코드를 작성하기 때문일 수 있습니다.

제 개인 평의회는 어셈블리를 좋아하지만 피할 수 있다면 어셈블리 코드를 작성하지 않는 것입니다.


1

여기에있는 모든 대답은 한 가지 측면을 배제하는 것처럼 보입니다. 때로는 특정 목표를 달성하기 위해 코드를 작성하지 않지만 그 재미 를 위해. 그렇게하는 데 시간을 투자하는 것은 경제적이지 않을 수 있지만, 수동으로 롤링 된 asm 대안으로 가장 빠른 컴파일러 최적화 코드 스 니펫을 속도로 치는 것보다 더 큰 만족은 없을 것입니다.


컴파일러를 이기고 싶을 때는 일반적으로 함수의 asm 출력을 가져 와서 조정하는 독립형 asm 함수로 만드는 것이 더 쉽습니다. 인라인 asm을 사용 하는 것은 C ++과 asm 사이의 인터페이스를 수정하고 최적의 코드로 컴파일되는지 확인하기위한 추가 작업입니다. (적어도 재미를 위해 할 때 함수가 다른 것으로 인라인 할 때 상수 전파와 같은 최적화를 물리 치는 것에 대해 걱정할 필요가 없습니다. gcc.gnu.org/wiki/DontUseInlineAsm ).
Peter Cordes

재미를 위해 컴파일러를 꺾는 방법에 대한 자세한 내용 은 Collatz-conjecture C ++ 대 손으로 쓴 asm Q & A 를 참조하십시오.
Peter Cordes

@PeterCordes 당신이 말하는 것은 당신이 동의한다는 것입니다.
madoki

1
그렇습니다. 인라인 asm은 일반적으로 장난을 치 더라도 잘못된 선택입니다. 이것은 기술적으로 인라인 asm 질문이므로 적어도 대답 에서이 점을 해결하는 것이 좋습니다. 또한 이것은 실제로 답변보다 더 많은 의견입니다.
Peter Cordes 2016

동의했다. 나는 예전의 유일한 사람 이었지만 80 년대였습니다.
madoki

-2

c ++ 컴파일러는 조직 수준에서 최적화 한 후 대상 CPU의 내장 기능을 활용하는 코드를 생성합니다. HLL은 여러 가지 이유로 어셈블러를 초과하거나 초과하지 않습니다. 1.) HLL은 접근 자 코드, 경계 검사 및 가비지 수집 (이전의 OOP 방식에서 범위를 다루는)에 내장되어 모든 사이클 (플랩 및 플롭)을 필요로하며 컴파일 및 출력됩니다. HLL은 요즘 훌륭한 작업을 수행하지만 (새로운 C ++ 및 GO와 같은 다른 것들 포함) 어셈블러 (즉, 코드)를 능가하는 경우 CPU 설명서를 참조해야합니다. 조잡한 코드가있는 비교는 결정적이지 않으며 어셈블러와 같은 컴파일 된 언어는 모두 해결됩니다. op-code HLL까지 세부 사항을 추상화하고 호스트 OS에서 인식하는 경우 앱이 실행되지 않는 다른 세부 사항을 제거하지 않습니다.

대부분의 어셈블러 코드 (주로 객체)는 처리 속도가 훨씬 적은 다른 실행 가능 형식에 포함하기 위해 "headless"로 출력되므로 훨씬 빠르지 만 안전하지는 않습니다. 실행 파일이 어셈블러 (NAsm, YAsm 등)에 의해 출력되는 경우 기능의 HLL 코드와 완전히 일치 할 때까지 여전히 더 빠르게 실행되며 결과가 정확하게 측정 될 수 있습니다.

어떤 형식 으로든 HLL에서 어셈블러 기반 코드 객체를 호출하면 변수 / 일정한 데이터 유형에 대해 전역 적으로 할당 된 메모리를 사용하는 메모리 공간 호출 외에 기본적으로 처리 오버 헤드가 추가됩니다 (LLL 및 HLL 모두에 적용됨). 최종 출력은 하드웨어 (opcode)와 관련하여 CPU를 api 및 abi로 궁극적으로 사용하고 있으며 어셈블러 및 "HLL 컴파일러"는 본질적으로 / 가장 근본적으로 읽기 쉬운 유일한 (문법적) 예외와 동일합니다.

FAsm을 사용한 어셈블러의 Hello World Console 응용 프로그램은 1.5KB이며 (FreeBSD 및 Linux의 경우 Windows에서는 훨씬 더 작음) GCC가 최상의 날에 버릴 수있는 모든 것을 능가합니다. 이유는 nops, 액세스 유효성 검사 및 경계 검사를 통한 암시 적 패딩입니다. 실제 목표는 깨끗한 HLL 라이브러리와 "하드 코어"방식으로 CPU를 대상으로하는 요즘 최적화 된 컴파일러입니다. GCC는 YAsm보다 낫지 않습니다. 문제가되는 개발자의 코딩 실습과 이해이며 "최적화"는 초보자 탐색 및 임시 교육 및 경험을 통해 이루어집니다.

컴파일러는 어셈블러와 동일한 opcode로 출력하기 위해 링크 및 어셈블해야합니다. 이러한 코드는 CPU가 제외한 모든 코드이기 때문입니다 (CISC 또는 RISC [PIC도]). YAsm은 초기 NAsm을 최적화하고 정리하여 궁극적으로 해당 어셈블러의 모든 출력 속도를 높였지만 여전히 NAsm과 같이 YAsm도 개발자를 대신하여 OS 라이브러리를 대상으로 외부 종속성이있는 실행 파일을 생성하므로 마일리지가 달라질 수 있습니다. C ++은 특히 상업 부문에서 80 + % 이상으로 어셈블러보다 믿을 수 없을 정도로 안전합니다 ...


1
C 및 C ++에는 요청하지 않는 한 경계 검사가 없으며 직접 구현하거나 라이브러리를 사용하지 않으면 가비지 수집이 없습니다. 실제 문제는 컴파일러가 인간보다 더 나은 루프 (및 전역 최적화)를 만드는지 여부입니다. 인간이 실제로 무엇을하고 있는지 알고 실제로 많은 시간을 소비 하지 않는 한 일반적으로 그렇습니다 .
Peter Cordes

1
NASM 또는 YASM (외부 코드 없음)을 사용하여 정적 실행 파일을 만들 수 있습니다. 둘 다 플랫 바이너리 형식으로 출력 할 수 있으므로 실제로 실행하지 않으려는 경우 ELF 헤더를 직접 어셈블 할 수는 ld있지만 실제로 파일 크기 (크기뿐만 아니라 파일 크기)를 최적화하지 않으면 차이가 없습니다. 텍스트 세그먼트). Linux 용 Teensy ELF 실행 파일 작성에 대한 회람 학습서를 참조하십시오 .
Peter Cordes

1
아마도 C #을 생각하고 있거나 std::vector디버그 모드로 컴파일 되었을 것 입니다. C ++ 배열은 그렇지 않습니다. 컴파일러는 컴파일 타임에 내용을 확인할 수 있지만 추가 강화 옵션을 활성화하지 않으면 런타임 확인이 없습니다. 예를 들어 int array[]인수 의 처음 1024 요소를 증가시키는 함수를 참조하십시오 . asm 출력에는 런타임 검사가 없습니다 : godbolt.org/g/w1HF5t . 그것이 얻는 것은 rdi크기 정보가없는 포인터입니다 . 1024보다 작은 배열로 호출하지 않고 정의되지 않은 동작을 피하는 것은 프로그래머의 몫입니다.
Peter Cordes

1
당신이 말하는 것은 평범한 C ++ 배열이 아닙니다 (로 할당 new,을 사용하여 수동으로 삭제 delete, 경계 검사 없음). 당신은 할 수 있습니다 (대부분의 소프트웨어와 같은) 엿 비 대한 ASM / 기계 코드를 생성하기 위해 C ++를 사용하지만 프로그래머의 잘못이 아니라 C ++의입니다. alloca스택 공간을 배열로 할당 하는 데 사용할 수도 있습니다 .
Peter Cordes

1
에 예를 링크 gcc.godbolt.orgg++ -O3일반 배열에 대한 코드를 경계 검사 생성, 또는 당신이에 대해 얘기하고 어떤 다른 일을. C ++을 사용하면 부풀린 이진 파일을 훨씬 쉽게 생성 할 수 있습니다 (실제로 성능을 목표로하는 경우 주의 하지 않아야 함). 그러나 문자 그대로 피할 수있는 것은 아닙니다. C ++이 asm으로 컴파일하는 방법을 이해하면 직접 작성하는 것보다 약간 더 나쁜 코드를 얻을 수 있지만 직접 관리 할 수있는 것보다 더 큰 규모의 인라인 및 상수 전파가 가능합니다.
Peter Cordes

-3

컴파일러가 많은 OO 지원 코드를 생성하면 어셈블리가 더 빨라질 수 있습니다 .

편집하다:

downvoters에게 : OP는 "나는 C ++에 집중하고 어셈블리 언어를 잊어야 하는가?"라고 썼다. 나는 내 대답을 기다립니다. 특히 메소드를 사용할 때 항상 OO가 생성하는 코드를 주시해야합니다. 어셈블리 언어를 잊어 버리지 않으면 OO 코드가 생성하는 어셈블리를 정기적으로 검토하여 제대로 작동하는 소프트웨어를 작성해야합니다.

실제로 이것은 OO뿐만 아니라 모든 컴파일 가능한 코드와 관련이 있습니다.


2
-1 : 사용중인 OO 기능이 없습니다. 당신의 주장은 "컴파일러가 백만 NOP를 추가하면 어셈블리가 더 빠를 수도 있습니다"와 같습니다.
Sjoerd

나는 확실하지 않았다. 이것은 실제로 C 질문이다. C ++ 컴파일러 용 C 코드를 작성하면 C ++ 코드를 작성하지 않으며 OO 항목을 얻지 못합니다. 실제 C ++로 작성을 시작하면 OO를 사용하여 컴파일러가 OO 지원 코드를 생성하지 않도록해야합니다.
Olof Forshell

그래서 당신의 대답은 질문에 관한 것이 아닙니다? (또한 설명은 주석이 아닌 답변에 들어갑니다. 주석은 통지, 알림 또는 기록없이 언제든지 삭제할 수 있습니다.
Mooing Duck

1
OO "지원 코드"가 정확히 무엇을 의미하는지 잘 모르겠습니다. 물론 많은 RTTI를 사용하는 경우 컴파일러는 이러한 기능을 지원하기 위해 많은 추가 명령을 작성해야합니다. 그러나 RTTI 사용을 비준하기에 충분히 높은 수준의 문제는 너무 복잡하여 어셈블리에서 쓰기가 가능하지 않습니다. . 물론 당신이 할 수있는 것은 추상 외부 인터페이스 만 OO로 작성하는 것이 중요합니다. 성능에 최적화 된 순수한 절차 코드로 전달합니다. 그러나 응용 프로그램에 따라 C, Fortran, CUDA 또는 가상 상속이없는 단순히 C ++이 어셈블리보다 낫습니다.
leftaroundabout

2
아닙니다. 최소한은 아닙니다. C ++에는 제로 오버 헤드 규칙이라고하는 것이 있으며 대부분의 경우에 적용됩니다. OO에 대해 자세히 알아보십시오. 결국 코드의 가독성이 향상되고 코드 품질이 향상되며 코딩 속도가 향상되며 견고성이 향상됩니다. 또한 임베디드의 경우-더 많은 제어, 임베디드 + OO를 제공하므로 C ++을 사용하면 Java 방식으로 비용이 발생합니다.
Zane
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.