addl $0x1,0xXXXXXXXX 코드의 사용중에 결과가 이상하게 나옵니다...
글쓴이: criny333 / 작성시간: 월, 2014/06/16 - 3:24오전
테스트 환경 :
머신 - x86_64 머신(AMD A8-4555M APU quad-core) (가상머신이 아닙니다)
OS - linux 3.13.0 32bit (kubuntu 14.04 32bit)
일단 소스는 다음과 같습니다.
#include <stdio.h> #include <pthread.h> #define NUM_THREAD 100 void * thread_inc(void * arg); void * thread_des(void * arg); int num=0; int main(int argc, char *argv[]) { pthread_t thread_id[NUM_THREAD]; int i; for(i=0; i<NUM_THREAD; i++) { if(i%2) pthread_create(&(thread_id[i]), NULL, thread_inc, NULL); else pthread_create(&(thread_id[i]), NULL, thread_des, NULL); } for(i=0; i<NUM_THREAD; i++) pthread_join(thread_id[i], NULL); printf("result: %d \n", num); return 0; } void * thread_inc(void * arg) { int i; for(i=0; i<100000; i++) num+=1; <--- (1) return NULL; } void * thread_des(void * arg) { int i; for(i=0; i<100000; i++) num-=1; <--- (2) return NULL; }
일단 코드를 간략하게 설명하자면, 0값이 할당된 전역변수 num을 ++시키는 pthread 50개, --시키는 phread 50개를 만든 후, 모든 스레드의 종료를 기다린 후 최종적으로 num값을 출력 하는 프로그램입니다.
당연히 num을 더하는 부분에 동기화를 해주지 않았기에 최종적인 값은 0이 아닌 다른 값이 나옵니다.
저 코드를 동기화 시키지 않고 0값이 나오게 하기 위해서, 위에서 화살표 표시한 (1), (2)번 부분의 x86 코드를 메모리값을 바로 연산하여 메모리에 저장하는 x86코드로 바꾸는 방법 시도했습니다.
즉, (1)번 코드의 경우, 아래의 코드를
804862f: a1 2c a0 04 08 mov 0x804a02c,%eax 8048634: 83 c0 01 add $0x1,%eax 8048637: a3 2c a0 04 08 mov %eax,0x804a02c ( 0x804a02c 는 num전역변수 주소 )
다음과 같이 변경하였습니다.
804862f: 83 05 2c a0 04 08 01 addl $0x1,0x804a02c 8048636: 90 nop 8048637: 90 nop 8048638: 90 nop 8048639: 90 nop 804863a: 90 nop 804863b: 90 nop ( 0x804a02c 는 num전역변수 주소 )
(2)번 코드의 경우, 아래을 코드를
804865f: a1 2c a0 04 08 mov 0x804a02c,%eax 8048664: 83 e8 01 sub $0x1,%eax 8048667: a3 2c a0 04 08 mov %eax,0x804a02c ( 0x804a02c 는 num전역변수 주소 )
다음과 같이 변경하였습니다.
804865f: 83 2d 2c a0 04 08 01 subl $0x1,0x804a02c 8048666: 90 nop 8048667: 90 nop 8048668: 90 nop 8048669: 90 nop 804866a: 90 nop 804866b: 90 nop ( 0x804a02c 는 num전역변수 주소 )
이렇게 실행파일의 코드를 고친후 테스트 해보면, 0이라는 결과값이 나오지 않습니다.
메모리값을 직접 add하는 코드의 경우 cpu자체적으로 코드를 쪼개서 실행해서 그런건가요...케시관련 문제인가요...아니면 파이프라인 문제인가요?
도무지 감이 안잡힙니다...
Forums:
gilgil.net
메모리 증가도 되는군요. 제가 잘못 알고 있었습니다. (-.-)(_._)(-.-)
www.gilgil.net
gilgil.net
1. op code 수행은 atomic한 것으로 알고 있는데...
2. 다른 thread라 하더라도 메모리는 공유되기 때문에 쫑날 일은 없을 것 같은데...
이상하네요.
www.gilgil.net
메모리에서 load -> ALU에 값 전달 ->
메모리에서 load -> ALU에 값 전달 -> ALU가 전달된 값 계산 -> 메모리에 store
이 정도면 답변이 됬을까요...
아니요...제가 지식이 많이 모자라서 너무
아니요...
제가 지식이 많이 모자라서 그렇게 너무 함축적으로 설명해주시면...ㅜㅜ
코어간에 동기화 문제인가요...?
하나 이상의 코어에서 동시에 같은 메모리 주소를 엑세스시에 머신 자체적으로 동기화 되지 않고, 코어사이 메세지 통신이나 다른 방법을 통해서 따로 동기화를 해줘야 하는건가요....?
메모리에 직접 쓴다고 했지만 결국 1을 증가시키거나
메모리에 직접 쓴다고 했지만 결국 1을 증가시키거나 감소시키기 위해서는
ALU에 메모리에 저장된 값을 읽어와서 전달하게 됩니다.
그다음 ALU에서 계산을 하고 다시 메모리에 저장을 하겠죠
즉, 이 부분에서도 메모리에 쓰이기전까지 일시적인 값 저장이 생긴다는 것입니다.
좀 쉽게 예를 들자면
thread1이 ALU에서 계산하는 단계 즉, 메모리에 저장하기 전 일때
thread2가 메모리에 있는 데이터를 읽어와 ALU에 전달하게 되면
thread2의 ALU에 전달되는 값은 thread1이 계산하기 전 값이 되겟죠
//
//
결국은 알고 계시는 동기화 문제와 같은
결국은 알고 계시는 동기화 문제와 같은 맥락입니다.
일시적으로 값이 저장되기 때문에 생기는 것이죠.
위에 mov add ..등등은 레지스터에 일시적으로 저장이 되는 것이고
addl subl은 alu에 일시적으로 저장되기 때문이라고 생각하시면 됩니다
좀더 확실하게 질문드리면
자세한 답변 감사합니다... 코어간 동기화 메커니즘 그래서 필요한 거군요...
한번 더 자세하게 질문드리자면,
질문1) 좀더 확실하게 질문드리면 "addl $0x1,0x804a02c" 라는 메모리 다루는 코드 1개를 "코어1(스레드1)"이 수행 완료하는 것을 다른 코어(스레드)들이 기다려 주지 않는다는 말인가요?
질문2)
제가 알기로 x86에서 머신코드는 다시 cpu내에서 atomic한 명령어로 쪼개져서 수행되는 걸로 알고있습니다.
그렇다면 결국
addl $0x1,0x804a02c
이 코드와
mov 0x804a02c,%eax
add $0x1,%eax
mov %eax,0x804a02c
이 코드는 fetch해온 후 명령어 해석한 뒤, atomic 한 명령어의 수행은 거의 같다는 말씀인가요?
(물론 전자의 경우 eax가 아닌 임시레지스터를 이용할것이라고 예상이 됩니다만...)
질문3)
그렇다면 코어 하나에서는 올바른 결과값이 나오는 것인가요?
싱글코어 컴퓨터가 없어서 일단은, 가상머신으로 테스트해보니 전자의 코드로 올바른 결과값이 나옵니다만....
코어1개일 경우에도 하이퍼스레드를 지원하는 코어의 경우에는 또 다른 경우가 생길려나요...
x86/x86_64에 lock prefix가
x86/x86_64에 lock prefix가 있습니다. 예를 들어,
아... lock prefix를 깜빡하고
아... lock prefix를 깜빡하고 있었습니다... 답변감사합니다...
댓글 달기