아주 오래전에 적어두었던 글을 블로그 정리중 발견하여 옮겨적습니다.(2008년 8월 27일 글)
그래픽 처리하는 루틴이나 라이브러리의 경우 C언어나 CPP로 아무리 컴팩트하게 프로그래밍을 했다고 해도, 컴파일러가 사람만큼 스마트 하지 못하기 때문에 비효율적이게 되는 부분이 발생하게 된다.
컴파일러의 첫 목적은 사용자가 의도한 대로 안전하게 어셈블링 해주는 것이기 때문에 이런 비효율은 피할 수 없는 것이다.
만약 사용자가 어셈블리에 관한 지식과 해당 H/W에 대한 지식이 조금이라도 있다면, 어셈블리 레벨에서 최적화를 시도해 볼 수 있는데, 그 실제적인 예를들어 설명하겠다.
<C CODE>
typedef _DWORD DWORD
struct _DWORD
{
unsigned short a ;
unsigned short b ;
unsigned short c ;
unsigned short d ;
};typedef _DWORD2 DWORD2
struct _DWORD2
{
unsigned int A;
unsigned int C ;
};void Loop(DWORD2 *dst,DWORD *src,int loopsize)
{
while(loopsize--)
{
int d = 256 - src->d;
dst->A=(DWORD2*)src->A+((dst->A*d)>>8)&0x1F001F00;
dst->C=(DWORD2*)src->C+((dst->C*d)>>8)&0x1F001F00;
dst++;
src++;
}
}
위의 코드를 C언어 레벨에서 분석하였을때, 구조체가 사용이 덜 최적화 되었다는 것을 알수 있다.
즉, (DWORD2*)src->C값은 src->d 값을 포함하고 있기 때문에, C코드상에서도 메모리 Access를 1회 제거하는데 큰 공헌을 할수는 있다.
CPU상에서 연산되는 것보다, 메모리 IO에 의한 성능저하가 상당하기 때문에 위와 같이 메모리 IO를 1회 줄이는것만으로도 성능향상을 확인할 수 있다.
앞으로, 우리가 튜닝을 할때에는 C언어로 무결한 코드를 완전히 컴팩트하게 구성한다음에 어셈블리 언어로 최적화 하면 된다.
위 C코드의 컴파일 결과 : ASM 코드(armcc 사용,LE, arm1136j-S)
Loop
SUBS R3,R2,#1
BXCC R14
PUSH {R14}
LDRH R2,[R1,#6]
LDR R4,[R0,#0]
LDR R12,[R1,#0]
RSB R2,R2,#0X100
SUBS R3,R3,#1
MUL R4,R2,R4
ADD R12,R12,R4,LSR #8
BIC R12,R12,#0x1F000000
BIC R12,R12,#0x1F00
STR R12,[R0,#0]
LDR R4,[R0,#4]
LDR R12,[R1,#4]
ADD R1,R1,#8
MUL R2,R4,R2
ADD R2,R12,R2,LSR #8
BIC R2,R2,#0x1F000000
BIC R2,R2,#0x1F00
STR R2,[R0,#4]!
ADD R0,R0,#4
BCS {PC} -0x4C
POP {R14}
BX R14
위의 ASM 코드를 보면 상당히 많은 부분이 개선가능하다는 것을 발견할 수 있다.
두 말 할 것 업이 바로 최적화 작업에 돌입한다.!!!
개선된 코드
Loop_opt
SUBS R3,R2,#1
BXCC R14
PUSH {R4-R9} ; 레지스터를 더 많이 쓰기 위해, 스택에 저장
MOV R8,#0x1F00
ORR R8,R8,R8, LSL #16
MOV R9,#0xFF
ORR R9,R9,R9 LSL #8
INLOOP
LDM R1!,{R6,R7} ; 한번에 두개씩 읽어오자!!
LDM R0, {R4,R5} ; 역시 한번에 두개 씩 읽어올것!!
AND R2,R9,R7, ASR #16 ;위에서 말했던, 메모리 Access 1회 줄이는 방법.!!!!
RSB R2,R2,#0x100
MUL R4,R2,R4 ; STALL제거를 위해 위로 올라옴
MUL R5,R2,R5 ; STALL제거를 위해 위로 올라옴
SUBS R3,R3,#1
ADD R6,R6,R4, LSR #8 ;이놈은 줄이고 싶지만... 더 연구해봐야 됨.
BIC R6,R6,R8 ; 이런거 말고 방법이 있을거야~~
ADD R7,R7,R5, LSR #8 ;이놈도...
BIC R7,R7,R8 ; 이런거 말고 방법이 있을거야~~
STM R0!, {R6,R7} ; 두개 저장.
BCS INLOOP
POP {R4-R9}
BX R14
적용 테스트 결과 :루틴 자체의 성능이 약 3배 개선되었다. (시스템 점유율 12 % --> 4%로 개선)
최적화 방법 :
1. 메모리 접근 횟수를 줄임
2. LOOP내 인스트럭션의 수를 줄임
3. STALL 발생 횟수를 없앰.
4. BIT 연산을 한번에 가능하도록 함.
그러나, "나는 더 개선하고 싶다." 하는 사람은 아직 한가지 더 방법이 있다.
바로 Cache의 최대한 활용하는 방법을 연구하는 것과 SIMD(Single Instruction Multiple Data)를 사용하는 것이다. ARM의 경우 Cache Line 사이즈가 32byte이므로 한 라인에 딱 맞는 Loop Code를 작성하면 캐시 Hit Ratio가 증가할것이고 이에 따라 성능향상도 기대할 수 있다.
그러기 위해서는 Thumb code는 16개, ARM code는 8개의 instruction으로 코드를 작성해야 한다.
그리고 SIMD를 적용한다면, 아래 코드를 개선할수 있다.
MUL R4,R2,R4 ;
MUL R5,R2,R5 ;
ADD R6,R6,R4, LSR #8 ;
BIC R6,R6,R8 ;
ADD R7,R7,R5, LSR #8 ;
BIC R7,R7,R8 ;
구조체 구조가 32bit SIMD에서는 못써먹을 구조라서, 64bit SIMD가 되면 바로 적용해 볼 수 있을것 같다.
한줄요약 : RISC에서는 생각 보다 최적화는 어렵지 않다.
'코딩하고 > Assembly' 카테고리의 다른 글
ARM 특정 라이브러리에서 오브젝트 추출 및 disassemble 방법 (0) | 2012.08.11 |
---|---|
[ARMv6] PKHBT / PKHTB 사용하기(Packing) (0) | 2012.08.11 |