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

64
TERA 서버의 Modern C++ 활용기 Bluehole 이상헌 1

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

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

TERA 서버의Modern C++ 활용기

Bluehole

이상헌

1

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

목차

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

2

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

발표자 소개

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

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

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

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

• Contact• [email protected]

3

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

The Exiled Realm of Arborea

Free-Targeting Action MMORPG

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

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

4

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

발표하기 앞서

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

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

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

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

5

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

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

6

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

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

필요한 사전 지식

• 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

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

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의 증가 연산이 많이 일어나기 때문

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

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!

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

사례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

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

사례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

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

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

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

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

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

12

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

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

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

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

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

13

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

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

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

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

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

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

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(?)

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

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

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

개선 후 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번 케이스보다 낮음

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

개선 후 Sample Rate 변화 (3N)

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

• Reallocation의 Sample Rate 변화

Before(14.8%)

After(2.8%)

19

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

(*) 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

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

(*) 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

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

(*) 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

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

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

23

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

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

사전 지식: Template in C++

• template <typename T>class Stack;{

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

}

• Stack<int> mStack;

24

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

‘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

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

‘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

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

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

‘tnew’ in TERA Server(먼 과거)

27

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

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

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

당연히 될 것 같은 것들

• 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

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

#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

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

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

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

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

끝판왕에 도전#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

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

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

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

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

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

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

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

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

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

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

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

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

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

(*) 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

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

완성?! ?! ?!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

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

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

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

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

해결법?

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

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

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

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

43

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

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

• 오버로딩이 존재하는 케이스에 대해서만 특수화(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)

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

(*) 콜스택 줄이기

• 과거엔 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

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

(*) 콜스택 줄이기 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

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

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

47

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

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

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

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

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

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

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

48

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

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

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

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

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

std::promise / std::future

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

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

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

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

51

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

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

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

그래서 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”

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

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

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

서버 데이터 로드 과정

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

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

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

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

• 하지만…

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

56

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

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

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

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

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

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

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

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

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

개선 결과• 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

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

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

61

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

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

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

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

Q & A

63

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

감사합니다.

64