programing

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

firstcheck 2022. 7. 3. 19:53
반응형

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

인라인 어셈블리 언어와 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;
}

그리고 프로세서의 사이클을 얻기 위해 프로그램을 5번 실행하는데, 이는 시간이라고 볼 수 있습니다.매번 위에 언급한 기능 중 하나만 호출합니다.

그리고 여기 결과가 있다.

어셈블리 버전의 기능:

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++에 집중하고 어셈블리 언어를 잊어버려야 한다는 뜻인가요?

네, 거의요.

우선 저수준 언어(이 경우 어셈블리)가 항상 고수준 언어(이 경우 C++ 및 C)보다 빠른 코드를 생성한다는 잘못된 가정부터 시작합니다.그것은 실이 아니에요.C코드는 자바코드보다 항상 빠른가요?아니요, 프로그래머라는 또 다른 변수가 있기 때문입니다.코드 작성 방법과 아키텍처 세부 정보에 대한 지식이 성능에 큰 영향을 미칩니다(이 사례에서 보셨듯이).

컴파일된 코드보다 핸드메이드 어셈블리 코드가 더 좋은 예는 언제든지 만들 수 있지만, 보통 C++ 코드의 500.000 행 이상의 실제 프로그램이 아닌 가상의 예 또는 단일 루틴입니다.)컴파일러는 95% 더 나은 어셈블리 코드를 생성할 수 있다고 생각합니다.또, 드물게, 적은 수, 짧은 수, 높은 사용률의 중요한 루틴이나, 마음에 드는 높은 레벨의 언어에 액세스 할 필요가 있는 경우는, 어셈블리 코드를 작성할 필요가 있습니다.이 복잡함을 경험해보고 싶으세요?SO에 관한 이 멋진 답변을 읽어보십시오.

왜 이런 거죠?

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

어셈블리에서 코드를 작성할 때는 잘 정의된 콜인터페이스를 사용하여 잘 정의된 함수를 작성해야 합니다.그러나 레지스터 할당, 지속적인 전파, 공통 하위 표현 제거, 명령 스케줄링 및 명백하지 않은 다른 복잡함(예: Polytope 모델)과 같은 전체 프로그램 최적화와 프로시저 간 최적화를 고려할 수 있습니다.RISC 아키텍처에서는 몇 년 전부터 이 문제에 대한 우려가 없어지고 있습니다(예를 들어 명령 스케줄은 수작업으로 조정하기가 매우 어렵습니다).또, 최신의 CISC CPU에도 매우 긴 파이프라인이 있습니다.

일부 복잡한 마이크로 컨트롤러의 경우 컴파일러가 더 나은 최종 코드를 생성하기 때문에 시스템 라이브러리도 어셈블리가 아닌 C로 작성됩니다.

컴파일러는 일부 MMX/SIMDx 명령을 자동으로 사용할 수 있으며, 사용하지 않으면 비교할 수 없습니다(다른 답변은 이미 어셈블리 코드를 잘 검토했습니다).루프에 대해서만 컴파일러에 의해 일반적으로 체크되는 루프 최적화의 간단한 리스트입니다(C# 프로그램의 스케줄이 정해졌을 때 스스로 할 수 있다고 생각하십니까).조립해서 뭔가를 쓴다면 적어도 몇 가지 간단한 최적화를 고려해야 한다고 생각합니다.배열의 교과서 예는 사이클을 전개하는 것입니다(컴파일 시에 사이즈를 알 수 있습니다).다시 테스트하고 다시 실행하세요.

오늘날에는 또 다른 이유로 어셈블리 언어를 사용해야 하는 경우가 거의 없습니다. 즉, CPU가 너무 많습니다.당신은 그들 모두를 지지하고 싶나요?각각 특정 마이크로아키텍처와 몇 가지 특정 명령 세트가 있습니다.기능 유닛의 개수가 다르므로 조립 명령어는 모두 비지니스 상태가 되도록 준비해야 합니다.C로 쓰면 PGO를 사용할 수 있지만 어셈블리에서는 특정 아키텍처에 대한 충분한 지식이 필요합니다(그리고 다른 아키텍처에 대해 모든 것을 재고하고 다시 실행합니다).작은 태스크에서는 컴파일러가 보통 더 잘합니다.복잡한 태스크에서는 보통 작업이 보상되지 않습니다(그리고 컴파일러는 더 할 수 있습니다).

앉아서 코드를 살펴보면 어셈블리로 변환하는 것보다 알고리즘을 재설계하는 것이 더 효과적이라는 것을 알 수 있을 것입니다(여기 SO에 대한훌륭한 게시물 참조). 어셈블리 언어를 사용하기 전에 효과적으로 적용할 수 있는 고급 최적화(및 컴파일러에 대한 힌트).대부분의 경우 내장 함수를 사용하면 원하는 성능을 얻을 수 있으며 컴파일러는 여전히 대부분의 최적화를 수행할 수 있습니다.

이 모든 것은, 5~10배의 고속 어셈블리 코드를 작성할 수 있는 경우에도, 고객에게 1주일의 시간을 지불하고 싶은지, 50달러의 고속 CPU를 구입하고 싶은지 물어보는 것이 좋습니다.대부분의 고객에게는 극단적인 최적화가 필요 없습니다(특히 LOB 어플리케이션의 경우).

어셈블리 코드가 최적 상태가 아니므로 다음과 같이 개선할 수 있습니다.

  • 내부 루프에서 레지스터(EDX)를 푸시 및 팝하고 있습니다.이 루프는 루프에서 벗어나야 합니다.
  • 루프가 반복될 때마다 어레이 포인터를 새로고침합니다.이것은 루프에서 벗어나야 합니다.
  • 요.loop대부분의 최신 CPU에서 매우 느린 것으로 알려진 명령어(아마도 오래된 어셈블리북을 사용한 결과*)
  • 수동 루프 롤링은 이용하지 않습니다.
  • 사용 가능한 SIMD 지침을 사용하지 않습니다.

따라서 어셈블러에 관한 스킬을 대폭 개선하지 않는 한 퍼포먼스를 위해 어셈블러 코드를 작성하는 것은 의미가 없습니다.

로 그 *를 받았는지는 loop수 없습니다.스마트하게 컴파일러를 사용할 수 입니다. 왜냐하면 모든 컴파일러가 충분히 스마트하기 때문입니다.loopIMHO의 약칭입니다.

최신 컴파일러가 코드 최적화를 훌륭하게 수행하고 있는 것은 사실이지만, 그래도 조립을 계속 배우셨으면 합니다.

우선, 당신은 분명히 그것에 겁먹지 않습니다.그것은 훌륭한 플러스입니다.다음으로는 속도 가정을 검증하거나 폐기하기 위해 프로파일링을 통해 올바른 방향으로 나아가고 있습니다.경험이 풍부한 사람에게 조언을 구하고 있습니다.인류가 알고 있는 최고의 최적화 도구인 뇌를 가지고 있습니다.

사용 경험이 증가함에 따라 사용 시기와 장소를 알게 됩니다(일반적으로 알고리즘 수준에서 깊이 최적화한 후 코드에서 가장 엄격한 내부 루프).

영감을 얻으려면 Michael Abrash의 기사를 찾아보라고 권하고 싶습니다(그는 최적화 전문가입니다.그는 John Carmack과 함께 Quake 소프트웨어 렌더러의 최적화에 협력하기도 했습니다).

"가장 빠른 코드란 없다" - 마이클 애버시

조립을 검토하기 전에 더 높은 수준의 코드 변환이 존재합니다.

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)는 이 변환을 수행하지 않는 것 같습니다.

[edit] 변환이 이루어지는 것을 알았습니다.restrict합니다.x ★★★★★★★★★★★★★★★★★」y 이 , this런면면면면면면면면면면면면면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 버전을 사용하는 것이 훨씬 더 휴대성이 좋습니다.

오늘날 어셈블리 언어를 사용하는 유일한 이유는 언어로 접근할 수 없는 일부 기능을 사용하기 위해서입니다.

이는 다음 항목에 적용됩니다.

  • MMU 등의 특정 하드웨어 기능에 액세스해야 하는 커널 프로그래밍
  • 컴파일러가 지원하지 않는 매우 구체적인 벡터 또는 멀티미디어 명령을 사용하는 고성능 프로그래밍입니다.

현재 두 .d = a / b; r = a % b;C에 이러한 연산자가 없는 경우에도 분할과 나머지를 한 번에 계산하는 단일 명령을 사용합니다.

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배 빠릅니다.

!!은!!!!!!!!!!
로 SSE에 했습니다.
을 사용하다

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

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

명령어에 의한 명령어 같은 알고리즘을 무작정 구현하기만 하면 컴파일러가 할 수 있는 것보다 속도가 느려집니다.

컴파일러가 하는 최소한의 최적화라도 최적화가 전혀 없는 경직된 코드보다 낫기 때문입니다.

물론 컴파일러를 이길 수 있습니다.특히 코드의 작은 현지화 부분일 경우, 약 4배의 속도를 내기 위해 직접 컴파일러를 해야 했지만, 이 경우 하드웨어에 대한 충분한 지식과 직관에 반하는 수많은 속임수에 크게 의존해야 합니다.

여기서의 모든 답변은 한 가지 측면을 배제하는 것처럼 보인다.때로는 우리는 특정한 목적을 달성하기 위해 코드를 작성하는 것이 아니라 순전히 재미를 위해 코드를 작성하는 것이다.그렇게 하는 데 시간을 투자하는 것은 경제적이지 않을 수 있지만, 수동 롤형 대안으로 가장 빠른 컴파일러 최적화 코드 조각보다 더 큰 만족은 없습니다.

내 손으로 쓴 어셈블리 언어 성능을 신뢰하면 안 된다는 뜻인가요?

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

의회는 특히 이것에 민감합니다. 왜냐하면, 여러분이 보는 것이 여러분이 얻는 것이기 때문입니다.CPU에서 실행할 특정 명령을 작성합니다.고급 언어를 사용하는 경우 코드를 변환하고 많은 비효율성을 제거할 수 있는 컴파일러가 있습니다.조립을 하면 알아서 할 수 있어

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

ASM에서 코드를 작성할 때는 명령어를 수동으로 재구성하여 논리적으로 가능한 경우 CPU가 병렬로 실행할 수 있도록 합니다.ASM에서 코드화할 때 RAM을 거의 사용하지 않습니다.다음은 예를 제시하겠습니다.ASM에는 20000줄 이상의 코드가 있을 수 있으며, 저는 push/pop을 사용한 적이 없습니다.

opcode의 중간에 끼어들어 코드와 동작을 자기수정할 수 있습니다.자체수정코드의 패널티가 발생하지 않을 수도 있습니다.레지스터에 액세스하려면 CPU의 1틱(때로는 0.25틱)이 필요합니다.RAM에 액세스하려면 수백 시간이 걸릴 수 있습니다.

지난번 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가 느리다고 해도 성능 문제는 전혀 눈치채지 못했습니다.이는 GPU를 3D로 프로그래밍하는 개념만으로 GPU에 명령을 전송하는 언어의 속도가 거의 무관하기 때문입니다.

나금속 위에서 조립하는 속도만으로는 반박할 수 없다.C++ 내부에서는 더 느릴 수 있습니까?- 컴파일러를 사용하지 않고 컴파일러로 어셈블리 코드를 작성하기 때문일 수 있습니다.

제 개인적인 협의회는 당신이 피할 수 있다면 절대 조립 코드를 작성하지 않는 것입니다. 제가 조립을 좋아하긴 하지만요.

컴파일러로서 나는 많은 실행 태스크에 고정된 크기의 루프를 대체할 것이다.

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;"이 쓸모없다는 것을 알게 되므로 이 선을 제거합니다.이제 머리 속에 코멘트로 몇 가지 최적화 옵션이 추가되기를 바랍니다.이러한 매우 효과적인 최적화를 통해 컴파일된 언어가 더욱 빨라집니다.

간단한 답변: 네.

장황한 답변: 네, 당신이 정말 무엇을 하고 있는지, 그리고 그렇게 할 이유가 없다면요.

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, 어셈블리 코드의 속도는 프로그래머에 따라 달라집니다.

대부분의 고급 언어 컴파일러는 매우 최적화되어 있으며 해당 컴파일러가 무엇을 하고 있는지 알고 있습니다.분해 코드를 덤프하여 네이티브어셈블리와 비교할 수 있습니다.컴파일러가 사용하고 있는 훌륭한 트릭을 보실 수 있을 거라고 생각합니다.

예를 들어, 더 이상 그것이 맞는지 확신할 수 없는 경우:)

실행:

mov eax,0

보다 많은 사이클이 들다

xor eax,eax

같은 일을 하는 거죠

컴파일러는 이 모든 속임수를 알고 사용합니다.

파일컴★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★♪ 「 TIMES가 보다 테스트를 합니다.y ★★★★★★★★★★★★★★★★★」x되어 , 16열로 되어 .length는 4.1의0이 4의 입니다.★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

  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이 히트해도 메모리 스루풋이 병목현상이 나타나기 때문에 훨씬 더 빨리 처리할 수 있다면 놀랄 것입니다.

그게 바로 그 의미야.마이크로 최적화는 컴파일러에 맡깁니다.

저는 이 예가 저수준 코드에 대한 중요한 교훈을 보여주기 때문에 매우 좋아합니다., C 코드만큼 빠른 조립체를 쓸 수 있습니다.이것은 동시적으로는 맞지만, 반드시 아무것도 의미하지는 않습니다.확실히 누군가는 할 수 있습니다.그렇지 않으면 조립자는 적절한 최적화를 알 수 없습니다.

마찬가지로 언어 추상화 단계를 올라갈 때도 같은 원리가 적용됩니다., 파서는 C로 빠르고 더티한 perl 스크립트만큼 빠르게 작성할 수 있습니다.많은 사람들이 쓰고 있습니다.그렇다고 해서 C를 사용했기 때문에 코드가 빠르다는 것은 아닙니다.대부분의 경우, 상위 레벨의 언어는 전혀 고려하지 않은 최적화를 수행합니다.

대부분의 경우 일부 작업을 수행하는 최적의 방법은 작업이 수행되는 컨텍스트에 따라 달라질 수 있습니다.루틴이 어셈블리 언어로 작성되어 있는 경우, 일반적으로 컨텍스트에 따라 명령 시퀀스를 변경할 수 없습니다.간단한 예로서 다음과 같은 간단한 방법을 생각해 보겠습니다.

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바이트의 코드 공간이 필요합니다.후자는 "로드"를 "추가"로 대체하므로 ARM7-TDMI에서는 2사이클이 더 빨리 실행됩니다.만약 코드가 r0이 알 수 없거나 상관하지 않는 컨텍스트에서 실행된다면 어셈블리 언어 버전은 컴파일된 버전보다 다소 더 나을 것입니다.한편, 컴파일러는 일부 레지스터[예: r5]가 원하는 주소 0x40001204[예: 0x40001000]의 2047바이트 이내의 값을 보유하는 것을 알고 있으며, 다른 레지스터[예: r7]가 낮은 비트의 값을 보유하는 것을 알고 있다고 가정합니다.이 경우 컴파일러는 C버전의 코드를 최적화하여 다음과 같이 할 수 있습니다.

strb r7,[r5+0x204]

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

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

임베디드 시스템을 코딩할 때는 전혀 믿을 수 없습니다. ifset_port_high이동해야 은 r0의 합니다).function1 코드를 에서 그 어셈블리 코드'를 호출하기 때문에function2첫 번째 파라미터는 r0)로 상정되므로 "최적화된" 어셈블리 코드는 5가지 명령이 필요합니다.컴파일러가 주소나 저장할 값을 가지고 있는 레지스터를 알지 못한다고 해도, 4개의 명령 버전(r0과 r1이 아닌 사용 가능한 레지스터를 사용하도록 적응할 수 있음)은 "최적화된" 어셈블리 언어 버전을 능가할 것이다.앞서 한 바와 같이 및 있는 , "R5" r7"은 "r5"function1 레지스터를 에, 이 를 대체할 수 .set_port_highstrb명령--"수작업으로 최적화된" 어셈블리 코드보다 작고 빠른 명령 4개요

있는 , 성능을 만, 그 인식되기 되어 있는 , 콘텍스트로부터 될 가능성이 해 주세요.조각이 작성되거나 여러 콘텍스트에서 소스 코드 조각이 호출될 수 있는 경우, 수작업으로 최적화된 어셈블리 코드는 컴파일러를 능가할 수 있습니다.set_port_high코드 내의 50개의 다른 장소에서 사용되고 있기 때문에 컴파일러는 각각 독자적인 방법으로 확장 방법을 결정할 수 있습니다.

일반적으로 어셈블리 언어는 매우 제한된 수의 컨텍스트에서 각 코드 조각에 접근할 수 있는 경우 가장 큰 성능 향상을 가져오고 여러 다른 컨텍스트에서 코드 조각에 접근할 수 있는 경우 성능에 악영향을 미칠 수 있습니다.흥미롭게도(그리고 편리하게도) 어셈블리가 퍼포먼스에 가장 도움이 되는 케이스는 대부분 코드가 가장 간단하고 읽기 쉬운 케이스입니다.어셈블리 언어 코드가 끈적끈적한 혼란으로 바뀌는 장소는 어셈블리에서 쓰는 것이 가장 작은 성능상의 이점을 제공하는 장소인 경우가 많습니다.

[단점: 어셈블리 코드를 사용하여 매우 최적화된 끈적끈적한 혼란이 발생하는 곳도 있습니다.예를 들어, 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비트주소 테이블을 사용하면 1사이클이 빨라지지만 매우 귀중한 RAM 1KB를 소비하게 됩니다.[플래시는 여러 개의 대기 상태를 추가했을 것입니다]64개의 32비트주소를 사용하면, 취득한 워드에서 몇개의 비트를 마스크 하는 명령을 추가할 필요가 있어, 실제로 사용한 테이블보다 192바이트를 더 소비하게 됩니다.8비트 오프셋 테이블을 사용하면 매우 콤팩트하고 빠른 코드가 생성되지만 컴파일러가 생각해 낼 수 있는 것은 아닙니다.또한 컴파일러가 레지스터를 테이블주소를 유지하는 데 "풀타임"으로 할 것이라고는 생각하지 않습니다.

상기의 코드는, 자급식 시스템으로 동작하도록 설계되어 있습니다.C 코드를 정기적으로 호출할 수 있습니다만, 통신하고 있는 하드웨어를 16 밀리초 간격으로 2 회 정도 안전하게 「유휴」상태로 할 수 있는 것은 특정의 경우 뿐입니다.

최근에 제가 한 모든 속도 최적화 작업은 뇌 손상 저속 코드를 합리적인 코드로 대체한 것입니다.하지만 속도가 매우 중요했기 때문에 저는 무언가를 빠르게 만드는 데 많은 노력을 기울였습니다. 결과는 항상 반복적인 프로세스였습니다. 반복할 때마다 문제를 더 잘 파악하고 더 적은 운영으로 문제를 해결할 수 있는 방법을 찾을 수 있었습니다.마지막 속도는 항상 내가 문제를 얼마나 통찰하느냐에 달려 있었다.어느 단계에서 어셈블리 코드 또는 지나치게 최적화된 C 코드를 사용하더라도 더 나은 솔루션을 찾는 프로세스가 어려워지고 최종 결과가 더 느려질 수 있습니다.

컴파일러가 많은 OO 지원 코드를 생성한다면 조립이 더 빠를 수 있습니다.

편집:

투표자 여러분께: OP는 "내가..."라고 썼다.C++에 집중해서 어셈블리 언어를 잊어버리나요?"라고 대답합니다.특히 메서드를 사용할 때는 OO가 생성하는 코드를 항상 주시해야 합니다.어셈블리 언어를 잊지 않는 것은 OO코드가 생성하는 어셈블리를 정기적으로 검토한다는 것을 의미합니다.이 코드는 고성능 소프트웨어를 작성하는데 필수적이라고 생각합니다.

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

언급URL : https://stackoverflow.com/questions/9601427/is-inline-assembly-language-slower-than-native-c-code

반응형