memcpy 보다 std::copy가 더 빠르게 나오는데요

mastercho의 이미지

http://www.gpgstudy.com/forum/viewtopic.php?t=20380&postdays=0&postorder=asc&start=140

여기서 토론을 하다가

vector 의 성능부분을 체크하는 부분까지 왔습니다
최적화시 , 전 vector와 배열의 차이는 없을거라 생각했습니다

Efficient C++ 의 내용을 보나 제가 테스트 한 내용을 보나 , 인터넷 자료를 봐도
동일하거나 오히려 빠른분도 있었기 때문이죠
[ http://kldp.org/node/94903 ]

자세한 토론이 궁금하시면 그 쓰레드를 살펴보셔도 되고요

중요한건
근데 어찌 어찌 하다보니까
vector 구현이 std::copy와 같은 함수로 이루어졌고
[ array와 vector 의 정확한 비교는 아니지만 ]

그에 대항마로 memcpy와 비교로까지 이어졌습니다

#include "stdafx.h"
 
#include <Windows.h>
#include <Mmsystem.h>
 
#include <vector>
#include <algorithm>
 
#include <iostream>
 
using namespace std;
 
const int MAX_LOOP = 1000000;
 
class CCGQueryPerformance 
{
public:
	   void CheckTimeStart()
	   {
	   	   _StartTime = timeGetTime();
	   }
 
	   void CheckTimeEnd()
	   {
	   	   _EndTime = timeGetTime();
	   }
 
	   double GetGapTime()	
	   {
	   	   return double(_EndTime - _StartTime)*0.001;
	   }
 
private:
	   DWORD _StartTime;
	   DWORD _EndTime;
};
 
 
int main()
{
	   typedef int TYPE; 
	   ///////////////////////////////
 
	   /////////////////////////////////
	   TYPE* source	       = new TYPE[MAX_LOOP];
	   TYPE* ArrayDst	     = new TYPE[MAX_LOOP];
 
	   vector<TYPE> VectorDst(MAX_LOOP);
 
	   for (int i = 0; i < MAX_LOOP; i++)
	   	   source[i] = 100;
 
	   /////////////////////////////////
 
 
	   {
	   	   CCGQueryPerformance	perfTest;
	   	   // Start) Timer 시작!
	   	   perfTest.CheckTimeStart();
 
	   	   for (int i = 0; i < 100; i++)
	   	   {
	   	   	   memcpy(ArrayDst,source, sizeof(TYPE)*MAX_LOOP);
	   	   }
 
	   	   // End) Timer 끝!
	   	   perfTest.CheckTimeEnd();
 
	   	   cout << "integer copy " << perfTest.GetGapTime() << endl;
	   }
 
 
	   {
	   	   CCGQueryPerformance	perfTest;
	   	   // Start) Timer 시작!
	   	   perfTest.CheckTimeStart();
 
	   	   for (int i = 0; i < 100; i++)
	   	   {
	   	   	   std::copy(source,source+MAX_LOOP,VectorDst.begin());
	   	   }
 
	   	   // End) Timer 끝!
	   	   perfTest.CheckTimeEnd();
 
	   	   cout << "vector copy : " << perfTest.GetGapTime() << endl;
	   }
}

그래서 한번 비교를 해보았습니다
[위 코드에 delete 코드가 없는건 ^^; 신경쓰지 말아주세요]

놀랍게도 std::copy 코드가 memcpy보다 제 시스템에서는 조금 빨랐습니다 [순서를 바꿔보고 몇십번 반복했습니다 ]
제 사양은 AMD 애슬론 64X2 듀얼 입니다
Core Processor 4000+ 이고요 XP 서비스팩3 입니다

윈도우 2003 , Intel CPU 2기가 셀러론에서는 memcpy가 조금 빨랐습니다

중요한건 어셈블리인데요

Array를 보면

   CCGQueryPerformance	perfTest;
	   	   // Start) Timer 시작!
	   	   perfTest.CheckTimeStart();
004012C2  call        dword ptr [__imp__timeGetTime@0 (4020A8h)] 
004012C8  mov         dword ptr [esp+10h],eax 
004012CC  mov         eax,64h 
 
	   	   for (int i = 0; i < 100; i++)
004012D1  dec         eax  
	   	   {
	   	   	   memcpy(ArrayDst,source, sizeof(TYPE)*MAX_LOOP);
004012D2  mov         ecx,0F4240h 
004012D7  mov         esi,ebx 
004012D9  mov         edi,ebp 
004012DB  rep movs    dword ptr [edi],dword ptr [esi] 
004012DD  jne         main+71h (4012D1h) 
	   	   }
 
	   	   // End) Timer 끝!
	   	   perfTest.CheckTimeEnd();
004012DF  mov         esi,dword ptr [__imp__timeGetTime@0 (4020A8h)] 
004012E5  call        esi  

------------------------------

vector부분을 보면

CCGQueryPerformance	perfTest;
	   	   // Start) Timer 시작!
	   	   perfTest.CheckTimeStart();
00401335  call        esi  
00401337  mov         edi,dword ptr [esp+24h] 
0040133B  mov         dword ptr [esp+18h],eax 
0040133F  lea         edx,[ebx+3D0900h] 
00401345  mov         dword ptr [esp+10h],64h 
0040134D  lea         ecx,[ecx] 
 
	   	   for (int i = 0; i < 100; i++)
	   	   {
	   	   	   std::copy(source,source+MAX_LOOP,VectorDst.begin());
00401350  cmp         ebx,edx 
00401352  mov         eax,ebx 
00401354  je          main+10Ch (40136Ch) 
00401356  mov         ecx,edi 
00401358  sub         ecx,ebx 
0040135A  lea         ebx,[ebx] 
00401360  mov         ebp,dword ptr [eax] 
00401362  mov         dword ptr [ecx+eax],ebp 
00401365  add         eax,4 
00401368  cmp         eax,edx 
0040136A  jne         main+100h (401360h) 
0040136C  dec         dword ptr [esp+10h] 
00401370  jne         main+0F0h (401350h) 
	   	   }
 
	   	   // End) Timer 끝!
	   	   perfTest.CheckTimeEnd();
00401372  call        esi  

어셈양 자체는 , vector쪽이 많지만 제 시스템에서는 더 빠른 속도를 보입니다

여러 환경 변수가 있겠지만
KLDP분들은 어셈블리쪽에 정통한분들이 많다고 알고 있기에 자문을 구해봅니다

brucewang의 이미지

와, 정말 재미있는 결과군요.

저야 어셈블리를 하나도 모르는 어셈 맹 이지만, 각 방식의 for loop 안 instruction 크기를 보니
memcpy : 004012D2 ~ 004012DD : 0x0A = 10
std::copy : 00401350 ~ 00401370 : 0x20 = 16
정도인데요, 이렇게 되면 CPU 한 pipeline으로 처리가 가능한게 아닌가요?
게다가 multi instruction 처리(?) 까지도 고려해 보면 정말 그런 결과가 나올지도 모르겠네요.

어셈 고수님들의 참여를 위해 제가 말도안되는 답변을 올려 봅니다.. ㅎㅎ
죄송~~

-------------------------------------------------
$yes 4 8 15 16 23 42

다즐링의 이미지

컴퓨터 구조에 보면..

메모리에 접근하는것이 당연히 레지스터에 접근하는것보다 느리죠.
( 라고 책에 있던거 같습니다 -_-;;; )

위의것이
004012D2 mov ecx,0F4240h <-- 특정번지에서 읽어오기
004012D7 mov esi,ebx
004012D9 mov edi,ebp
004012DB rep movs dword ptr [edi],dword ptr [esi] <-- 특정번지에 있는 메모리값 비교

밑의것이
00401360 mov ebp,dword ptr [eax] <-- 특정번지에서 읽어오기
00401362 mov dword ptr [ecx+eax],ebp <-- 레지스터에서 특정번지로
0040136C dec dword ptr [esp+10h] <-- 특정번지의 메모리값 dec
이정도에 파이프라이닝 타버리니까 빨라지는거 같군요.

근데 gcc 최적화 옵션은 어떻게 주셨나요?
( 아 저 어셈블리 모릅니다 )

------------------------------------------------------------------------------------------------
Life is in 다즐링

------------------------------------------------------------------------------------------------
Life is in 다즐링

다즐링의 이미지

결론은.. 성능이슈에서

cpu , 아키텍쳐 , os , 컴파일러를 빼면 참 할말이 없죠 -_-;;;

amd64 에서 현재 쓰시는 컴파일러가 최적화를 잘해주거나..

stl 자체가 뭔가 잘되게 코딩이 되어 있다던가.. 그런듯하네요.

------------------------------------------------------------------------------------------------
Life is in 다즐링

------------------------------------------------------------------------------------------------
Life is in 다즐링

hultul의 이미지

정확한 답변은 아니지만 한가지만 말씀드리면, Array 부분에서 repeat instruction 이 사용되었다는 것입니다.

004012DB rep movs dword ptr [edi],dword ptr [esi]

즉, 단순히 코드 상에 표현된 instruction 개수만으로 비교하기는 어렵습니다.

pipeline 효과는 나중에 고려하더라고, 최소한 각 instruction의 cycle 값 정도를 계산해 보시는 것도 괜찮을 듯 합니다.

코더에서 프로그래머까지

다즐링의 이미지

바로 밑의 http://kldp.org/node/96302 이글도 읽어보시죠.

추상적으로 memcpy 와 std::copy 를 비교하면서...

결론적으로 성능을 이야기 하시는거 같군요.

std library 의 memcpy 가 느리다는건 다 아는 이야기고...
그래서 영상등의 처리 프로그램에선 sse나 등등을 써서 최적화하게 되어 있습니다.

이 쓰레드와 위의 분이 올려주신 gpg 의 쓰레드와 c/c++ 쓰레드를 읽어보시면..

재미가 나실껍니다. -.-;;;

근데.. 올리신분께 질문을 드리자면...

성능이 전분가요? 저 쓰레드에선..(c/c++) 아니라고 하는데.. -0-;;

------------------------------------------------------------------------------------------------
Life is in 다즐링

------------------------------------------------------------------------------------------------
Life is in 다즐링

winner의 이미지

memcpy와 std::copy의 성능비교는 그렇게 의미가 있다는 생각이 들지 않네요.
왜냐하면 기본 algorithm이 다를 여지가 없기 때문입니다.
같은 연속 memory의 복사를 다루니까 말이죠.

결국 내부적으로 얼마나 최적화되어 있느냐의 문제입니다.
누가 언제 어떻게 memcpy를 작성하고,
누가 언제 어떻게 std::copy를 작성했느냐에 따라 달라지겠죠.

그런데 GPGStudy의 글타래를 보니 글 작성시점이 2003년, 2004년, 2008년이 있더군요.
사이에 간격은 있습니다만 5년을 두고 논쟁이 이어지는 건가요?
어떻게 보면 대단하다고 생각합니다.

하지만 그 5년의 세월동안 복사 algorithm 내부의 사정이 많이 바뀌었을테고,
결과도 이렇게 저렇게 세월에 흘러 다르게 나올 것 같군요.

제 생각은 성능이 문제가 되고, 그 성능 문제가 연속 memory 복사문제라고 판단되었을 때
성능이 문제가 되는 부분에 대한 대안으로서 다른 것을 검토해 보는 것이 적절하다고 판단됩니다.

최신 기술이 현재 얼마나 적용되고 있는 것인지가 궁금한 것이 아니라면 말이죠.

jick의 이미지

어쩌면 처음 코드를 수행하면서 데이터값이 CPU cache에 저장되기 때문에 그에 의해 두번째 코드의 수행 시간이 달라질지도 모른다는 생각이 살짝 들어서요...

jick의 이미지

http://softwarecommunity.intel.com/isn/Community/en-US/forums/permalink/30221802/30222021/ShowThread.aspx#30222021

결론은 손으로 잘짠 코드가 rep movs 명령보다 빠르답니다. 그나마 "아주 최근" CPU에서는 그 차이가 좁혀졌다고...

요즘 CPU는 상식을 깨는 행동을 많이 하는군요. -.-

neogeo의 이미지

이래서 프로그래밍 하다보면 하드웨어 공부를 피할수가 없나봅니다 -ㅅ-;

역시 공부는 끝이 없군요 OTL

Neogeo - Future is Now.

Neogeo - Future is Now.

winner의 이미지

공부에 끝이 없는 것은 공감합니다.

제가 hardware에 대해서 공부를 한 바는 거의 없지만 앞으로 하려고 합니다.
하지만 그것은 단지 hardware의 조작방식을 배우기 위함이 아니라
hardware 그 자체와 그것이 주는 일반화된 영감을 위해서입니다.

Hardware를 공부하면서 그것을 programming의 연장선상에 놓고 이야기하기도 하더군요.
그 반대도 있는 것 같습니다만...

mastercho의 이미지


답글이 빨리 달릴줄 알았는데......... -_-; 퇴근전 까지 안달려;;;

제 질문이 묻혀졌구나 했습니다

그래서 잠시 잊었다가 와보니 답변을 많이 달아주셨네요

답변 감사합니다

승자는 자기보다 우월한 사람을 보면 존경심을 갖고 그로부터 배울 점을 찾지만 패자는 자기보다 우월한 사람을 만나면 질투심을 갖고 어디 구멍난 곳이 없는지 찾는다.
- 하비스

neogeo의 이미지

항상 GPG 에서 mastercho 님 글을 눈팅으로 글 많이 읽고 있습니다. :)

KLDP 에선 한동안 못 뵌거 같은데 오랫만에 등장하셔서 GPG에서 가장 뜨거웠던 논쟁을 슬쩍 던져주고 가시는군요.

여하튼 개인시간을 버려가시며 그렇게 깊게 digging 해주신 노력에 항상 감사하겠습니다.

상덕님과 보넥스님과의 논쟁 이야기는 항상 시간을 들여서 아주 재미있게 보고 있답니다 :)

Neogeo - Future is Now.

Neogeo - Future is Now.

grassman의 이미지

rep movs가 의존성이 없는 레지스터를 이용하는 복사보다 느려진 건 꽤 오래된 일입니다. pentium 때 부터였으니까요. 그때 x86 cpu에서는 pipeline이 처음 등장하면서 잘 만든 복사 코드 보다 동일 클럭 수에서 rep 쪽이 느리게 동작했습니다.

다만 놀란 점은 최적화가 잘 되는 컴파일러에 속하는 Visual C++가 저런 식으로 코드를 생성한다는 것입니다. (Visual C++ 6.0, .NET 2003으로 확인해 봤습니다) Visual C++를 사용할 때는 차라리 for 문을 돌려서 복사를 하는 코드를 작성하는게 나을지도 모르겠다는 생각이 들기 시작하는군요. :)

mastercho의 이미지


아 제가 이제 게임 개발을 하다보니 , GPG쪽에 많이 가게 되었네요

예전 KLDP에서 활동도 기억해주시니 ^^ 감사합니다

이제 확실히 rep보다 레지스터쪽 활용이 더 좋을수 있다는것을 이해했습니다

관심 가져주셔서 감사힙니다

승자는 자기보다 우월한 사람을 보면 존경심을 갖고 그로부터 배울 점을 찾지만 패자는 자기보다 우월한 사람을 만나면 질투심을 갖고 어디 구멍난 곳이 없는지 찾는다.
- 하비스

winner의 이미지

어차피 병목은 memory에서 발생하는 것일테니지만 각 core가 독립적으로
memory를 access 할 수 있다면 분명 다른 결과가 나올텐데요.
C 표준상 memcpy는 두 배열이 겹치치 않아야 한다고 정의하고 있습니다만
std::copy는 겹치는 방향에 따라 가능성이 결정됩니다.
결국 memcpy는 병렬처리가 가능하다는 이야기인데 예전에 기억하기로
Linux에서 SMP용 mempcy 함수가 따로 있던 것으로 기억합니다.
memcpy가 동적연결이 되어야 말이 되는 이야기인데
실제로 동적연결이 되는지도 모릅니다만. -_-a.

최근 GCC homepage 가보면 STL도 parallel mode가 등장하고 있고...

하긴 이런 실험은 사실 OpenMP를 쓰는게 정답이긴 합니다.

cppig1995의 이미지

컴파일러에는 "최적화 옵션"이라는 것이 있답니다. 잘 살펴보시길 바라요.
사족인데, "AMD Athlon 64 X2 Dual-Core Processor 4000+"는 "AMD 애슬론 64X2 듀얼" 제품군의 "Core Processor 4000+"인가요?

참고: http://kldp.org/files/AMD_block_prefetch_paper.pdf
--
임수서룬뫼 윤희수 {cppig1995/돼지군}

Real programmers /* don't */ comment their code.
If it was hard to write, it should be /* hard to */ read.

mastercho의 이미지

제 컴퓨터 사항항은 말씀하신거 그대로인거 같습니다;;;

걸어주신 , 링크 자료를 읽어보려면 시간좀 걸리겠네요
어째튼 참고 자료 정말 감사드립니다

승자는 자기보다 우월한 사람을 보면 존경심을 갖고 그로부터 배울 점을 찾지만 패자는 자기보다 우월한 사람을 만나면 질투심을 갖고 어디 구멍난 곳이 없는지 찾는다.
- 하비스

winner의 이미지

기본은 loop unrolling이고 거기에 대한 최적화로 보이군요.

정태영의 이미지

loop unrolling 정도야 컴파일러에서 해줄 수 있는 최적화 중 하나가 될 수 있겠지만 저 문서에 나온 내용 중 80% 이상은 컴파일러에 적용된 케이스가 거의 없을겁니다.

streaming instruction 을 활용해서 최적화하는 것은 아직까지 사람의 몫이죠.

저 문서는 꽤나 유용하네요. EMMX 가 필요없는[1] SSE 를 이용할 경우 얼마나 더 빨라질지 기대됩니다. SSE3 에서도 저기 나온 것처럼 캐쉬를 통하지 않는 메모리 접근이 가능하니 그대로 적용해볼 수 있겠군요.

그리고 memcpy 같은 함수는 이미 다 컴파일되서 오브젝트 형태로 되어 있으니, 이와 링크되는 프로그램에서 최적화 옵션을 주는 것은 영향을 끼치지 못합니다. 메모리 복사와 관련된 템플릿이 memcpy 보다 빠르다면 그 코드가 이미 더 잘 작성되어 있기 때문이겠죠.

[1] MMX 는 레지스터를 FPU 와 공유하기 때문에 mm0~7 중 하나의 레지스터를 사용했다면 fpu stack 에 문제를 일으키지 않도록 EMMS 인스트럭션을 실행해줘야 합니다. 이게 꽤 비용이 크죠.

--
오랫동안 꿈을 그리는 사람은 그 꿈을 닮아간다...

http://mytears.org ~(~_~)~
나 한줄기 바람처럼..

오랫동안 꿈을 그리는 사람은 그 꿈을 닮아간다...

http://mytears.org ~(~_~)~
나 한줄기 바람처럼..

alsong의 이미지

과거에는 dma controler의 처리속도가 더빨랐지만
요즘 cpu가 상당히 빨라져서 이런 결과가 생긴것 아닐까요?
cpu overhead측면에서는 전자가 유리할듯하네요.

헉 삑사리군요 ㅡㅡㅋ dma가 보조기억장치만 관련되어 있나보네요.

그나저나 백수 언제 탈출하냐... ㅡㅡ; 배고파라.

그나저나 백수 언제 탈출하냐... ㅡㅡ; 배고파라.

정태영의 이미지

DMA 를 사용하는 것은 DMA 컨트롤러가 CPU 보다 빠른게 아니라 CPU 에서 busy waiting 을 하느냐 하지 않느냐의 문제입니다.

DMA 를 사용하지 않을 경우 read 가 끝났는지 혹은 write 가 끝났는지를 알기 위해 상태를 체크하기 위한 룹을 돌려야하고, 이로 인해 실제 하는 일은 거의 없지만 CPU 자원은 계속해서 사용을 하게 되는 일이 발생합니다. 코드로 표현할 경우 아래 정도?

while( status != END );

하지만 DMA 를 사용할 경우 read / write 명령을 내린뒤 딴 일을 열심히 하고 있으면 이 명령이 끝난 뒤 interrupt 를 통해 명령이 끝났음을 통지 받고, 그 후에 처리할 일들을 이어서 처리할 수 있게 됩니다. read/write 가 수행되는 시간동안 CPU 에서는 그만큼 다른 일들을 더 처리할 수 있게되죠.

다중 프로세스 환경에서는 read/write 가 일어날 경우 그 프로세스를 sleep 상태로 만들어놓고, 인터럽트가 날라오면 이 프로세스를 깨워주겠죠. ;)

std::copy 나 memcpy 는 서로 거의 비슷한 역할이므로, 코드의 더 최적화정도에 따라 (조금 더 복잡함에도) std::copy 가 더 빠른 결과를 보일 수도 있다고 생각됩니다.

memcpy 는 standard c library 이고, std::copy 는 c++ 의 기본 라이브러리 함수인가요? (제가 C++ 하곤 그닥 안친해서) 맞다면 glibc 와 stdc++ 중 어떤 게 더 최적화가 잘되어있는지랑 연관이 있을 문제라고 생각되네요.

MS 의 c library 나 bsd 의 libc 등을 이용할 경우는 어떤 결과가 나올지 궁금하네요. 또 플랫폼에 따라서도요. ARM 이냐 X86 이냐 X86-64 냐에 따라서도 최적화 정도가 달라질 수 있을테니... :)

--
오랫동안 꿈을 그리는 사람은 그 꿈을 닮아간다...

http://mytears.org ~(~_~)~
나 한줄기 바람처럼..

오랫동안 꿈을 그리는 사람은 그 꿈을 닮아간다...

http://mytears.org ~(~_~)~
나 한줄기 바람처럼..