[performance] 루프 언 롤링이 언제 여전히 유용합니까?

루프 언 롤링을 통해 성능에 매우 중요한 코드 (몬테카를로 시뮬레이션 내에서 수백만 번 호출되는 빠른 정렬 알고리즘)를 최적화하려고했습니다. 속도를 높이려는 내부 루프는 다음과 같습니다.

// Search for elements to swap.
while(myArray[++index1] < pivot) {}
while(pivot < myArray[--index2]) {}

나는 다음과 같이 풀어 보았습니다.

while(true) {
    if(myArray[++index1] < pivot) break;
    if(myArray[++index1] < pivot) break;
    // More unrolling
}


while(true) {
    if(pivot < myArray[--index2]) break;
    if(pivot < myArray[--index2]) break;
    // More unrolling
}

이것은 전혀 차이가 없었기 때문에 더 읽기 쉬운 형식으로 다시 변경했습니다. 나는 루프 언 롤링을 시도한 다른 시간에도 비슷한 경험을했습니다. 현대 하드웨어에서 분기 예측 자의 품질을 고려할 때 루프 언 롤링이 여전히 유용한 최적화일까요?



답변

루프 언 롤링은 종속성 체인을 끊을 수 있다면 의미가 있습니다. 이것은 순서가 맞지 않거나 슈퍼 스칼라 CPU에 일을 더 잘 예약하여 더 빨리 실행할 수있는 가능성을 제공합니다.

간단한 예 :

for (int i=0; i<n; i++)
{
  sum += data[i];
}

여기서 인수의 종속성 체인은 매우 짧습니다. 데이터 어레이에 캐시 미스가있어 지연이 발생하면 CPU는 대기하는 것 외에는 아무것도 할 수 없습니다.

반면에이 코드 :

for (int i=0; i<n; i+=4)
{
  sum1 += data[i+0];
  sum2 += data[i+1];
  sum3 += data[i+2];
  sum4 += data[i+3];
}
sum = sum1 + sum2 + sum3 + sum4;

더 빨리 달릴 수 있습니다. 하나의 계산에서 캐시 미스 또는 기타 지연이 발생하는 경우 지연에 의존하지 않는 다른 종속성 체인이 세 개 있습니다. 고장난 CPU가이를 실행할 수 있습니다.


답변

동일한 수의 비교를 수행하고 있기 때문에 차이가 없습니다. 더 나은 예가 있습니다. 대신에:

for (int i=0; i<200; i++) {
  doStuff();
}

쓰다:

for (int i=0; i<50; i++) {
  doStuff();
  doStuff();
  doStuff();
  doStuff();
}

그럼에도 불구하고 거의 확실하게 중요하지 않지만 이제 200 개 대신 50 개의 비교를 수행하고 있습니다 (비교가 더 복잡하다고 상상해보십시오).

그러나 일반적으로 수동 루프 언 롤링은 대부분 역사의 산물입니다. 그것은 중요한 컴파일러가 당신을 위해 해줄 것입니다. 예를 들어, 대부분의 사람들은 쓰고 귀찮게하지 않습니다 x <<= 1또는 x += x대신 x *= 2. 작성 만하면 x *= 2컴파일러가 최선을 다해 최적화합니다.

기본적으로 컴파일러를 추측 할 필요가 점점 줄어 듭니다.


답변

최신 하드웨어의 분기 예측에 관계없이 대부분의 컴파일러는 어쨌든 루프 언 롤링을 수행합니다.

컴파일러가 얼마나 많은 최적화를 수행하는지 알아내는 것은 가치가 있습니다.

나는 Felix von Leitner의 프레젠테이션 이 주제에 대해 매우 깨달음을 얻었습니다. 읽어 보는 것이 좋습니다. 요약 : 최신 컴파일러는 매우 영리하므로 수동 최적화는 거의 효과적이지 않습니다.


답변

내가 이해하는 한, 현대 컴파일러는 이미 적절한 경우 루프를 풀고 있습니다. 예를 들어 gcc가 최적화 플래그를 전달하면 매뉴얼에서 다음과 같이 말합니다.

반복 횟수를 컴파일 타임 또는 루프에 들어갈 때 결정할 수있는 루프를 언롤합니다.

따라서 실제로 컴파일러가 사소한 경우를 수행 할 가능성이 높습니다. 따라서 컴파일러가 필요한 반복 횟수를 결정하기 위해 가능한 많은 루프를 쉽게 확인하는 것은 사용자의 몫입니다.


답변

수동 언 롤링이든 컴파일러 언 롤링이든 관계없이 루프 언 롤링은 특히 최신 x86 CPU (Core 2, Core i7)에서 역효과를 낼 수 있습니다. 결론 :이 코드를 배포하려는 CPU에서 루프 언 롤링을 사용하거나 사용하지 않고 코드를 벤치마킹하십시오.


답변

모르는 사이에 시도하는 것은 그렇게하는 방법이 아닙니다.
이 정렬이 전체 시간에서 높은 비율을 차지합니까?

모든 루프 언 롤링은 증가 / 감소, 중지 조건 비교 및 ​​점프의 루프 오버 헤드를 줄이는 것입니다. 루프에서 수행하는 작업이 루프 오버 헤드 자체보다 더 많은 명령주기를 필요로한다면, 그다지 개선 된 비율을 보지 못할 것입니다.

다음은 최대 성능을 얻는 방법의 예입니다.


답변

루프 언 롤링은 특정 경우에 유용 할 수 있습니다. 유일한 이득은 일부 테스트를 건너 뛰는 것이 아닙니다!

예를 들어 스칼라 교체, 소프트웨어 프리 페치의 효율적인 삽입을 허용 할 수 있습니다. 공격적으로 풀면 실제로 얼마나 유용 할 수 있는지 놀라게 될 것입니다 (-O3를 사용해도 대부분의 루프에서 10 % 속도 향상을 쉽게 얻을 수 있음).

앞서 말했듯이 루프에 많이 의존하고 컴파일러와 실험이 필요합니다. 규칙을 만드는 것은 어렵습니다 (또는 언 롤링을위한 컴파일러 휴리스틱이 완벽 할 것입니다).