|
좀 전에 이용태님이 직접 메일로 추가 질문을 주셔서 테스트 해 봤습니다.
물론 Release모드로 컴파일 하셔야 정확한 결과를 얻을 수 있으며... 타임 체크에 사용된 TStopWatch는 제가 공개한 PXL소스에 포함된 HighPerformanceCounter기반 타임 채크 클래스입니다.
이유는 잘 모르겠지만 예전에도 그랬고, 지금도 해 보면 어느놈이 먼저 수행되느냐에 따라 먼저 수행되는놈이 성능이 좀 느리게 평가됩니다. 그래서 각 두번씩 반복하게 해 봤습니다. Win32 API의 FillMemory결과도 추가합니다.
// memset.c 에 나온 소스코드를 그대로 옮긴겁니다.
void * __fastcall MyMemSet8bit(void *Data, int c, int n)
{
const unsigned char ui = c;
unsigned char *su = (unsigned char*)Data;
for (; 0 < n; ++su, --n)
*su = ui;
return Data;
}
// 이건 4byte씩 DWORD 단위로 세팅하도록 바꾼 버전입니다. n은 반드시 4의 배수이어야만 제대로 동작합니다.
void * __fastcall MyMemSet32bit(void *Data, int c, int n)
{
const unsigned int ui = ((c << 24) | (c << 16) | (c <<8 ) | c);
n >>= 2;
unsigned int *su = (unsigned int*)Data;
for (; 0 < n; ++su, --n)
*su = ui;
return Data;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TStopWatch sw;
const int arrsize = 4096;
const int loop = 576000;
char arr[arrsize];
int k;
sw.begin();
for(k = 0; k < loop; k++)
MyMemSet8bit(arr, 32, arrsize);
sw.end();
Memo1->Lines->Add("MyMemSet8bit");
Memo1->Lines->Add(sw.msec());
sw.begin();
for(k = 0; k < loop; k++)
MyMemSet32bit(arr, 32, arrsize);
sw.end();
Memo1->Lines->Add("MyMemSet32bit");
Memo1->Lines->Add(sw.msec());
sw.begin();
for(k = 0; k < loop; k++)
memset(arr, 32, arrsize);
sw.end();
Memo1->Lines->Add("memset");
Memo1->Lines->Add(sw.msec());
sw.begin();
for(k = 0; k < loop; k++)
FillMemory(arr, arrsize, 32);
sw.end();
Memo1->Lines->Add("FillMemory");
Memo1->Lines->Add(sw.msec());
////////////////////////////////////////
// Test Once more
////////////////////////////////////////
sw.begin();
for(k = 0; k < loop; k++)
MyMemSet8bit(arr, 32, arrsize);
sw.end();
Memo1->Lines->Add("MyMemSet8bit");
Memo1->Lines->Add(sw.msec());
sw.begin();
for(k = 0; k < loop; k++)
MyMemSet32bit(arr, 32, arrsize);
sw.end();
Memo1->Lines->Add("MyMemSet32bit");
Memo1->Lines->Add(sw.msec());
sw.begin();
for(k = 0; k < loop; k++)
memset(arr, 32, arrsize);
sw.end();
Memo1->Lines->Add("memset");
Memo1->Lines->Add(sw.msec());
sw.begin();
for(k = 0; k < loop; k++)
FillMemory(arr, arrsize, 32);
sw.end();
Memo1->Lines->Add("FillMemory");
Memo1->Lines->Add(sw.msec());
}
//---------------------------------------------------------------------------
결과는... 재밌게도
MyMemSet8bit
1403.566863945
MyMemSet32bit
264.243430380118
memset
262.800020673018
FillMemory
264.952668565418
MyMemSet8bit
1009.21835037693
MyMemSet32bit
259.847410774274
memset
262.006344381758
FillMemory
262.544122227825
4바이트 복사버전과 memset이 앞서거니 뒤서거니 거의 비슷하게 나오고... memset.c를 그대로 옮긴것은 오히려 4배 이상 느리게 나오는군요.
아마도 빌더에 포함된 바이너리는 뭔가 최적화가 이루어져 있거나 FillMemory를 호출하는게 아닌가 싶습니다. 원 소스로는 저렇게 빨리 나올리가 없는데요...
다른 컴파일 옵션은 바꾸지 않은 상태에서 한겁니다. BCB6, BDS2006에서 돌려봤는데 결과는 유사합니다.
저도 예전 테스트 결과로 memset이나 memcpy가 Win32 API에 포함된 버전보다 느리다는 좀 생각은 좀 바꿔야 할지도 모르겠군요.
김상구.패패루 님이 쓰신 글 :
: 이용태님이 쓰신 내용을 자세히 읽어보진 않았습니다만...
:
: 원래가 memset은 생각보다 상당히 느립니다.
: 기본적으로 byte단위로 복사하기 때문이죠. 예를 들어 4096바이트 크기의 배열을 모두 특정 byte값으로 채우는걸 생각해 본다면... 기본적으로 memset은 4096회의 대입연산을 수행합니다.
: 그러나 32bit컴퓨터의 특성을 생각해본다면 4배 더 빠르게 동작하도록 만드는 것이 가능하죠. 예를 들어 32 (0x20)을 채워넣는다고 하면
:
: char arr[4096];
: for (int i = 0; i < 4096; i++)
: arr[i]=0x20;
:
: 이렇게 돌리는 방법이 일반적이지만...
:
: char arr[4096];
: DWORD *p = (DWORD*)arr;
: for (int i=0; i < 1024; i++)
: p[i] = 0x20202020;
:
: 이렇게 채우면... 1024회 루프로 4096개의 배열을 모두 채울 수 있습니다. 32bit컴퓨터에선 8bit를 대입하건 32bit를 대입하건 속도가 동일하죠. 물론 64bit OS에서는 LONGLONG 타입으로 처리하면 8배 빠르게 동작하게 만들 수 있습니다.
:
: 이 예제에서는 특정 값을 반복해서 채우는 것만 적용했지만 memcpy에도 충분히 적용 가능하다는걸 금방 눈치채실겁니다. 아무튼 mem으로 시작하는 기본 함수들은 대부분 곧이곧대로 채워넣는 방식이라서 속도가 느립니다.
:
: Windows API로 있는 CopyMemory나 ZeroMemory등의 함수들은 소스를 볼 수 없으니 확신은 못합니다만... 예전에 제가 직접 구현한 여러가지 방식으로 속도테스트를 했던 결과로 보건데 제가 얘기한 방식으로 되어있는 듯 합니다. mem계열 함수보다 4배정도 빠릅니다.
:
: 다른분들의 조언처럼 다른 알고리즘의 최적화도 좋지만... 당연하게 사용하는 함수들의 성능에 대해서도 조금씩은 의문을 가지고 만들어보시면 좋을겁니다.
:
:
: 이용태 님이 쓰신 글 :
: : 밑에 글에서 제가 설명을 자세히 하지 못한거 같아서 다시 글 올립니다.
: :
: : 자세히 설명을 하면요... 일단
: :
: : 원본 데이터는 다음과 같습니다.
: :
: : 데이터 셋트가 120개 있습니다. 한 셋트는 다시 데이터 4800개로 이루어집니다.
: :
: : 그러므로 총 데이터 포인트 수는 120개셋트 = 120*4800개입니다.
: :
: : a[120] [4800] <-- 원본 데이터
: :
: : 가로 120개..
: : 세로 4800개로 다음과 같이 메모리 구조가 되어 있겠죠..(배열에 들어가 있는 값은 랜덤값으로 가정했습니다...)
: : --->가로 120
: : ___________________ ___________________
: : 0|2|8|9|1|3|4|5|0|1 ---중간생략 --- 1|4|1|2|8|9|6
: : 9|2|7|1|3|5|8|0|1|4 ----중간생략 --- 2|3|7|2|7|9|1
: : | | |
: : 중간생략 중간생략 중간생략
: : | | |
: : 0|1|5|7|2|5|6|7|9|4 ---중간생략 --- 1|5|7|2|7|8|0
: : 8|2|2|2|3|3|7|1|2|4 ----중간생략 --- 1|2|3|2|1|2|4
: : ___________________ ___________________
: : 세로 4800
: :
: :
: : 여기서 120*4800개의 데이터 한개한개를 가로로 100개씩 뻥튀기를 합니다..
: :
: : 그러면 총 데이터 개수는 120*100*4800입니다. 데이터 형은 BYTE 입니다.
: :
: : b[120*100][4800] <--- 가로로 100배 뻥튀기 된 데이터
: :
: : 메모리 구조는 다음과 같겠죠..
: :
: :
: : 가로 120*100개
: : ___________________ ___________________
: : 0|0|0|0|0|0|0|0|0|0|2|2|2|2|2|2|2|2|2|2 ---중간생략 --- 9|9|9|9|9|9|9|9|9|6|6|6|6|6|6|6|6|6|6|
: : 9|9|9|9|9|9|9|9|9|9|7|7|7|7|7|7|7|7|7|7 ----중간생략 --- 9|9|9|9|9|9|9|9|9|1|1|1|1|1|1|1|1|1|1|
: : | | |
: : 중간생략 중간생략 중간생략
: : | | |
: : 0|0|0|0|0|0|0|0|0|0|1|1|1|1|1|1|1|1|1|1| ---중간생략 --- 8|8|8|8|8|8|8|8|8|8|0|0|0|0|0|0|0|0|0|0
: : 8|8|8|8|8|8|8|8|8|8|2|2|2|2|2|2|2|2|2|2| ----중간생략 --- 2|2|2|2|2|2|2|2|2|2|4|4|4|4|4|4|4|4|4|4
: : ___________________ ___________________
: : 세로 4800개
: :
: :
: : 데이터 맵을 위와 같이 만들려고 하려다 보니... for()문을 돌려 원본 데이터(a[])로부터 일일히 해당 가로/인덱스에 해당하는 값을 읽어와 순차적으로 한개씩 값을 대입하는것보단 한꺼번에 범위로 값을 때려박는게 효율적이어서 memset()를 사용했던 것이죠...
: :
: : 이렇게 하여 데이터 맵을 만들어보니... memset()가 120*4800번 수행을 하게 되어 시간이 대략 0.23초 정도 걸리더군요..
: :
: : 데이터를 만든 후, 이미지 맵핑을 하고 각종 다른 부가 작업을 하니 시간이 0.32초 정도 걸립니다. 전체 작업에서 데이터 만드는 부분에서 무려 2/3의 시간을 잡아 먹게 되는거죠...
: : 그래서 어떻게 해서든지 memset()보다 더 빠른 함수를 사용하던지 아니면 memset()를 적게 사용하는것이 답인거 같아서 여러가지 생각을 해봤습니다.
: :
: : memset()보다 빠른 함수를 찾지 못해서... memset()를 적게 사용할 수 있는 방법을 강구해봤습니다.
: :
: : 위의 데이터 그림에서와 같이.. 한셋트 데이터가 세로로 4800개씩.. 가로로 120개씩 늘어져 있는 모습입니다.
: :
: : 지금 방법은 각 데이터 포인트에 대해 memset()를 적용하여 뻥튀기를 했기 때문에 4800*120번 memset()을 사용하게 됩니다.
: : 그런데 이것을 만약에 세로로 데이터 한셋트를 통채로 복사하여 가로로 복사를 하게 된다면 memset()를 120번만 수행하면 되죠...
: :
: : 그래서 어떻게 할 수 있을까 생각하면서 테스트를 해봤으나 도저히 방법이 떠오르지 않습니다.
: :
: : 배열에서 인접한 가로의 값을 긁어와 memcpy()로 복사 할 수 는 있지만.. 세로로 인접한 값은 한꺼번에 긁어오지 못하므로 memcpy()를 쓰지 못하더군요...
: :
: : 어떻게 하면 될까요?
: :
: : 0.23초는 너무 많이 걸립니다.. 0.1초 이하로 데이터 맵을 만들 수 있게끔 할려고 하는데요... 어떻게 하면 될까요??
|