Modern Kim's mypi

김모던
접속 : 4933   Lv. 59

Category

Profile

Counter

  • 오늘 : 12 명
  • 전체 : 343703 명
  • Mypi Ver. 0.3.1 β
[기본] 프로그래밍 스레드에 대해 질문좀.. (20) 2019/06/22 PM 10:50

Func(long long* a)

{

 for(long long i = 0; i < 10000000; ++i)

 {

  ++(*a);

 }

}

 

위와같이 함수에서 주소를 매개변수로 받아와 값을 증가시킬 때,

 

스레드를 사용하지 않았을 때와, 복수의 스레드를 사용했을 경우(서로 다른 변수주소를 매개변수로 주므로 경쟁상태는 일어나지 않음.)

속도차이가 10배정도로 심하게 많이 났습니다.(복수의 스레드를 사용한 경우가 느렸습니다.)

 

 

 

Func(long long* a)

{

 long long b = 0;

 for(long long i = 0; i < 10000000; ++i)

 {

  ++b;

 }

 *a = b;

}

 

위와같이 반복연산에 스레드 내부데이터를 사용했을 경우엔 약간 느린정도인 것을 보면, 외부자원에 대한 스레드에서의 연산이 느린것으로 보이는데

(동일한 스레드 외부 데이터인 전역변수에 대해서도 같은 상황. 예외로 스레드를 하나만 생성할 경우 발생하지 않았습니다.)

 

왜 그런지 아시는분 혹시 설명해 주실수 있을까요...

 

 

 

 

//추가

로그 첨부합니다. i5 6세대 4코어 CPU, 윈도우10 1903, VS2017이며, Release로 디버깅하였습니다.

함수호출의 경우 큰 차이는 없었고, 좌측상단의 경우 환경에 따라 각 스레드별 40초까지도 늘어났습니다.

 


img/19/06/22/16b7fa98911122379.jpg

 

 

 

 

신고

 

칠삼아    친구신청

크리티컬 섹션을 사용하지 않았기 때문에, 경쟁상황이 생겨서 느린건 아닌것 같고요,
그러니까 스레드가 공용자원에 엑세스 해서 느려진게 아니라,
스레드간 전환(컨텍스트 스위칭)따위에 CPU자원을 소모하고 있는걸로 보이네요

김모던    친구신청

혹시 외부 자원을 사용하지 않았을 경우, 스레드간 컨텍스트 스위칭이 덜 일어나거나 하기도 하나요?
저 외부자원 사용 여부에따라 복수 스레드 작동속도가 확 달라져서요..

칠삼아    친구신청

그건 OS의 역활이라, 사용여부와 관계없을것 같습니다
스레드를 만들었고, 스레드에서 루프가 돌고 있다면 스위칭이 발생할겁니다.
그리고 루프에 Sleep(0)같은게 없다면 CPU를 독점해버리는 일도..

김모던    친구신청

코드 전체입니다. 루프부분 최적화를 막기위해 volatile을 사용했습니다.
Func2의 경우 컨텍스트 스위칭이 일어나는건 마찬가지일텐데, 이전보다 훨신 빠른 속도를 보여줍니다.

#include <iostream>
#include <thread>

using namespace std;

//문제 발생 함수.
void Func(long long* z)
{
for (volatile long long i = 0; i < 1000000000; ++i)
{
++(*z);
}
}

//문제 미발생 함수.
void Func2(long long* z)
{
long long a = 0;

for (volatile long long i = 0; i < 1000000000; ++i)
{
++a;
}

(*z) = a;
}

void main()
{
//======스레드 사용.
long long a = 0;
long long b = 0;
long long c = 0;
long long d = 0;

//문제 미발생의 경우 Func2로 변경.
thread th1(Func, &a);
thread th2(Func, &b);
thread th3(Func, &c);
thread th4(Func, &d);

th1.join();
th2.join();
th3.join();
th4.join();


//======스레드 미사용.
a = 0;
b = 0;
c = 0;
d = 0;

//문제 미발생의 경우 Func2로 변경.
Func(&a);
Func(&b);
Func(&c);
Func(&d);
}

힝~_~    친구신청

func1와 func2의 조건이 동일하지 않아서 그런 것 같아요.
func1의 경우 +1을 하기 위해서 포인터의 주소값을 가져와서 그 주소에서 변수를 읽은 다음 +1해서 다시 그 포인터 변수를 읽어 그 주소값에 write 해야하지만
func2는 이미 스택에서 정해진 주소에서 +1을 하면 되니 단계가 줄어듭니다.

김모던    친구신청

그런데 스레드를 사용하지 않았을 경우엔 Func1에서 해당 현상이 발생하지 않았습니다 ㅠㅠ..

힝~_~    친구신청

thread 쓰지 않았을때도 volatile로 테스트 하신건가요?
thread 썻을땐 register로 안해줄것 같은데...

김모던    친구신청

네 동일함수 콜 해줬습니다.

힝~_~    친구신청

앗.. 그럼 disassemble 해봐야 할것 같은데..
그라고 보니 성능 측정방법은 어떻게 되나요?

김모던    친구신청

<time.h>
clock_t begin, end;
begin = clock();
측정내용
end = clock();
cout << (end - begin) / (float)1000 << endl;
로 측정하였습니다.
함수 내부에서 각 시간 측정하고, 함수호출/스레드시작~끝부분에 넣어 전체시간 측정하였습니다.

김모던    친구신청

디스어셈블 같은 경우, 내용은 스레드/함수호출 둘다 동일했고 Func1과 Func2별로 아래와 같습니다.
제가 어셈블리어를 볼줄 몰라서 어느부분이 원인인지 파악이 안됩니다 ㅠㅠ.

/////////////////////////////////////////////////////////////////////////Func1
/ for (volatile long long i = 0; i < size; ++i)
00521080 mov edi,dword ptr [ebp+14h]
00521083 xorps xmm0,xmm0
00521086 movlpd qword ptr [esp+10h],xmm0
0052108C mov ecx,dword ptr [esp+10h]
00521090 mov eax,dword ptr [esp+14h]
00521094 cmp eax,edi
00521096 jg Func+93h (05210D3h)
00521098 mov esi,dword ptr [size]
0052109B jl Func+61h (05210A1h)
0052109D cmp ecx,esi
0052109F jae Func+93h (05210D3h)
005210A1 mov edx,dword ptr [z]
/ {
/ / ++(*z);
005210A4 add dword ptr [edx],1
005210A7 mov eax,dword ptr [esp+10h]
005210AB adc dword ptr [edx+4],0
005210AF add eax,1
005210B2 mov ecx,dword ptr [esp+14h]
005210B6 adc ecx,0
005210B9 mov dword ptr [esp+10h],eax
005210BD mov dword ptr [esp+14h],ecx
005210C1 mov ecx,dword ptr [esp+10h]
005210C5 mov eax,dword ptr [esp+14h]
005210C9 cmp eax,edi
005210CB jl Func+64h (05210A4h)

/ for (volatile long long i = 0; i < size; ++i)
005210CD jg Func+93h (05210D3h)
005210CF cmp ecx,esi
005210D1 jb Func+64h (05210A4h)

/////////////////////////////////////////////////////////////////////////Func2
/ long long a = 0;

/ for (volatile long long i = 0; i < size; ++i)
00361081 mov ebx,dword ptr [ebp+14h]
00361084 xorps xmm0,xmm0
00361087 movlpd qword ptr [esp+10h],xmm0
0036108D mov ecx,dword ptr [esp+10h]
00361091 mov eax,dword ptr [esp+14h]
00361095 movlpd qword ptr [esp+18h],xmm0
0036109B cmp eax,ebx
0036109D jg Func+0A0h (03610E0h)
0036109F mov edi,dword ptr [size]
003610A2 jl Func+68h (03610A8h)
003610A4 cmp ecx,edi
003610A6 jae Func+0A0h (03610E0h)
003610A8 mov edx,dword ptr [esp+1Ch]
003610AC mov esi,dword ptr [esp+18h]
003610B0 mov eax,dword ptr [esp+10h]
/ {
/ / ++a;
003610B4 add esi,1
003610B7 mov ecx,dword ptr [esp+14h]
003610BB adc edx,0
003610BE add eax,1
003610C1 mov dword ptr [esp+10h],eax
003610C5 adc ecx,0
003610C8 mov dword ptr [esp+14h],ecx
003610CC mov ecx,dword ptr [esp+10h]
003610D0 mov eax,dword ptr [esp+14h]
003610D4 cmp eax,ebx
003610D6 jl Func+70h (03610B0h)

/ long long a = 0;

/ for (volatile long long i = 0; i < size; ++i)
003610D8 jg Func+0A8h (03610E8h)
003610DA cmp ecx,edi
003610DC jb Func+70h (03610B0h)
003610DE jmp Func+0A8h (03610E8h)
003610E0 mov edx,dword ptr [esp+1Ch]
003610E4 mov esi,dword ptr [esp+18h]
/ / //++(*z);
/ }

/ (*z) = a;
003610E8 mov eax,dword ptr [z]
003610EB mov dword ptr [eax],esi
003610ED mov dword ptr [eax+4],edx

루리웹-3649315747    친구신청

외부자원에 대한 권한 문제보단 첨자연산에 의한 속도 이슈가아닐런지요? 레퍼런스로 받아서 다시 테스트 해보시겠어요?

루리웹-3649315747    친구신청

아니면 포문위에 int& c = *z; 후에 c++ 해서 동일한지
확인해보세영

김모던    친구신청

레퍼런스로 받는건 스레드에서 지원 안하는지 에러뱉네요...
레퍼런스에다 포인터 넣어서 돌린건 결과가 같았습니다 ㅠㅠ

doubleO7    친구신청

개별 스레드 스택 메모리 인지 아닌지의 차이같네요.
long long a = 0으로 변수를 할당하면(문제 미발생) 해당 개별 스레드 stack 메모리에 할당을 합니다.
http://www.drdobbs.com/tools/memory-constraints-on-thread-performance/231300494

김모던    친구신청

네 그래서 스레드 내부 스택에 있는경우는 문제 안되는데,
스레드에서 공유데이터나 main함수 스택에 쌓인 변수로 접근하는게 레지스터나 캐시에 올라온게 아니라 느린건가 싶어서 올렸습니다.
관련정보 찾아보려해도 못찾겠더라요...
최적화 후엔 충분히 빠르니까 문제없는거긴 한데 공부차 궁금했었어요.

김모던    친구신청

아 링크 글 내용이 도움이 많이 됐네요. 감사합니다.

김모던    친구신청

//volatile 포함. 느림.
void Func(long long* z)
{
00A31040 push ebp
00A31041 mov ebp,esp
00A31043 sub esp,8
00A31046 xorps xmm0,xmm0
/ for (volatile long long i = 0; i < 1000000000; ++i)
00A31049 movlpd qword ptr [ebp-8],xmm0
00A3104E mov ecx,dword ptr [i]
00A31051 mov eax,dword ptr [ebp-4]
00A31054 test eax,eax
00A31056 jg Func+52h (0A31092h)
00A31058 jl Func+22h (0A31062h)
00A3105A cmp ecx,3B9ACA00h
00A31060 jae Func+52h (0A31092h)
00A31062 mov edx,dword ptr [z]
/ {
/ / ++(*z);
00A31065 add dword ptr [edx],1
00A31068 mov eax,dword ptr [i]
00A3106B adc dword ptr [edx+4],0
00A3106F add eax,1
00A31072 mov ecx,dword ptr [ebp-4]
00A31075 adc ecx,0
00A31078 mov dword ptr [i],eax
00A3107B mov dword ptr [ebp-4],ecx
00A3107E mov ecx,dword ptr [i]
00A31081 mov eax,dword ptr [ebp-4]
00A31084 test eax,eax
00A31086 jl Func+25h (0A31065h)
/ for (volatile long long i = 0; i < 1000000000; ++i)
00A31088 jg Func+52h (0A31092h)
00A3108A cmp ecx,3B9ACA00h
00A31090 jb Func+25h (0A31065h)
/ }
}
00A31092 mov esp,ebp
00A31094 pop ebp
00A31095 ret





//volatile 제거. 빠름.
void Func(long long* z)
{
00131040 push ebp
00131041 mov ebp,esp
00131043 sub esp,8
/ for (long long i = 0; i < 1000000000; ++i)
00131046 mov eax,dword ptr [z]
00131049 xorps xmm0,xmm0
0013104C movlpd qword ptr [ebp-8],xmm0
00131051 mov edx,dword ptr [ebp-4]
00131054 mov ecx,dword ptr [i]
00131057 nop word ptr [eax+eax]
/ {
/ / ++(*z);
00131060 add dword ptr [eax],1
00131063 adc dword ptr [eax+4],0
00131067 add ecx,1
0013106A adc edx,0
0013106D js Func+20h (0131060h)
/ for (long long i = 0; i < 1000000000; ++i)
0013106F jg Func+39h (0131079h)
00131071 cmp ecx,3B9ACA00h
00131077 jb Func+20h (0131060h)
/ }
}
00131079 mov esp,ebp
0013107B pop ebp
0013107C ret

doubleO7    친구신청

false sharing의 전형적인 상황 같은데
https://software.intel.com/en-us/articles/avoiding-and-identifying-false-sharing-among-threads
이것도 도움되는군요. 저도 덕분에 공부가 된듯.

김모던    친구신청

답변 달아주시고 시간써 주신분들 모두 감사합니다!
X