Nucleus 의 성능을 측정하기 위해서 이리저리 삽질을 하던 중에 새롭게 알게된 것들을 정리한다.
연구실에서 구입한 바이너리(?) 파일은 총 3 개(net, file, plus) 이다. 이 중에서 성능 측정과 가장 관계가 깊다고 생각되는 것은 plus 이다.
각각의 바이너리에서 필요한 API 들은 doc 디렉토리의 pdf 파일 형태로 제공된다. 다시 한번 말하지만, 바이너리 형태로 구입을 했기 때문에 단지 헤더만 정의되어 있다고 보면 된다.
Uart 로 정수 출력하기
어찌보면, 너무나 당연한 것일 수도 있는 데, Nucleus 에서는 정수(int, long 등)의 Uart 출력 함수가 없다. 단지 char 나 char*(= string) 변수들을 출력해주는 API 만 제공할 뿐이다.
일반적으로 사용하는 printf 를 보자. printf 함수를 사용해서 'hello world' 라는 출력하게끔 했다고 하자. 과연 어떤 결과가 발생할까?
X86 아키텍처가 아닌 임베디드 환경에서의 결과는 그때 그때 다르다는 것이다. X86 의 경우, 굳이 uart 가 아니라도 모니터를 통해서 출력을 확인할 수 있지만, 임베디드 타겟에서는 어떻게 출력을 확인할 수 있을까?
그 중에 가장 간단한 것이 시리얼 포트(= uart) 를 이용한 방법이다. 앞에서 그때 그때 다르다고 했던 이유는 라이브러리에서 uart 로 출력하는 API 를 제공한다면, 개발자는 큰 고민없이 일반적인 printf 함수를 사용해서 uart 로 결과값을 확인할 수 있다.
Nucleus 의 경우에는 그렇지가 못하다. 현재 제공하는 함수들이 문자열만을 출력해주기 때문에, 약간의 편법(?)을 사용해야 한다.
정수를 문자열로 바꾸는 함수를 사용해야 한다.
다음은 필자가 실제로 정수를 출력하기 위해 사용한 예제 루틴이다.
... UNSIGNED clock_value; // 정수를 저장할 변수 CHAR buffer1[12]; // 정수를 저장할 문자열 배열 clock_value = 34224; // 정수 34224 를 저장 DEMO_Ultoa(clock_value, buffer1, 10); // 정수를 buffer1 의 문자열 배열에 저장 NU_SD_Put_String(buffer1,&UART_Port); // buffer1 을 Uart 로 출력 ...
실제로 실행해보면, 34424 가 출력되는 것을 볼 수 있다.
이번에는 DEMO_Ultoa() 함수를 살펴보도록 한다.
/************************************************************************* * * FUNCTION * * DEMO_Ultoa * * DESCRIPTION * * Binary-to-ascii conversion for an unsigned long. * * Returns a pointer to a string built somewhere * in "buf". The convup parameter should be non-zero * if upper case letters are wanted for the conversion. * * This routine will handle bases from 8 to 16. * * INPUTS * * value The integer to convert to ASCII * *string Pointer to were the string should be built * radix Base to be used for conversion * * OUTPUTS * * A pointer to the converted long * *************************************************************************/ CHAR *DEMO_Ultoa(UINT32 value, CHAR *string, INT radix) { UINT32 i, d; INT flag = 0; CHAR *ptr = string; /* This implementation only works for decimal numbers. */ if (radix != 10) { *ptr = 0; return string; } if (!value) { *ptr++ = 0x30; *ptr = 0; return string; } for (i = 1000000000; i > 0; i /= 10) { d = value / i; if (d || flag) { *ptr++ = (CHAR)(d + 0x30); value -= (d * i); flag = 1; } } /* Null terminate the string. */ *ptr = 0; return string; }
하드웨어 타이머를 이용한 성능측정
nucleus 에서 api 형태로 제공하는 소프트웨어 타이머의 경우, 정확한 시간 측정이 어렵다고 판단해서 하드웨어 타이머를 사용하기로 했다.
사용 방법은 기존의 vpos 성능 측정 방법과 거의 흡사하기 때문에, 큰 어려움은 없다. 하지만, 소스코드가 없이 단지 바이너리 파일 상에서 측정이라 많은 제약이 따른다.
쓰레드를 수행하는 함수의 처음과 끝의 시간을 잡아서 차이를 구하는 방식으로 측정했다.
nucleus 의 경우, 쓰레드를 만들기 전에 메모리를 할당하는 부분과 쓰레드를 만드는 부분이 나뉘어져 있다.
여기서는 두 부분을 합친 결과를 구하기로 한다.
void timer_test(void) { STATUS status; VOID *pointer; /* Check to see if previous operation successful */ start_test = _RD(0x51000040); /* Allocate memory for task 1 */ status = NU_Allocate_Memory(&System_Memory, &pointer, 2000, NU_NO_SUSPEND); /* Check to see if previous operation successful */ //start_test = _RD(0x51000040); /* Create task 1. */ status = NU_Create_Task(&Task_1, "TASK 1", task_1, 0, NU_NULL, pointer, 2000, 10, 5, NU_PREEMPT, NU_START); end_test = _RD(0x51000040); timer_result(); }
성능 테스트 및 결과
테스트 항목
테스트할 항목은 다음과 같다.
1 | Thread creation overhead | 쓰레드가 생성되는 시간 측정(pthread_creation 함수를 호출한 직후부터 READY 큐에 삽입되는 시점까지의 소요시간) |
2 | Thread deletion overhead | 쓰레드가 제거되는 시간 측정(pthread_exit 함수를 호출한 직후부터 할당된 메모리가 해제되는 시점까지의 소요시간) |
3 | Thread switching overhead | 쓰레드의 컨텍스트를 저장하고 복구하는 데 소요되는 시간 측정 |
4 | Scheduling overhead | READY 큐에서 CPU 를 할당할 쓰레드를 선택하는데 소요되는 시간 측정 |
5 | Interrupt latency | 인터럽트 발생시점부터 인터럽트 핸들러 진입시점까지의 소요되는 시간 측정 |
6 | Clock interrupt service routine overhead | 타이머 인터럽트 핸들러만의 순수한 overhead 측정 |
테스트 결과
테스트 결과는 다음과 같다.
쓰레드의 메모리를 할당하고, 생성하는데, EDGE 툴을 이용해서 찍어본 결과, 45.8 us 가 나왔다.
Thread creation overhead
최소(us) | 최대(us) | 평균(us) | 표준편차 |
44.037 | 55.344 | 48.457 | 1.516 |
Thread deletion overhead
최소(us) | 최대(us) | 평균(us) | 표준편차 |
26.184 | 28.564 | 27.204 | 0.355 |
Thread switching overhead + Scheduling overhead
소스코드가 없는 관계로 컨텍스트 스위치 시간과 READY 큐에서 CPU 를 할당할 쓰레드를 선택하는데 소요되는 시간을 합쳐서 테스트 했다.
최소(us) | 최대(us) | 평균(us) | 표준편차 |
4.76 | 5.951 | 5.1 | 0.316 |
Interrupt latency
타이머 인터럽트를 이용해서 특정 시간 후에 인터럽트 핸들러가 수행되기 까지의 시간을 측정해야 한다. 하지만, 바이너리의 제한 때문에 여기서는 측정이 불가능하며, 측정한다고 해도 값의 의미가 없다.
벤치마크 테스트
filesystem 과 network 관련한 간단한 테스트를 수행한다.
application(MPEG 또는 이미지 뷰어) 을 실행하여 성능을 측정한다.