기술자료

[TIP] 배열 초기화 작업 어떻게 하고 계신가요? (loop unroll / memset 함수 활용)

싱크웍스 2019.02.27 15:37:44 조회수 4,005
C언어 환경에서 이런저런 프로그램들을 작성하다 보면, 각종 데이터들을 누적 하기 위해 배열을 사용하곤 합니다. 일반적으로 C코드 상단이나 헤더에 아래와 같이 선언하고 main( ) 함수 안에서 0 또는 적절한 값으로 초기화를 진행하게 되는데요.

unsigned int  Signal[1024];

main( )
{
unsigned int  i;

...(생략)...

for(i = 0 ; i < 1024 ; i++)
{
Signal[i] = 0;
}

...(생략)...

}

저 초기화 과정에 CPU Clock Cycle 이 얼마나 소비될까요? TMS320F28X 칩과 Code Composer Studio(CCS)를 이용해서 위와 같은 코드를 처리하는데 필요한 CPU Clock Cycle 을 측정해봤습니다.


어마 어마 합니다. 1024개 배열을 0 으로 채우는데 CPU Clock이 17416개나 사용되었습니다. 칩 초기화 과정에서 1회만 처리되어야 하는 코드라면 그래도 괜찮을 수 있지만, 알고리즘 수행 중 빈번히 이런 버퍼(배열) 초기화 과정이 반복되어야 한다면 큰 문제가 아닐 수 없습니다. 왜 이런 결과가 관찰될까요?

바로 칩 코어의 파이프라인 구조 때문입니다. 마이크로컨트롤러(MCU)에서 어떤 명령을 처리하고자 하면 Fetch, Decode, Read, Execution의 총 4가지의 과정을 거치게 되는데요. 칩에 따라 각 과정에 1 CPU Cycle이 필요하기도 하고, 2 CPU Cycle이 필요하기도 합니다. 중요한 것은 CPU Clock Cycle 1개로 명령어 처리가 이뤄지지 않는다는 점 입니다. 예를 들어 F, D, R, E의 각 과정에 2 CPU Cycle이 필요하다고 하면 그 CPU는 명령어 1개 당 총 8개의 Clock Cycle을 사용한다고 볼 수 있는데요. TI의 TMS320F28X 칩이 바로 그렇습니다. 이 경우에 만약 칩의 코어(CPU)가 80MHz의 클럭으로 동작한다고 하면, 명령어 처리 속도는 어떻게 될까요?

계산해보면 10MIPS(Million Instructions Per Second)가 됩니다. 80MHz 나 되는 클럭을 공급해줬지만 10MIPS 밖에 안되는 것인데요. 그렇다 보니 최근의 MCU들은 모두 파이프라인이라는 구조를 가지고 있습니다. 다수로(Pipeline Depth : 4 or 8 or ... / TMS320F28X의 Pipeline Depth = 8) 구성된 파이프라인 구조를 통해 현재 처리되고 있는 명령어 다음 명령어들을 미리 가져와서 처리하는 방식인데요. 이를 통해 MCU들이 80MIPS@80MHz, 150MIPS@150MHz 등의 성능을 제공하게 됩니다.

그런데 80MIPS@80MHz가 가능하려면 이 파이프라인이 깨지지 않아야 한다는 전제가 붙습니다. 즉, 코드가 분기 하지 않고 물 흐르듯 선형적(Linear Code)으로 구성되어 있어야 합니다. 하지만, for( ), while( ) 과 같은 루프 구문들은 반드시 탈출 조건 비교에 의한 분기 구문을 포함하고 있습니다. 위의 코드 예라면 for(i = 0 ; i < 1024 ; i++) 에 해당되므로 루프가 도는 동안 1023번 파이프라인이 깨지게 되는데요. 이렇게 미리 채워둔 파이프라인이 깨지게 되면 다시 빈 파이프라인을 채우는데 CPU Clock Cycle이 소비되고 예상보다 많은 CPU Clock Cycle을 소비하게 되는 것 입니다. 즉, 80MIPS@80MHz가 성립되지 못하는 조건이 되는 것이지요.

그러면 어떻게 해야 고속으로 위와 같은 루프 구문을 처리할 수 있을까요?

Loop unroll
코드 최적화 기법들 중 Loop unroll 이라는 테크닉이 있습니다. 돌돌 감긴 루프를 풀어낸다는 뜻인데요. 말 그대로 루프를 다 풀어서 코드를 구성하는 방법 입니다. 위의 for( ) 루프 코드를 풀어낸다면 아래와 같이 총 1024 라인의 코드로 대치되게 됩니다.

Signal[0] = 0;
Signal[1] = 0;
Signal[2] = 0;
Signal[3] = 0;

...(생략)...

Signal[1021] = 0;
Signal[1022] = 0;
Signal[1023] = 0;

노가다의 향기가 물씬 풍기는데요. 전부 Ctrl + C / Ctrl + V 해서 수정하려면 엄두가 나지 않는 작업이지만 잔머리를 조금 돌리면 C에서 제공하는 fprintf( ) 함수 등을 이용해서 MCU에서 하던 PC에서 하던 위에 나열된 코드를 자동으로 생성할 수 있습니다. (^^;)

그럼 위와 같이 루프를 풀어버리면 CPU가 저 초기화 루틴을 처리하는데 소비하는 CPU Clock Cycle은 어떻게 변할까요?


위의 INIT_BUFF( ) 함수는 헤더 파일에 아래와 같이 구성한 Macro 함수로, 코드를 해당 라인에 직접 작성하는 것과 같습니다.


결과를 보면 총 1041 개의 CPU Clock Cycle을 소비해서 1024개의 16-bits unsigned int 형 배열을 0 으로 초기화 했습니다. 이제 좀 납득이 가는 수치가 나왔네요. 맨 처음 for( ) 루프로 구성된 초기화 코드에 비해 16배 이상 빨라졌습니다. 그럼 이 Loop unroll 테크닉의 단점은 무엇일까요?

바로 Output File의 크기가 커진다. 즉, 코드 사이즈가 커진다 입니다. 원래 for( ) 루프 구문 너댓줄 밖에 없었는데 이게 1024줄의 코드로 대치된 셈이라 Build 했을 때 코드 사이즈가 꽤 커지고 칩의 메모리 공간을 많이 필요로 하게 됩니다. 최근에는 MCU들의 내부 메모리 용량이 과거에 비해 많이 커졌지만 그래도 싼 칩을 골라 사용하다 보면 한 톨의 메모리도 소중한 경우가 많습니다. 메모리도 남아돌지 않고 루프 수행 시간은 줄여야 할 때 어떤 방법이 있을까요?

memset 함수의 활용
표준 C언어에는 memset(시작주소, 초기화 할 값, 초기화 할 메모리 길이) 이라는 함수가 포함되어 있습니다. 이 함수를 이용하면 연속된 메모리 공간을 원하는 값으로 일괄 초기화 할 수 있습니다. 그럼 위의 for( ) 루프 초기화 구문을 memset( ) 함수를 이용한 코드로 대체해보겠습니다. memset 함수를 이용하시려면 코드의 상단에 strings.h 헤더 파일이 포함되어 있어야 합니다.


총 12309 개의 CPU Clock Cycle이 소비되었습니다. 이야기 전개 상 여기서 이런 수치가 나오면 안되는 것인데요. 무엇인가 이상합니다. 우측에 Assembler 로 컴파일 된 코드를 보면  LCR    memset   이라는 코드가 보이는데요. 이 명령어는 memset 이라는 별도의 루틴을 호출하는 명령어 입니다. string.h 파일에 단서가 있을 것 같은데요. 한번 살펴보겠습니다.


사실상 그냥 while( ) 루프로 구성하는 것과 코드가 같네요. (--;)

그럼 여기서 이렇게 해보면 어떨까요? memset 함수를 이용하되 1024개의 연속된 메모리 공간을 256개 단위로 나누어 초기화를 하는 것 입니다.


1033 Cycles !! 이제 소비되는 CPU Clock Cycle 수치가 좀 기대에 맞게 변경되었네요. 우측에 Assembler로 변환된 코드를 보면 앞의 경우처럼    LCR    memset 루틴을 호출하지 않고 CPU가 지원하는 RPT(Repeat) 명령이 사용되었습니다. 코드 사이즈도 별로 크지 않으면서 수행 시간은 대폭 줄어들었습니다. 아주 만족스러운 결과네요. 그럼 앞에서 memset 함수 1개로 1024개 배열 전부를 초기화 했을 때에는 컴파일러가 왜 지금과 다른 코드로 변환을 했을까요?

TMS320F28X 칩 CPU가 지원하는 명령어들 중 RPT #n 의 상세 설명을 살펴보겠습니다.


Repeat 명령(RPT) 다음의 반복 횟수를 나타내는 전달 인자가 8-bits 로 정의되어 있습니다. 내부 Repeat Counter가 8-bits로 구성되어 있는 것 같습니다. 그래서 memset 함수의 전달 인자들 중 초기화 할 메모리 공간의 길이가 256개를 넘어가면 컴파일러가 아예 다른 코드로 치환을 하는 듯 보입니다.

정리
TMS320F28x 칩은 DSP 코어를 통한 빠른 수치 처리 능력과 강력한 주변회로들(ADC, PWM 등)이 조합되어 모터제어 및 전력변환과 같은 분야의 실시간 제어(Real-time Control)라는 것에 특화된 칩 입니다. 즉, 최우선이 사용자가 던져준 알고리즘 코드들을 빠르게 처리하는 것 입니다. 펌웨어를 설계하심에 있어서 배열과 같이 연속된 메모리 공간을 특정 값으로 빈번하게 초기화 해야 한다면, 특정 코드를 계속 반복해서 수행해야 한다면 단순한 루프 대신에 위에 소개한 Loop unroll 테크닉이나 memset 함수를 활용해보세요. 굉장히 빠르고 깔끔한 펌웨어를 구성하실 수 있으실 것 입니다. (단, memset 함수를 이용한 방법은 배열의 내용을 '0' 으로 초기화하는 것에만 유효합니다.)

총 3일 과정으로 구성된 싱크웍스의 TMS320F2837x 펌웨어 설계 강좌는 TMS320F28X 칩의 CPU 구조를 시작으로 TMU, VCU와 같은 강력한 보조연산 유닛들, Memory Mapped Register(MMR), 칩-초기화, 메모리 관리 및 배치(Linker), 범용 입출력 포트, 주변회로 인터럽트, 리셋 및 부트, 플래시 메모리 등 펌웨어 설계를 위해 필수적인 내용들을 다루고 있으며, 이론과 연계된 실습을 통해 보다 확실히 TMS320F28X 칩 펌웨어 설계에 대한 내용을 체득하실 수 있습니다.

총 댓글 0
로그인을 하셔야 댓글을 등록하실 수 있습니다.
번호 제목 작성자 날짜
번호 제목 날짜 작성자 조회
22 [E2E 스레드] F28x의 16비트 주소 지정과 엔디안(Endian) 정리 이미지첨부 있음 2026.09.16 인터럽트 27
21 [TIP] 배열 초기화 작업 어떻게 하고 계신가요? (loop unroll / memset 함수 활용) 이미지첨부 있음 인기글 2019.02.27 싱크웍스 4005
20 [CCS TIP] CCS 실행속도를 빠르게 ... 이미지첨부 있음 인기글 2019.01.31 싱크웍스 5930
19 [CCS TIP] CCS 실시간 디버깅 시, 화면 갱신율 조정 방법 이미지첨부 있음 인기글 2018.10.11 싱크웍스 2514
18 [CCS TIP] CCS에서 라이브러리 파일(*.lib)을 만들기 - 3편 이미지첨부 있음 인기글 2016.10.21 싱크웍스 4354
17 [CCS TIP] CCS에서 라이브러리 파일(*.lib)을 만들기 - 2편 이미지첨부 있음 인기글 2016.10.19 싱크웍스 2016
16 [CCS TIP] CCS에서 라이브러리 파일(*.lib)을 만들기 - 1편 이미지첨부 있음 인기글 2016.10.18 싱크웍스 1946
15 [SPRAAD8A] 6. TMS320280x and TMS3202801x ADC 교정(Calibration) 이미지첨부 있음 인기글 2010.09.16 이지영 1058
14 [SPRAAD8A] 5. TMS320280x and TMS3202801x ADC 교정(Calibration) 이미지첨부 있음 인기글 2010.09.15 이지영 626
13 [SPRAAD8A] 4. TMS320280x and TMS3202801x ADC 교정(Calibration) 이미지첨부 있음 인기글 2010.09.13 이지영 647
12 [SPRAAD8A] 3. TMS320280x and TMS3202801x ADC 교정(Calibration) 이미지첨부 있음 인기글 2010.09.10 이지영 722
11 [SPRAAD8A] 2. TMS320280x and TMS3202801x ADC 교정(Calibration) 이미지첨부 있음 인기글 2010.09.09 이지영 989
10 [SPRAAD8A] 1. TMS320280x and TMS3202801x ADC 교정(Calibration) 이미지첨부 있음 인기글 2010.09.08 이지영 1927
9 [SPRAAU8] 5. TMS320C28xxx DSC에서 컴파일러 섹션을 Flash로부터 RAM으로 복사하는 방법 인기글 2010.03.30 싱크웍스 1374
8 [SPRAAU8] 4. TMS320C28xxx DSC에서 컴파일러 섹션을 Flash로부터 RAM으로 복사하는 방법 인기글 2010.03.30 싱크웍스 1157
7 [SPRAAU8] 3. TMS320C28xxx DSC에서 컴파일러 섹션을 Flash로부터 RAM으로 복사하는 방법 이미지첨부 있음 인기글 2010.03.30 싱크웍스 1450
6 [SPRAAU8] 2. TMS320C28xxx DSC에서 컴파일러 섹션을 Flash로부터 RAM으로 복사하는 방법 인기글 2010.03.30 싱크웍스 1732
5 [SPRAAU8] 1. TMS320C28xxx DSC에서 컴파일러 섹션을 Flash로부터 RAM으로 복사하는 방법 인기글 2010.03.30 싱크웍스 2667
4 [SPRAAH1] 4. TMS320F280x, 28xxx의 eQEP 모듈을 Capture로 활용하기 인기글 2010.03.25 싱크웍스 641
3 [SPRAAH1] 3. TMS320F280x, 28xxx의 eQEP 모듈을 Capture로 활용하기 이미지첨부 있음 인기글 2010.03.25 싱크웍스 562
2 [SPRAAH1] 2. TMS320F280x, 28xxx의 eQEP 모듈을 Capture로 활용하기 이미지첨부 있음 인기글 2010.03.25 싱크웍스 821
1 [SPRAAH1] 1. TMS320F280x, 28xxx의 eQEP 모듈을 Capture로 활용하기 이미지첨부 있음 인기글 2010.03.25 싱크웍스 1552

비밀번호 인증

비밀번호를 입력해 주세요.

닫기
 
 

비밀번호 인증

글 작성시 설정한 비밀번호를 입력해 주세요.

닫기