misc.log2016. 4. 17. 16:15

2006년 이후의 내 PC사양을 기록해본다.
그 이전의 기록은 여기


2007년

CPU : Intel Core2Duo E6600

코드네임 Conroe. 멀티코어의 시대를 자리잡게 한 기념비적 듀얼코어 CPU. 잠깐 AMD에 빼앗겼던 대세를 다시 돌려놓은 인텔의 전환점. 시제품으로 벤치마크를 했을 때부터 기존 자사 제품은 물론 AMD CPU까지 쌈싸먹었던 결과가 나와서 출시하자마자 "당연히 CPU는 콘로"라는 인식이 박혔다. 나도 13년간 인텔을 써오다가 잠깐 AMD를 썼지만 콘로가 나오자마자 그해 가을쯤 다시 인텔로 갈아타게 되었다. (2016년 현재 상태를 보면 이후로도 쭉 당분간은 AMD CPU로 바꿀 일은 아마 없을 것 같다)


메인보드 : ASUS P5K

CPU를 다시 인텔 제품으로 바꿨기에 어쩔 수 없이 메인보드도 인텔 칩셋으로 교체. 소켓1155가 오래 갈것이란 발표가 있었기에 좀 고가의 메인보드로 CPU업글을 한번 더 할 생각으로 산 제품.


그래픽카드 : GeForce 7900GS

원래는 AMD Athlon64 3200+를 쓰다가 한번 그래픽카드만 업글 한 것인데, CPU+메인보드로 돈을 많이 들여서 그냥 쓰던 걸 계속 썼다.


2009년

CPU : Intel Core2Duo E8400

코드네임 울프데일. 마이너한 CPU업그레이드. 전에 쓰던 E6600은 미러한테 끼워줬다. 울프데일은 이전에 쓰던 콘로보다 저전력, 저발열에 강점이 큰 아키텍쳐라서, CPU를 바꿔달고 귀엽기까지 한 납작한 정품 쿨러의 크기가 인상적.


메인보드 : ASUS P5K
동일 소켓에서 CPU만 바꾼 업글이어서 메인보드는 쓰던 걸 그대로.


그래픽카드 : Radeon HD4850

원래는 7900GS를 쓰다가 8800GT를 사려고 했다. 8800이 처음 나왔을 때는 DX10을 하드웨어레벨에서 지원하는 유일한 물건이었고, 기존 게임 성능도 그냥 당대 최강이었다. 그런데 워낙 잘나가는 물건이 돼 놔서 가격이 잘 안떨어 졌던 것이 문제. 게다가 가격 하락을 기다리다 보니 nVidia쪽 그래픽카드는 9800시리즈가 나왔는데, 이게 사실 8800을 오버클럭한 정도의 제품인지라 메리트가 떨어졌다. 한편 ATI가 AMD에게 먹힌 후 벤치마크에서 지포스보다 앞서는 데다 가격까지 저렴해진 것을 발견. 역시 7900GS는 미러한테 끼워주고 떨거지로 나온 6800GS는 폐기처분. 사실 이후의 업글에서 남게 되는 쓰던 부품은 다 여사님한테 끼워주는 걸로....


2011년

CPU : Intel Core i7 2600K

코드명 샌디브리지. 사실 네할렘이 나올 때부터 무척 갈아타고 싶었는데, 2009년에 이미 울프데일로 CPU를 바꾼 상태에서 1년도 안된 시점에서 네할렘이 나오는 바람에 시기가 안맞아서 여엉부영 하다보니 네할렘의 시대가 가고 샌디브리지의 시대가 온 것.  


메인보드 : ASUS P8P67

ASUS의 P67칩셋 메인보드. P67칩셋은 SATA컨트롤러때문에 말이 많았다. 사우스브리지 내장 SATA컨트롤러만 쓰면 문제가 없었지만, 외부 SATA컨트롤러의 포트에 드라이브를 연결하면 점점 성능이 저하되는 문제가 있었던 것. 나는 다행히도, 자금사정때문에 시기를 기다리다 보니 본의 아니게 P67칩셋의 SATA 문제가 해결된 P67 B3가 출시된 이후에 사게 되어서 문제를 회피.


Radeon HD6850

이것도 CPU교체와 동시에 산 것은 아니고 가을쯤에 따로 그래픽카드만 업글한 것. 이 당시의 nVidia는 8800당시의 아키텍쳐를 가지고 클럭을 올렸다가, 스트림프로세서를 늘렸다가, 또 메모리 대역을 늘렸다가... 하는 식으로 3년을 버티던 시기였다. 사실상 8800, 9800, GT250까지는 그놈이 그놈이다 (모두 코어 아키텍쳐는 G92). 그래서 이번에도 다시한번 그래픽은 역사와 전통의 ATI를 먹은 AMD를 믿고 가 주었다. 이 당시는 AMD가 가장 잘 나갔던 시기로써, 비슷한 시기의 GTX500 시리즈와 성능이 엎치락 뒤치락하면서도 가격이 상당히 쌌다. 한마디로 가성비 갑.



2013년

Intel Core i7 3770

우리집 여사님이 와우를 플레이할 때 끊기는 거 같아서.... 내 PC를 업글하고 샌디브리지를 여사님 PC에 장착. 샌디브리지에서 아이비브리지로의 변화는 GPU쪽에 더 중점이 있고 사실상 CPU성능차이는 10%도 안나지만 발열도 좀 더 적고 전력도 좀 더 적게 먹고 하는 장점이 있다.


메인보드 : GIGABYTE Z77X-UD4H

사실 소켓이 바뀐게 없기 때문에 그냥 쓰던 보드를 써도 되지만, 울프데일을 아직도 쓰던 여사님의 보드도 같이 갈아야 해서 내꺼를 새로 샀다. (어째 이상한거 같다면 기분탓) 이때 기분탓인지 모르겠지만 고급 부품을 아낌없이 쓰던 걸로 유명했던 ASUS가 예전같지 않은거 같아서 기가바이트로 갈아탐.


그래픽카드 : Geforce GTX 770

툼레이더 리부트를 하기 위해서 2014년에 교체. 당연히 쓰고 있던 6850은 여사님에게. GTX770은 당시 보다 상위로 가려면 780밖에 없었는데 780이 너무 고가라 어쩔 수 없이 770으로 목표를 낮춰서 샀는데, 2014년에 사서 어쨌거나 2016년 지금까지 잘 쓰고 있다.


2016년도 항목도 곧 생겼으면 좋겠다.... (사심)


Posted by uhm
dev.log2014. 9. 10. 12:27

C++ 람다가 잘 이해가 안간다면, 다음과 같은 코드를 생각해 보면 된다.


void function()

{

struct functor

{

    ClassX& x;
    int& i;

    functor( ClassX& a, int& b ) : x(a), i(b) {}
    void operator() ( ValueType v ) {  ..... /* code using x & i */ }

};


ClassX x;

int i;

functor f( x, i );


ValueType a;

f(a);

}


위 코드와 '거의' 동등한 일을 다음 코드를 쓰면 컴파일러가 알아서 만들어 준다는 거다.


void function()

{

ClassX x;

int i;

auto f = [&x,&i](ValueType v){ .... /* code using x&i */ };


ValueType a;

f(a);

}


Posted by uhm
dev.log2013. 10. 21. 21:32

part 1에서, 언리얼 리소스 로딩은 재귀적 알고리듬으로 짜여 있다고 한 바 있다. 재귀적 알고리듬 자체는 문제가 아니지만, 내가 할 작업에 있어서는 저런 큰 덩어리의 작업이 재귀적으로 짜여 있으면 락을 큰 단위로만 걸어야 하므로 문제가 있다. 게다가 읽기 작업이 언제 어디서 몇회나 일어날 지 모르므로, 이에 대한 컨트롤을 위해서 재귀적 알고리듬을 반복적 알고리듬으로 바꿔야만 했다.


재귀적 알고리듬을 반복적 알고리듬으로 바꾸는 건, 대학교 2학년때 배우는 알고리듬 수업만 해도 충분하다. 펑션 파라미터를 모아서 스택을 구성하고, 재귀 호출이 일어날 때 스택에 파라미터를 push하고 루프를 돌리면 된다. 이 과정을 슈도 코드로 써보면 이렇게 된다.


load( name )

{

stack.push( name )

while !stack.empty()

{

o = create( stack.top );

for each property in o

read( property );       // 디스크 읽기!!!

if ( property is a object )

push( property.name ); // 재귀호출이 일어나는 곳은 스택으로 대체


if ( stack.top == o.name )

o.postload();

stack.pop();

}

}


그리고 이 수도코드에 크리티컬 섹션으로 락을 걸면 실제 프로젝트에 들어가 있는 코드와 대충 비슷하다. 이제 문제는 실제 읽기 작업을 크리티컬 섹션 밖에서 할 수 있도록 빼는 것이다. 이 작업의 핵심 아이디어는, 실제 파일에서 읽어들인 내용물을 메모리 버퍼에 담아놓고 오브젝트 로딩 과정에서는 디스크보다는 속도가 훨씬 빠른 메모리에서 값을 가져오도록 하는 것이다.
(load_buffer는 락 바깥에 있어야 한다는 점에 주의!!!)


load( name )

{

stack.push( name )

while !stack.empty()

{

buffer = load_buffer( stack.top ); // 디스크 읽기!!

lock();

o = create( stack.top );

for each property in o

buffer.read( property ); // 메모리 복사

if ( property is a object )

push( property.name );


if ( stack.top() == o.name )

o.postload();

stack.pop();

unlock();

}

}


이제 남은 문제는 버퍼를 어디에서 읽어야 하는지를 파고들어가서 그걸 내손으로 구현하는 일이다.


언리얼 엔진에서 쓰는 패키지 파일을 설계한 아저씨는 (아마도 팀 스위니겠지만) 여러 면에서 DLL에 대한 은유를 많이 사용했다. 각 패키지 안에 들어있는 모든 오브젝트는 외부 패키지에서 접근할 수 있도록 export 테이블에서 노출되고, 또한 다른 패키지에서 참조하는 오브젝트는 import 테이블을 통해 관리한다. 이들 오브젝트의 레퍼런스를 resolve해주는 과정을 link라고 부르는 것은 덤.


언리얼 패키지에서 오브젝트 하나를 로딩하는 절차는 UDN에 대충 나와 있긴 하고, 소스 코드를 보면 누구나 알 수 있긴 하지만, 정리해 보면,


(시작)

1> 오브젝트 이름에서 패키지 이름 추출

2> 패키지 summary 읽기 

3> 패키지 export 테이블 읽기 

4> 패키지 import 테이블 읽기

5> 패키지 export 테이블에서 오브젝트 검색하고 대상 오브젝트 생성

6> export 테이블에서 지정된 대로 오브젝트 읽기

7> 읽는 동안 패키지 내부의 오브젝트 참조가 발생하면 export 테이블에서 검색

8> 읽는 동안 외부 패키지의 오브젝트 참조가 발생나면 import 테이블에서 검색

9> 모든 참조가 resolve되면 종료.


part 1에서 생성 -> 읽기 -> 정리의 과정은 위에서 5~9 사이의 과정이라고 할 수 있다. 물론, 2~4 사이의 과정은 패키지가 이미 열려져 있는 상태라면 생략되도록 구성되어 있다. (에픽 아저씨들이 바보는 아니니까) 이 절차를 정리해 본 것은, 패키지 summary와 export/import  테이블은 언리얼 리소스 로딩 과정을 개조하기 위해 필수적으로 건드려야 하는 개념이기 때문이다.


summary에는 각 오브젝트의 정보보다는, 패키지 자체에 대한 정보가 있지만, 여기서 가장 중요한 것은 export/import 테이블의 옵셋과 사이즈를 알려준다는 점이다. export 테이블에는 패키지 내에 저장돼 있는 모든 오브젝트의 옵셋과 사이즈, 이름, 클래스 등에 대한 정보가 담겨 있다. 오브젝트를 로딩하기 위한 핵심 정보이다. import 테이블은 전체적으로는 export 테이블과 비슷한데, 차이점이라면 외부 패키지의 오브젝트에 대한 참조이기 때문에 패키지 이름이 추가돼 있다는 정도?


이제 읽어야 할 것에 대한 정보가 어디 들어있는지를 알았으므로, 이 지식을 코드에 반영하면 된다.


load_buffer( name )

{

if ( name is internal )

e = export.find( name )

else

e = import.find( name )


buffer = create( e.size );

read( buffer, e.offset, e.size );

return buffer;

}


여기까지 하고, load 펑션을 백그라운드 쓰레드로 돌리면, 디스크 읽기로 인해 메인 쓰레드가 멈추는 일 없이, 실제 공유자원인 전역 오브젝트 배열에 대한 락에 필요한 시간보다 크게 길지 않게 락을 잡고 작업을 병렬화 할 수 있을 것이고, 실제로 동작하긴 했다.


그런데 무수히 많은 크래쉬와 데드락이 일어났다.


- 계속



Posted by uhm