[NDC2016] TERA 서버의 Modern C++ 활용기

Post on 16-Apr-2017

2.378 views 1 download

Transcript of [NDC2016] TERA 서버의 Modern C++ 활용기

TERA 서버의Modern C++ 활용기

Bluehole

이상헌

1

목차

1. Introduction2. 스마트포인터의 오남용과 Move Semantics3. Variadic Template and some pratical techniques4. 미래와 약속 (Future-Promise)5. Q&A

2

발표자 소개

• Software Engineer• Interest: C++, Concurrency, Server-Side Techs...

• 경력• 2014 – 현재 : Bluehole 테라본부 서버팀

• 2012 – 2014 : M.S. Programming Languages Lab, KAIST

• 2004 : 전국 이야기 말하기 대회 대구지역 2등, 지역대표

• Contact• developistbv@gmail.com

3

The Exiled Realm of Arborea

Free-Targeting Action MMORPG

2007-2011: 개발2011.01 : 한게임 서비스2016.01 : NEXON 서비스

NA, EU 포함 7개 지역에서 서비스 중

4

발표하기 앞서

• C++03 기준으로 작성된 테라서버 코드에서 Modern C++를 활용한 사례를 소개합니다• 더 많은 활용 예가 공유되었으면 하는 바램에 발표하게 되었습니다.

• 과거가 ‘잘못 되었다’가 아니라, 이제 ‘더 나은 길’이 열렸다는 것을 소개 하는게 목적입니다.• 보안 문제로 실제 테라 코드를 담진 않았습니다.

• (*)가 표시된 슬라이드는 부연 설명을 위한 슬라이드입니다.• 발표 땐 시간 관계상 다루지 않을 수 있습니다.

• 저작권법 제 35조의 제 3 ‘공정이용’ 조항에 따라 교육과 연구 목적으로 일부 저작물을 인용하였습니다. 혹시 문제가 있을 경우 메일로 연락주시면 적절한 조치를 취하겠습니다.

5

1. Introduction2. 스마트포인터의 오남용과 Move Semantics3. Variadic Template and some pratical techniques4. 미래와 약속(Future-Promise)5. Q&A

6

리빙 포인트: 발표 중간중간에 있는 이런 슬라이드는 진행 상황을 알려준다

필요한 사전 지식

• smart pointer (RAII)• 객체의 생성 소멸 관리를 위해서 사용하는 객체

• shared_ptr<T>, unique_ptr<T>

• t_ptr<T>• shared_ptr<T>처럼 Reference Count를 공유

• 침습형(invasive) 카운터를 사용

• Move Semantics, Rvalue Reference, Perforect Forwarding...

7

Cppcon2015 – Writing Good C++14 中

• “Smart pointers” are popular• To represent ownership

• To avoid dangling pointers

• “Smart pointers” are overused• Can be expensive

• E,g., shared_ptr

• Can mess up interfaces fore otherwise simple function• E.g. unique_ptr and shared_ptr

• Often, we don’t need a pointer• Scoped objects

• We need pointers

8

Reference Count의 증가 연산이 많이 일어나기 때문

shared_ptr의 문제는 t_ptr의 문제와 같다

void BattleField::OnAttack(int gameId) {

t_ptr<User> attacker = GUserManager->GetUser(gameId);

for(auto it = mUserList.begin(); it != mUserList.end(); ++it) {

t_ptr<User> target = *it;

IsSameBattleFieldTeam (attacker, target);

}

}

bool BattleField::IsSameBattleFieldTeam(t_ptr<User> user1, t_ptr<User> user2) {

t_ptr<BFChannel> channel = type_cast<BFChannel*>(user1->GetChannel());

if(channel) { … }

}

9

Bad Code!

사례1) 빠른줄만 알았던 type_cast

template<typename T, typename S>

t_ptr<T> type_cast<T>(t_ptr<S> s)

{<29.9%

if(s && TypeTable[Idx<S>][Idx<T>])<0.1%

{

return t_ptr<T>(*s);

}<70.0%

return nullptr;

}

template<typename T, typename S>

T* type_cast<T>(S* s)

{if(s && TypeTable[Idx<S>][Idx<T>])

{

return static_cast<T>(s);

}

return nullptr;

}

뜻밖의복사생성자와소멸자가호출!

10

사례1) 빠른줄만 알았던 type_cast (수정)

template<typename T, typename S>

t_ptr<T> type_cast<T>(t_ptr<S>& s)

{

if(s && TypeTable[Idx<S>][Idx<T>])

{

return t_ptr<T>(*s);

}

return nullptr;

}

template<typename T, typename S>

t_ptr<T> type_cast<T>(t_ptr<S>&& s)

{

if(s && TypeTable[Idx<S>][Idx<T>])

{

return t_ptr<T>(*s);

}<29.9%

return nullptr;

}

뜻밖의호출들이사라졌다!

11

다시는 atomic 연산을 무시하지마라

• Atomic Increment/Decrement 연산은 (생각보다) 비싸다.• ++ 처럼 가볍게 여기지 말자

• 본의아니게 dynamic_cast만큼 느린 type_cast를 쓰고 있었다.• 서비스엔 아무런 지장이 없었음 (0.1ms vs 0.09ms, 호출양도 적음)

• 이처럼 스마트포인터의 복사생성이 의미 없이 발생할 때가 많다.• type_cast는 그저 하나의 예제일 뿐

12

사례 2) 빠른줄만 알았던 Vector

• GatherGameObjectList 라는 함수가 오래 걸린다.

• 주위에 있는 GameObject의 t_ptr를 Vector에 저장 하는데 왜 느리지?

• Vector 예찬론자로서 인정할 수 없는 일• Reallocation에서 RefCount++ / RefCount-- 가 일어나는걸 확인

13

Vector Reallocation (push_back)

A B C D

A B C D E

Step 1. 재할당 및 복사 A B C D

Step 2. 원본 객체 소멸

Step 3. 새 객체 삽입

RefCount ++ *4

RefCount -- *4

RefCount ++

E객체 E를 추가하려 할 때 R1

R2

R1

R2

불필요한 연산!

14

Move Semantics

• Rvalue Reference• Object&& object = GetObject();

(cf. Object& object = otherObject;)

• 생성자나 대입연산자가 Rvalue를 사용할 땐 ‘소유권’ 을 넘겨주는 것만으로 충분하다

Object(Object&& other){mChild = other.mChild;other.mChild = nullptr;

}

Object(Object& other){DeepCopy(mChild, other.mChild);

}

15

소유권을 계승하는 중입니다• 사라질 객체는 소유권을 위임하는 것으로 충분하다.

t_ptr<T>(t_ptr<T>&& t) {mT = t.mTt.SetNullWithoutRelease();

}

t_ptr<T>& operator=(t_ptr<T>&& t) {release();mT = t.mTt.SetNullWithoutRelease();

}

• 와! 이제 Vector Reallocation 때 Move Constructor가 호출 되겠죠?

• 아니요.

16

Succeeding you, rvalue(?)

Perfect Forwarding

• Custom allocator에서 쓰는 C++03 버전 construct 함수가 문제

void construct(pointer _ptr, const _Ty& _Val) → Lvalue Reference가 되어버렸다!

{

::new ((void*)_Ptr) value_type(_Val));

}

• Perfect Forwarding을 하도록 수정하여 해결

void construct(pointer _Ptr, _Ty&& _Val) → Universal Reference!

{

::new ((void*)_Ptr) value_type(forward<_Ty>(_Val)); // R->R, L->L로 처리

}

17

개선 후 CPU 점유율 변화

• 시나리오: 다수의 채널에서 다수의 PC가 뭉쳐서 NPC 무리와 전투

주위의 NPC 수 Before Avg After Avg

N 12.6% 7.8%

2N 23.6% 18.4%

3N 40.0% 30.0%

4N 57.5% 37.8%

18

(*) Live 서버에서 평균 CPU 사용률은 1번 케이스보다 낮음

개선 후 Sample Rate 변화 (3N)

• RefCount 연산의 Sample Rate 변화: 47.5% → 26.5%

• Reallocation의 Sample Rate 변화

Before(14.8%)

After(2.8%)

19

(*) Cppcon2015 – Writing Good C++14 中

• 스트롭스트룹 할아버지께서 말씀하시길• Shared_Ptr이 지나치게 남용(Overuse)되고 있다는 것을 느꼈다.

• 많은 사람들이 shared_ptr를 파라미터(by value)로 쓰고, 또 리턴하고 있다.

• 이러면 RefCount가 의미에 맞지않게 올라가고 내려가는 것이 반복된다.

• Dangling Pointer는 룰을 잘 따르고(툴을 이용하면) 다 잡을 수 있다.

• Rules for Eliminating Dangling Pointers• Owner와 Non-Owner를 구분한다.

• Raw-Pointer는 모두 Non-Owner이다.

• Pointer가 Owner의 Scope를 벗어나려하는 시도를 모두 잡는다.

• Parameter로 받아서 Return으로 돌려주는건 괜찮음

• Owner를 가지고 있는 객체는 Owner다.

• vector<T*>, owner<T*>

20

(*) Cppcon2015 – Writing Good C++14 中

int* f(int* p){

return p; //괜찮음

return new int{7} //Owner로받으면 O.K.

int x = 4;return &x; //극혐

}

owner<int*> k = f(p); // O.K.

21

(*) GSL: Owner<T>

• GSL = Guideline Support Library (Global Starcraft II League)• https://github.com/Microsoft/GSL

• STL = The ISO C++ standard library

• C++ Core Guideline의 규칙들을 지원하는 라이브러리• https://github.com/isocpp/CppCoreGuidelines

• template <class T> using owner = T; • 정적분석을 위해

• 문서화를 위해

22

1. Introduction2. 스마트포인터의 오남용과 Move Semantics3. Variadic Template and some pratical techniques4. 미래와 약속(Future-Promise)5. Q&A

23

리빙 포인트: 과거에 작성된 스마트포인터엔 Move Semantics를 구현해주자

사전 지식: Template in C++

• template <typename T>class Stack;{

…bool push(T elem);T pop();

}

• Stack<int> mStack;

24

‘tnew’ in TERA Server (과거)

#define MEMORY_ALLOC_ARG_NUM 40#define MEMORY_tnew_print(z, n, _) \template<class Type BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n, class Arg)> \Type* tnew(BOOST_PP_ENUM_BINARY_PARAMS(n, Arg, arg)) \{ \

Type* memory = (Type*)AllocMemory(sizeof(Type)); \new(memory) Type(BOOST_PP_ENUM_PARAMS(n, arg)); \return memory; \

} \

BOOST_PP_REPEAT(BOOST_PP_INC(MEMORY_ALLOC_ARG_NUM), MEMORY_tnew_print, _)

25

‘tnew’ in TERA Server (먼 과거)

template<class Type, class Arg1, class Arg2, class Arg3>Type* tnew(Arg1 arg1, Arg2 arg2, Arg3 arg3){

Type* memory = (Type*)AllocMemory(sizeof(Type));new(memory) Type(arg1, arg2, arg3);return (Type *)memory;

}

• 과거의 나: 이게 더 깔끔한거 같은데요? -_-

26

• 사수님: 헷갈린다면 이 리비전의 코드를 보고 공부 하거라.

‘tnew’ in TERA Server(먼 과거)

27

Variadic Template

• 임의의 인자 개수를 지원하는 템플릿 형식(클래스, 함수 모두 지원)

• Ellipsis operator(= …)와 함께 사용- arguments, parameters, sizeof op, initializer list of array, member initializer list, lambda capture, exception specification list

• template<typename... Args >void DoFunc(Args... args) {

Func(args…);}DoFunc(1, “hi”) = void DoFunc<int, string>(1, “hi”);DoFunc() = void DoFunc<>()

28

당연히 될 것 같은 것들

• template<typename T, typename... Args>void DoFunc(T obj, Args&... args) {

Func(obj, args...);}

• template<typename... Args>void DoFunc(Args... args) {

const unsigned argSize = sizeof...(Args);}

• template<typename... Base>class DoClass : public Base... {

DoClass(Base&&... base): Base(base)... {}}

29

#define MEMORY_ALLOC_ARG_NUM 40#define MEMORY_tnew_print(z, n, _) \template<class Type BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n, class Arg)> \Type* tnew(BOOST_PP_ENUM_BINARY_PARAMS(n, Arg, arg)) \{ \

Type* memory = (Type*)AllocMemory(sizeof(Type)); \new(memory) Type(BOOST_PP_ENUM_PARAMS(n, arg)); \return memory; \

} \

BOOST_PP_REPEAT(BOOST_PP_INC(MEMORY_ALLOC_ARG_NUM), MEMORY_tnew_print, _)

‘tnew’ in TERA Server

template<class Type, class... Args>Type* tnew(Args&& ... args){

Type* memory = (Type*)AllocMemory(sizeof(Type));new(memory)Type(forward<Args>(args)...);return memory;

}

30

성공에 힘 입어 여기저기 수정

Typelist<typename Head, typename Tail>

#define TYPELIST_1(T1) Typelist<T1, NullType>

#define TYPELIST_2(T1, T2) Typelist<T1, TYPELIST_2(T2)>

#define TYPELIST_3(T1, T2,T3) Typelist<T1, TYPELIST_3(T2, T3)>

TYPELIST_5(GameObject, Creature, User, Npc, Item);

Typelist<typename... T>Typelist<GameObject, Creature, User, Npc, Item>;

• TypeList, PacketWriter, LogWriter 등등…

Before

After

31

끝판왕에 도전#define ASYNC_JOB_print(z, n, _)\template <class T BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n, class Arg)> \struct BOOST_PP_CAT(AsyncJob, n) : public Job \{ \typedef void (T::*MemFunc)(BOOST_PP_ENUM_PARAMS(n, Arg)); \BOOST_PP_CAT(AsyncJob, n)(T* obj, MemFunc memFunc BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_BINARY_PARAMS(n, Arg, arg)) \: mObj(obj), mMemFunc(memFunc) BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM(n, ASYNC_PARAM_ASSIGN_print, ~) \{ \AddRef(mObj, REUSE_NDC); \BOOST_PP_REPEAT(n, ASYNC_ARG_ADDREF_print, REUSE_NDC) \

} \void OnExecute() \{ \(mObj->*mMemFunc)(BOOST_PP_ENUM_PARAMS(n, mArg)); \ReleaseRef(mObj, REUSE_LFECALL); \BOOST_PP_REPEAT(n, ASYNC_ARG_RELREF_print, REUSE_NDC) \} \MemFunc mMemFunc; \T* mObj; \BOOST_PP_REPEAT(n, ASYNC_ARG_DECL_print, _) \

};

#define ASYNC_print(z, n, _) \template <class T BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n, class Arg)> \bool Async(void (T::*memFunc)(BOOST_PP_ENUM_PARAMS(n, Arg)) BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_BINARY_PARAMS(n, Arg, arg)) \{ \Job* job = tnew<BOOST_PP_CAT(AsyncJob, n)<T BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n, Arg)> >(static_cast<T*>(this),memFunc BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n, arg)); \

return mJobQueue.Do(job); \}

32

template <typename T, typename Arg1T, typename Arg2T>struct AsyncJob2 : public Job {

typedef void (T::*MemFunc)(Arg1T, Arg2T);AsyncJob2(T* obj, MemFunc memFunc, Arg1T arg1, Arg2T arg2): mObj(obj), mMemFunc(memFunc), mArg1(arg1), mArg2(arg2) {AddRef(mObj, REASON_NDC);AddRef(mArg1, REASON_NDC);AddRef(mArg2, REASON_NDC);

}void OnExecute() {

(mObj->*mMemFunc)(mArg1, mArg2);ReleaseRef(mObj, REASON_NDC);ReleaseRef(mArg1, REASON_NDC);ReleaseRef(mArg2, REASON_NDC);

}

MemFunc mMemFunc;T* mObj;Arg1T mArg1;Arg2T mArg2;

};

template <typename T, typename Arg1T, typename Arg2T>bool Async(void (T::*memFunc)(Arg1T, Arg2T), Arg1T arg1, Arg2T arg2) {

Job* job = tnew<AsyncJob2<T, Arg1T, Arg2T> >(static_cast<T*>(this), memFunc, arg1, arg2);return mJobQueue.Do(job);

}

어떻게 처리해야 할까?

33

std::tuple

• 정의template<typename... Types >class tuple;

• 예제tuple<int, bool, string> tpl = make_tuple(3, true, “Hi”);cout << get<2> tpl << endl;

• 구현template<typename Head, typename... Tail>class Tuple_impl {

Head head;Tuple_impl<Tail...> tail;…

}

34

Template <class T, class… Args>Struct AsyncJob : public Job{

typedef void (T::*MemFunc)(Args…);AsyncJob(T* obj, MemFunc memFunc, Args… args): mObj(obj), mMemFunc(memFunc), mParams(args…){

AddRef(mObj, REASON_NDC);//AddRefForEach???(…)

}

void OnExecute(){

//(mObj->*mMemFunc)(mParams???);ReleaseRef(mObj, REASON_NDC);//ReleaseRefForEach???(…)

}

MemFunc mMemFunc;T* mObj;tuple<Args...> mParams;

};

template <class T, class... Args>bool Async(void (T::*memFunc)(Args...), Args... args){

Job* job = tnew<AsyncJob<T, Args...>>(static_cast<T*>(this), memFunc, args...);return mJobQueue.Do(job);

}

감독님…“튜플의 각 원소들에 대해서함수를 수행하고 싶고, 함수의 인자로도 사용”하고 싶어요….

35

Integer_sequence

• template<int... Remains>struct seq{};

• template<int N, int... Remains>struct gen_seq : gen_seq <N - 1, N - 1, Remains...> {};

• template<int... Remains>struct gen_seq<0, Remains...> : seq <Remains...> {};

• gen_seq<3> → <2, 2> → <1, 1, 2> → <0, 0, 1, 2> → seq<0, 1, 2>

(*) C++14부턴 간단하게 std::make_index_sequence<N>로 대체 가능

36

Tuple을 매개변수로 써보자(Apply)

• template<typename Ret, typename... Args>Ret apply(Ret (*func)(Args...), tuple<Args...>& tup){

return apply_impl(func, gen_seq<sizeof...(Args)>(), tup);}

• template<typename Ret, typename... Args, int... Is>Ret apply_impl(Ret(*func)(Args...), seq<Is...>, tuple<Args...>& tup) {

return func(std::get<Is>(tup)...); }

37

Sequence 이용해서 Tuple을 순회하자

• template<typename... Ts, typename F>void for_each(tuple<Ts...>& tup, F func){

for_each_impl(tup, func, gen_seq<sizeof...(Ts)>());}

• template<typename T, typename F, int... Is>void for_each_impl(T& tup, F func, seq<Is...>){

auto l = { ( func(get<Is>(tup)) , 0 )... }; //Using Ellipsis Op}

• for_each(make_tuple(3, 3.0), functor());

38

(*) Ellipsis Operator의 활용

int k = (0, 3); k = ?

1. Brace-Init-List

int k[] = {1, 2, 3};

auto l = { ( func(get<Is>(tup)) , 0 )... };

2. Function Argument

template<typename... Args>

void MagicFunc(Args&&... args) {}

MagicFunc( ( func(get<Is>(tup)) , 0 )...); → 실행 순서 보장이 안됨!

39

완성?! ?! ?!template <class T, class… Args>struct AsyncJob : public Job{

typedef void (T::*MemFunc)(Args…);AsyncJob(T* obj, MemFunc memFunc, Args… args): mObj(obj), mMemFunc(memFunc), mParams(args…){

AddRef(mObj, REASON_NDC);AddRefForEach(mParams, REASON_NDC);

}

void OnExecute(){

Apply(mObj, mMemFunc, mParams);ReleaseRef(mObj, REASON_NDC);ReleaseRefForEach(mParams, REASON_NDC);

}

MemFunc mMemFunc;T* mObj;tuple<Args...> mParams;

};

template <class T, class... Args>bool Async(void (T::*memFunc)(Args...), Args... args){

Job* job = tnew<AsyncJob<T, Args...>>(static_cast<T*>(this), memFunc, args...);return mJobQueue.Do(job);

}

40

안 돼 안 추론해줘. 해줄 생각 없어. 빨리 돌아가.

session->Async(&NdcSession::End, name, reason);error: Cannot deduce template argument as function argument is ambiguous

class NdcSession{

…void End(string name, int reason);void End();…

}

41

해결법?

1. session->Async(((void (NdcSession::*)(string, int))&NdcSession::End, name, reason);

2. session->Async(((void (NdcSession::*)(decltype(name), decltype(reason)))&NdcSession::End, name, reason);

3. session->Async<NdcSession, string, int>(&NdcSession::End, name, reason);

4. #define Async(obj, func, ...) _Async((void (Obj::*)(decl_helper(__VA_ARGS__))&Obj::func, __VA_ARGS__)

5. 오버로드 함수를 다지운다.

42

잘 모르겠어서 일단 훈련소를 다녀왔습니다

장병들과 함께한 행복한 연말...

43

논산의 지혜: 각!개!전!투!

• 오버로딩이 존재하는 케이스에 대해서만 특수화(Specialization)를 해주면 된다.

• 성공적으로 컴파일이 되었다

• 근데 이러면 ‘먼 과거’와 코드량이 비슷하지 않나요?• Async 함수 개수: 45개 → 6개 / AsyncJob 클래스 개수: 45개 → 1개

44

template <class T, class... Args>bool Async(void (T::*memFunc)(Args...), Args... args)

template <class T, class Arg1, class Arg2>bool Async(void (T::*memFunc)(Args1, Arg2), Arg1 arg1, Arg2 arg2)

(*) 콜스택 줄이기

• 과거엔 this의 타입을 추론하기 위해서 *_impl 함수를 만들었다.

template <class T, class... Args>bool AsyncOther(T* obj, void (T::*memFunc)(Args...), Args... args){

return AsyncOther_Impl(this, obj, memFunc, args...);}

template <class Owner, class T, class... Args>bool AsyncOther_Impl(Owner* owner, T* obj, void (T::*memFunc)(Args...), Args... args){

Job* job = tnew<AsyncOtherJob<Owner, T, Args...>>(owner, obj, memFunc, args...);return mJobQueue.Do(job);

}

45

(*) 콜스택 줄이기 with decltype

template <class T, class... Args>bool AsyncOther(T* obj, void (T::*memFunc)(Args...), Args... args){

Job* job = tnew<AsyncOtherJob<remove_pointer<decltype(this)>::type, T, Args...>>(obj, memFunc, args...);

return mJobQueue.Do(job);}

template <class _Ty>struct remove_pointer<_Ty*>{

Typedef _Ty type;}

46

1. Introduction2. 스마트포인터의 오남용과 Move Semantics3. Variadic Template and some pratical techniques4. 미래와 약속(Future-Promise)5. Q&A

47

리빙 포인트: Variadic Template을 적절히 활용하면 가독성을 높힐 수 있다.

C++ 11/14 비동기 키워드

• async, future, promise, packaged_task, thread...

• Cross Platform• 바퀴를 다시 만들지 않아도 되도록

• STL 내부의 알고리즘을 믿으며 써야한다.

• Future-Promise 패턴에 익숙하다면 어려울게 없다• 하지만 저도 처음 볼 땐 이해가 잘 안되었습니다.

48

std::thread

void Func(int a) { printf("%d", a); }

main()

{

thread t(&Func, 3);

thread t2([]{ printf(“Do you know Sae-dol Lee~?\n");} );

t.join();

t2.join();

}

49

std::promise / std::future

main()

{

promise<int> prom;

future<int> fut = prom.get_future();

auto t = thread([&]{ prom.set_value(1); });

cout << fut.get() << endl;

t.join();

}

50

std::promise / std::future

• 약속을 통해 미래라는 객체를 받을 수 있다.• future<int> fut = prom.get_future();

• 미래엔 언젠가 지켜질 약속의 결과가 담긴다.• prom.set_value(1);

• 약속이 언제 행해질진 모르지만 우리는 미래에 결과가 담겼는지 확인 할 수 있다.• fut.get()

• (미래라는 관념을 실체화 할 수 있다는 것에서 감동)

51

std::async

int foo(double k) { return k*0; }

main()

{

auto myFuture = async(launch::async, &foo, 1.0);

cout << myFuture.get() << endl;

}

• std::launch::async• 호출 즉시 비동기로 실행• 다른 쓰레드에서 실행되는 것이 보장

• std::launch::deferred• 나중에 값을 필요로 할 때 실행• Lazy Evaluation

52

그래서 future 패턴을 어디에 쓰는게 좋을까?

• 콜백 지옥으로부터 벗어나고 싶을 때• 순차적인 비동기 처리를 만들면 콜백 지옥이 만들어지곤 한다.• doIO1([..](Data io1Result){ doIO2([..](Data io2Result) { doIO3([..](Data io3Result){ .... }, io2Result)}, io1Result)}, io1Input)

• 싱글 쓰레드 로직 중간에 분산처리를 넣고 싶을 때

<Reference: Futures for C++11 at Facebook>53

Reference: “Futures for C++11 at Facebook”

async를 활용한 분산 작업 예제

original

bool result = true;

for(auto& job : jobList) {

if(job.Execute() == false)

result = false;

}

distribution

bool result = true;

vector<future<bool>> futures;

for(auto& job : jobList) {

futures.push_back(

async(launch::async, [&]{ job.Execute();}));

}

for(const auto& fut : futures) {

if(fut.get() == false)

result = false;

}

54

서버 데이터 로드 과정

Group 1

UserData..

UserSkillDataUserAppear

Group 4

NpcData

NpcPartyData

NpcSkillData

(NpcSkill_1.xml~

NpcSkill_101.xml)

Waiting…

Group 3

GuildData..

Waiting…

Group 2

ItemDataEnchantDataGachaData

..StoreData

Waiting…

• 각 쓰레드마다 한 그룹씩 배치• 종속성 때문에 더는 분산 시킬 수 없다.• 휴리스틱한 방법으로는 확실한 개선이 힘듦.

• NpcSkillData에 Future-Promise 패턴을적용해보면 어떨까?

55

std::async를 활용해서 프로토 타이핑

• NpcSkill 파일들을 각각 std::async를 통해서 읽도록 수정• 효과는 굉장했다!

• 하지만…

• 테라서버의 구조에 맞게 적당히 구현해봐야겠다!

56

서버 데이터 로드 과정 (수정)

Group 1

UserDataUserSkillDataUserAppear

Group 4

NpcData

NpcPartyData

Make Promise

NpcSkill_1.xmlNpcSkill_7.xml

…NpcSkill_101.xml

Group 3

GuildData

NpcSkill_5.xmlNpcSkill_9.xml

……

NpcSkill_100.xml

Group 2

ItemDataEnchantDataGachaData

..StoreData

Wait Future..NpcSkill_2.xml…

NpcSkill_99.xml

Waiting…Waiting… Waiting…

• Promise를 만들어 다른 쓰레드로 작업을 분산

• Future에 결과값이 다 모이면 다음 작업을 수행

• 종속성을 해치지 않고 분산작업이 가능!

57

구성원들이 익숙한 형태로 만들기

template<class Ret, class T, class Args...>

future<Ret> DoFutureWork(T* obj, Ret(T::*memFunc)(Args...), Args... args) { … }

template<class Ret, class T, class Args...>

struct FutureJob: Public Job {

FutureJob(T* obj, Ret(T::*memFunc)(Args...), Args... args) {

mFunc = [=]{ return (obj->*memFunc)(args...); };

}

void OnExecute() { mProm.set_value(mFunc()); }

future<Ret> GetFuture() { mProm.get_future(); }

private:

function<Ret(Args…)> mFunc;

promise<Ret> mProm;

}

58

DataManager::LoadGroup6

if(!LoadNpcData())

return -1;

if(LoadNpcSkillData())

return -1;

if(LoadDungeonData())

return -1;

DataManager::LoadNpcSkillData

bool result = true;

vector<future<bool>> futures;

for(auto& dataFile : fileList) {

futures.push_back(DoFutureWork(GDataManager,&DataManager::LoadSkillData, dataFile);

}

for(const auto& fut : futures) {

if(fut.get() == false)result = false;

}

DoFutureWork를 활용한 분산 작업 예제

59

개선 결과• Intel i7-4770, Windows Server 2012

• 최 팀장님: 어 나는 Before도 저렇게 빠르지 않았는데?

BUILD DATA Before(sec) After(sec)

DEBUG XML 150 77

DEBUG Binary 70 50

RELEASE XML 90 45

RELEASE Binary 24 17

60

1. Introduction2. 스마트포인터의 오남용과 Move Semantics3. Variadic Template and some pratical techniques4. 미래와 약속(Future-Promise)5. Q&A

61

리빙 포인트: 윈도우 피시가 느릴 땐 전원 관리 옵션을 살펴보자

Reference

• P8, 20~22: Writing Good C++14 - CPPCON2015, Bjarne Stroustrup (https://github.com/isocpp/CppCoreGuidelines/blob/master/talks/Stroustrup%20-%20CppCon%202015%20keynote.pdf)

• P13: 잡았다 요놈! - 황준호 작가님, 네이버, 인간의 숲(http://comic.naver.com/webtoon/detail.nhn?titleId=163295&no=15&weekday=tue)

• P16: 아서스 - Warcraft 3, Blizzard Entertainment

• P24: 더 이상 자세한 설명을 생략한다: 김성모 화백님, 나무위키(http://namu.wiki)

• P27: 착각 했던 것 같다 - 살아남아라 개복치!, SELECT Button inc.

• P35: 감독님 ... - 이노우에 다케히코, 대원씨아이, 나무위키(http://namu.wiki)

• P43: 논산훈련소 정문 - Newsis(http://www.newsis.com/)

• P41: 안돼 안바꿔줘 - SBS, 천종호 판사님, 나무위키(http://namu.wiki)

• P49: Why so asynchronous? - http://minocys.azurewebsites.net/guest-post/

• P54: Futures for C++11 at Facebook - Facebook Code (https://code.facebook.com/posts/1661982097368498/futures-for-c-11-at-facebook/)

62

Q & A

63

감사합니다.

64