To Be Developer

[Code Complete] 코드 최적화 기법 본문

CodeComplete

[Code Complete] 코드 최적화 기법

Jeff Hwang 2019. 5. 9. 21:16

코드 최적화 기법

  • 속도를 향상, 코드를 작게

논리구조

  • 답을 알고 있을 때에는 테스트 하지마라

    if 5 < x and x < 10: ~~~ # 중복된 논리식이면 굳이 다시 검증할 필요가 없다
  • 빈도에 따른 테스트 정렬

    // 가장 빈번히 나오는 경우를 고의적으로 먼저 필터링 시켜 검증 수를 줄인다
    switch(num){
     case "+" || "=" :
         /* ~~~ */
         break;
     case "0" To "9":
         /* ~~~ */
         break;
     case ",",".",";":
         /* ~~~ */
         break;
    }
  • ifthenelseswitch~case 문의 실행속도를 비교해서 더빠른 것을 사용하도록 한다

  • 복잡한 표현식을 테이블 참조로 대체

    if (a and not c)  or (a and b and c):
        category = 1
    elif (b and not a) or (a and c and not b):
        category = 2
    categoryTable[2][2][2] = [
        # !a
            [
                [0,3], # !b!c
                [2,2]  # !bc
            ],
           # a
            [
                [1,2], # b!c
                [1,1]  # bc
            ]
        ]
    
    # 복잡한 논리 구조를 대체하기 위해서 테이블 참조를 사용
    category = categoryTable[a][b][c]

루프

  • 루프는 여러 번 수행되기 때문에, 프로그램에서 분쟁 지역(hot spot)은 종종 루프 내에 있다. 루프 자체를 보다 빠르게 만드는 법
  • 언스위칭(Unswitching)
// 스위칭으로 작성한 자바 코드
for (i=0; i < count; i++){
    if (sumType == SUMTYPE_NET){ // 이 코드는 결과가 항상 같음에도 불구하고 루프가 반복될 때마다 반복되서 테스트함
        netSum += amount[i];
    }else{
        grossSum+=aumout[i];
    }
}
// 언스위칭으로 변환한 자바 코드
if (sumType == SUMTYPE_NET){ // 언스위칭으로 해서 프로그램 실행속도를 높일 수 있다
    for(i=0; i<count; i++){
        netSum += amount[i];
    }
}else{
    for(i=0; i<count; i++){
        grossSum+=amount[i];
    }
}
/*
    언스위칭의 단점으로는 루프가 병렬로 함께 유지 보수 되어야 한다는 것이다.
    변경할 점이 발생된다면 모두 변경을 해야된다는 점에서 힘들 수가 있다
    이 경우 로직이 같다면 funtion으로 해결하면 될 듯 하다.
*/
  • 재밍(jamming)(연합) - 동일한 요소의 집합을 다루는 두 개의 루프를 결합한 결과, 두 개의 루프를 하나로 줄임으로써 루프의 오버헤드를 줄이는 이득을 볼 수 있다.
# 재밍 가능한 분리되어 있는 루프
for i in range(0, employeeCount-1):
    employeeName[i] = ""

for i in range(0, employeeCount-1):
    employeeEarnings[i] = 0
# 재밍된 루프
for i in range(0, employeeCount-1):
    employeeName[i] = ""
    employeeEarning[i] = 0
  • 언롤링(unrolling) - 언롤링의 목표는 루프의 보조 수단 코드(HouseKeeping)의 양을 줄이는 것
// 언롤링 될 수 있는 루프를 자바로 작성한 예제
i = 0;
while (i < count){
    a[i] = i;
    i = i + 1;
}
// 한 번 언롤링된 루프를 자바로 작성한 예제
i= 0;
while( i < count - 1){
    a[i] = i;
    a[i+1] = i+1;
    i = i+2;
}
if ( i == count){
    a[count-1] = count-1; // 루프가 2씩 증가할 때 놓칠 수 있는 경우를 처리
}
// 두 번 언롤링된 루프를 자바로 작성한 예제
i= 0;
while( i < count - 2){
    a[i] = i;
    a[i+1] = i+1;
    a[i+2] = i+2;
    i = i+3;
}
if ( i == count - 1){
    a[count-1] = count-1;
}
if ( i == count - 2){
    a[count-2] = count-2;
}

// 파이썬 같은 경우는 성능이 떨어지지만
// 다른 언어인 경우 16~43% 정도 시간을 절약할 수 있음
  • 루프 내부 작업의 최소화
// 루프 내부에 복잡한 포인터 표현식이 있는 C++ 예제
for (i = 0; i<rateCount; i++){
    netRate[i] = baseRate[i] * rates->discounts->factors->net;
}
// 복잡한 포인터 표현식을 단순화한 C++ 예제
quantityDiscount = rates->discounts->factors->net;
for(i=0; i<rateCount; i++){
    netRate[i] = baseRate[i] * quantityDiscount;
}

데이터변환

  • 가능한 가장 적은 차수의 배열을 사용하라
// 2차원 배열을 초기화하는 자바 예제
for (row=0; row < numRows; rot++){
    for(column=0; column<numColumns; column++){
        matrix[row][column] = 0;
    }
}
// 1차원 배열로 표현한 자바 예제
for ( entry=0; entry < numRows * numColumns; entry++){
    matrix[ entry ] = 0;
}
  • 배열에 대한 참조를 최소화 하라
// 루프 내부에서 불필요하게 배열을 참조하고 있는 Java 예제
 for ( discountType = 0; discountType < typeCount; discountType++ ){
     for (discountLevel = 0; discountLevel < levelCount; discountLevel++){
         rate[discountLevel] = rate[discountLevel] * discount[discountType];
     }
 }
// 배열에 대한 참조를 루프의 외부로 이동시킨 Java 예제 
for ( discountType = 0; discountType < typeCount; discountType++ ){
    thisDiscount = discount[discountType];
     for (discountLevel = 0; discountLevel < levelCount; discountLevel++){
         rate[discountLevel] = rate[discountLevel] * thisDiscount;
     }
 }
  • 캐싱(caching)을 사용하라 - 캐싱은 자주 사용되지 않는 값보다 자주 사용되는 값을 보다 쉽게 가져올 수 있는 방법으로 값을 저장하는 것
// 캐싱을 하면 도움을 얻을 수 있는 루틴을 자바로 작성한 예제
double Hypotenuse(double sideA, double sideB){
    return Math.sqrt( (sideA * sideA) + ( sideB * sideB) );
    }
// 시간이 많이 소요되는 계산을 피하기 위해서 캐시를 사용하고 있는 자바 예제
private double cachedHypotenuse = 0;
private double cachedSideA = 0;
private double cachedSideB = 0;

public double Hypotenuse( double sideA, double sideB ){
    // 삼각형이 이미 캐시에 있는지 확인한다.
    if ( (sideA == cachedSideA) && (sideB == cachedSideB)){
        return cachedHypotenuse;
    }

    // 새로운 빗변의 길이를 구하고 캐시한다
    cachedHypotenuse = Math.sqrt( (sideA * sideA) + (sideB * sideB));
    cachedSideA = sideA;
    cachedSideB = sideB;
    return cachedHypotenuse;
}

루틴 - 코드 최적화에서 가장 강력한 도구 중 하나는 훌륭한 루틴의 분해

저급 언어를 이용한 재구성

변경이 많을수록 상태는 그대로