[ndc2015] c++11 고급 기능 - crow에 사용된 기법 중심으로
TRANSCRIPT
Crow Framework 을 작성하기 위해 사용한 기법들을 소개 (TMP 중심 )
auto, lambda 등 일반적인 C++ 개발에서 접하기 쉬운 기법들에 대한 설명은 생략
목표
목표
Crow
C++ Framework for writing Web server
http://github.com/ipkn/crow
GitHub Star 1251 개Hacker News vote 194
https://news.ycombinator.com/item?id=8002604
Web server 개념
브라우저로 접속해서 요청을 보내면해당 요청에 맞는 응답을 보내줌
GET / HTTP/1.1Host: www.example.com
HTTP/1.1 200 OKContent-Length: 12...
Hello World!
간단한 Crow 예제#include “crow_all.h”
int main() { crow::SimpleApp app; CROW_ROUTE(app, “/”) ([]{ return “Hello world!”; }); app.port(8080).run(); return 0;}
… as Flask (from Quickstart)
from flask import Flaskapp = Flask(__name__)
@app.route('/')def hello_world(): return 'Hello World!'
if __name__ == '__main__': app.run()
목차
RoutingCompile timeRun time
Middleware서버 구조
소개될 C++11 기능
template metaprogrammingconstexprvariadic templateSFINAEdecltype, declvalstd::chrono, operator ””, enum class
왜 굳이 C++ 로 ?https://github.com/ipkn/crow-benchmark
C++?
빠른 속도멀티 패러다임 언어
절차형 + OOP + Generic (template)
template
compile time code generation
vector<T> →vector<int>, vector<string>
T 는 컴파일 타임에 결정되어야 한다 .
Quick Review - auto, lambda, move
auto get_answer = []{ return 42; };auto x = get_answer();
SomeBigData f() { … }v.push_back(f());
Crow 클래스 구조
AppServer
ConnectionRouter
Rule (TaggedRule)Trie
Routing
Routing - CROW_ROUTE
어떤 요청에 어떤 응답을 줄지 지정
CROW_ROUTE(app, “/”)CROW_ROUTE(app, “/post/all”)CROW_ROUTE(app, “/post/<int>”)CROW_ROUTE(app,
“/near/<double>/<double>”)
CROW_ROUTE 목표
쓰기 편하게실수하기 어렵게
잘못된 핸들러를 주면 에러나게빠르게실제 함수 호출 시 오버헤드를 최소화
CROW_ROUTE 기능 (1)
여러 타입의 인자 지원
<int><uint><double><str><path>
CROW_ROUTE 기능 (2)
컴파일 시간에 핸들러 타입 체크
CROW_ROUTE(app, “/post/<int>”)([](string a) … ([](int a, int b) … ([](int a) ...
CROW_ROUTE 기능 (3)
여러 종류의 핸들러 지원
[](…)[](const request& req, …)[](const request& req, response& res, …)
* 어떻게 여러 종류를 지원하는지에 관해선 나중에 설명 예정
실제 CROW_ROUTE 구현
#define CROW_ROUTE(app, url) app.route< crow::black_magic::get_parameter_tag(url) >(url)
black_magic ???!!!
이름을 말할 수 없는 그분 (...)
다음 슬라이드부터 지옥의 내용이 펼쳐진다는 소문이 있습니다
(?)
app.route<get_parameter_tag(url)>(url)
핸들러 형태 체크 → 컴파일 타임 → 템플릿 인자실행 중에 URL 체크 → 런타임 → 함수 인자
실제 CROW_ROUTE 구현 (2)
app.route<get_parameter_tag(url)>(url)
핸들러 형태 체크 → 컴파일 타임 → 템플릿 인자실행 중에 URL 체크 → 런타임 → 함수 인자
실제 CROW_ROUTE 구현 (2)
app.route<get_parameter_tag(url)>(url)
핸들러 형태 체크 → 컴파일 타임 → 템플릿 인자실행 중에 URL 체크 → 런타임 → 함수 인자
실제 CROW_ROUTE 구현 (2)
app.route<get_parameter_tag(url)>(url)
스트링 값 (“abc” 등 ) 은 템플릿 인자로 사용할 수 없음
url 로 부터 인자에 대한 정보를 얻어템플릿 인자로 넣을 수 있는 값으로 변환
템플릿 인자부터 ..
constexpr function
constexpr int fact(int n) { return (n <= 1)
? 1 : fact(n-1)*n;}
char arr[fact(5)];
constexpr
컴파일 타임에 계산할 수도 있는 함수 또는 값
cin >> n; cout << fact(n); // 런타임
fact(5) == 120, 컴파일 타임에 계산 가능 → 배열의 크기 , 템플릿 인자 등으로 사용 가능
string literal 의 컴파일 타임 해쉬 계산
get_parameter_tag
URL 을 받아 인자 정보를 가지는 숫자로 변환
인자가 없는 경우 0URL 인자는 5 종류 : <int>=1 <uint>=2 …6 진법으로 표현
( 자세한 구현은 생략 )
get_parameter_tag
“/”“/post”“/post/<int>”“/near/<int>/<int>”“/put_score/<double>”“/wiki/<path>”
0011135
숫자에서 다시 타입 리스트 복원
0 → <>1 → <int>11 → <int, int>13 → <double, int>4 → <string>5 → <string> * path 도 타입은 string
black_magic::S
variadic template 으로 타입 리스트를 구현S<int, double, string> 이 가능 S<a,b>::push<c> === S<c, a, b>S<a>::push_back<b> === S<a, b>S<a,b,c>::rebind<Rule> === Rule<a,b,c>
black_magic::S<T…> 구현 (1)
template <typename ... T> struct S {
template <typename U> using push = S<U, T...>;
S<a,b>::push<c> === S<c, a, b>
* using A = B; === typedef B A;
black_magic::S<T…> 구현 (2)
template <typename ... T> struct S {
template <template <typename ...> class U>using rebind = U<T...>;
S<a,b,c>::rebind<Rule> == Rule<a,b,c>
black_magic::arguments<uint64_t>
get_parameter_tag 값을 타입 리스트로
arguments<0>::type == S<>arguments<3>::type == S<double>arguments<11>::type == S<int, int>
arguments 구현
template <uint64_t Tag> struct arguments { using subarg = typename arguments<Tag/6>::type;
using type = typename subargs::template push< typename single_tag_to_type<Tag%6>::type >;};
실제로 일어나는 일
argument<31>subarg = arg<3>type = subarg::push<int>
실제로 일어나는 일
argument<31>subarg = S<double>type = S<int, double>
arg<3>subarg = arg<0> = S<>type = S<>:push<double> =
S<double>
partial template specialization
template <int N>struct single_tag_to_type {};
template <>struct single_tag_to_type<1> {
using type = int64_t;};// C++11 은 아니지만 ..
arguments 구현
template <> struct arguments<0> { using type = S<>;};
URL 이 라우팅 규칙이 되기까지
get_parameter_tag(“/<int>/<int>”)→ arguments<11>→ S<int, int>→ TaggedRule<int, int>
Router vector<unique_ptr<BaseRule>> rules_;
Q&A for Routing 1
get_parameter_tag(“/<int>/<int>”)→ arguments<11>→ S<int, int>→ TaggedRule<int, int>
실제 핸들러 호출하기
Routing 2
남은 문제
/v<uint>/<string>/<path>“/v2/wiki/example/document”
TaggedRule<uint, string, string>
handler[](uint32_t ver, string func, string path)
Rule, TaggedRule
최종적으로 호출될 핸들러를 가지고 있음app.route 의 리턴 값
void handle( const request& req, response& res, const routing_params& params)
Trie based routing
여러 rule 들의 URL 을 모아서요청이 들어왔을 때 어느 Rule 에서 처리할 지 판단
트리 구조로 성능 향상
Trie example
GET statuses/home_timelineGET statuses/retweets/:idGET statuses/retweets_of_mePOST statuses/retweet/:id
(from Twitter REST API)
statuses/
retweethome_timelin
e
s/
_of_meuint
uint
/
GET statuses/home_timelineGET statuses/retweets/:idGET statuses/retweets_of_mePOST statuses/retweet/:id
Trie
add( string url, rule )find( string url )
-> rule, routing_params
http://en.wikipedia.org/wiki/Trierouting.h
routing_params
vector<int> int_params;vector<uint> uint_params;vector<double> double_params;vector<string> string_params;
Trie example (1)
/near/<double>/<double>“/near/37.1/10.1”
routing_params.double_params == {37.1, 10.1}
Trie example (2)
/v<uint>/<string>/<path>“/v2/wiki/example/document”
routing_params.uint_params == {2}.string_params ==
{“wiki”, “example/document”}
핸들러 호출하기
/v<uint>/<string>/<path>[](unsigned int, string, string)
handler(rp.uint_params[0], rp.string_params[0], rp.string_params[1])
compile time
호출부 구현template <typename CP,
int NInt, int NUint, int NDouble, int NString, typename S1, typename S2>struct call;
S1 - URL 로 부터 정의된 핸들러 인자 타입의 리스트S2 - 호출을 위한 타입 + 인덱스 정보의 리스트 N?? - 인자 처리를 위한 인덱스 값들 , CP - 호출
파라미터
TaggedRule<Args…>::handle
void handle(req, res, routing_params) {call<CallParams, 0, 0, 0, 0, S<Args…>, S<>>(
call_params);}
call_paramsreq + res + routing_params + handler
call 호출 과정 - /v<uint>/<string>/<path>
0 0 [uint, str, str] []1 0 [str, str] [(uint, 0)]1 1 [str] [(uint, 0) (str, 0)]1 2 [] [(uint, 0) (str, 0) (str, 1)]
call 을 재귀호출
NUint NStr S1 S2
call 호출 과정 - /v<uint>/<string>/<path>
0 0 [uint, str, str] []NUint NStr S1 S2
call 호출 과정 - /v<uint>/<string>/<path>
0 0 [uint, str, str] []
→ 1 0 [str, str] [<uint, 0>]
NUint NStr S1 S2
struct call<CP, NInt, NUint, NDouble, NString, S< int64_t, Args1...>, S<Args2...>> { void operator()(CP cp) { using pushed = typename S<Args2...>:: template push_back<call_pair<int64_t, NInt>>; call<CP, NInt+1, NUint, NDouble, NString, S<Args1...>, pushed>()(cp); }};
call 호출 과정 - /v<uint>/<string>/<path>
0 0 [uint, str, str] []1 0 [str, str] [<uint, 0>]
→ 1 1 [str] [<uint, 0> <str, 0>]
NUint NStr S1 S2
call 호출 과정 - /v<uint>/<string>/<path>
0 0 [uint, str, str] []1 0 [str, str] [<uint, 0>]1 1 [str] [<uint, 0> <str, 0>]
→ 1 2 [] [<uint, 0>, <str, 0>, <str, 1>]
NUint NStr S1 S2
struct call<CP, NInt, NUint, NDouble, NString,S<>, S<Args2...>> { void operator()(CP cp) { // 일부 생략 cp.res = cp.handler( cp.routing_params.template get <typename Args2::type>(Args2::pos) ... ); cparams.res.end(); return;
T routing_params::get<T>(index)
template<> string routing_params::get<string>(index) const {
return string_params[index]; }
핸들러 호출하기handler(rp.uint_params[0], rp.string_params[0], rp.string_params[1]);
handler(rp.get<uint>(0), rp.get<string>(0), rp.get<string>(1));
handler( rp.get<Args2::type>(Args2::pos)... )
핸들러 호출하기Args… = <uint, 0>, <str, 0>, <str, 1>
handler( rp.get<Args::type>(Args::pos)... )
<uint,0> 을 대입한 경우를 보면 = rp.get<uint>(0)
... 을 통해서 반복되므로rp.get<uint>(0), rp.get<str>(1), rp.get<str>(2)
struct call<CP, NInt, NUint, NDouble, NString,S<>, S<Args2...>> { void operator()(CP cp) { // 일부 생략 cp.res = cp.handler( cp.routing_params.template get <typename Args2::type>(Args2::pos) ... ); cparams.res.end(); return;
Variadic template from example
void print(A a, B b, C c, …) 모든 인자를 출력해주는 함수
print() print(1) - 1print(2, “hello”, 40) - 2hello40
print 구현void print() {}template <typename A, typename ... Args>void print(A a, Args... args){ cout << a; print(args...);}
print(2, “hello”, 40) 실행
A = intArgs… = const char*, intargs… = “hello”, 40
cout << 2;print(“hello”, 40);
print 실행
print<int, const char*, int>(2, “hello”, 40) - 2print<const char*, int>(“hello”, 40) - helloprint<int>(40) - 40print()
2hello40
Variadic template 대입의 예
args… = 1, 5, 10
(args+1)… = 1+1, 5+1, 10+1func(args)… = func(1), func(5), func(10)func(args…) = func(1, 5, 10)
Q&A for Routing 2
TrieRouting parameterVariadic template
Middleware
Middleware?
C 요청 -> S 응답 -> C 받음
요청 --[MW]--> 응답 --[MW]--> 받음
before_handle(req, res)after_handle(req, res)
Example Middleware
Static page serving (cache control)Cookie parsing, Session supportJSON parsingProfiling, Logging
Middleware of express.js (Node.js)
app.use(function (req, res, next) { req.cookies = parse_cookies(req); next();});
// req에 임의로 변수추가 가능
Middleware 구현의 제약들
근데 우린 C++ 이잖아… 변수는 다 선언되어야 unordered_map<string, any> ???
+ express 의 문제점 : Dependency 세션은 사용하는데 쿠키파서가 없는 경우
이 문제를 어떻게 해결하면 좋을까 ?
각 MW 별로 저장해야할 값들이 존재어떤 MW 는 다른 MW 의 값을 참조해야 한다
(C++ 이니까 )필요한 MW 가 빠진 경우 컴파일 에러가 발생해야함
struct MW::contextMW 가 필요한 값을 저장하는 struct내용에 특별한 제한이 없음
App<A, B, C> app;A, B, C 세 MW 를 사용하는 app
SimpleApp == App<>
Crow 에서의 해결 방식
ctx_A : public A::contextctx_AB : public ctx_A, public B::contextctx_ABC : public ctx_AB, public C::contextcontext : public ctx_ABC
실제로 ctx_AB === partial_context<A, B>
context 체인 상속 - App<A, B, C>
Crow 에서의 해결 방식template <typename AllContext>before_handle(req, res, context& ctx,
AllContext& all_ctx)
from MW - all_ctx.get<A>()from handler -
app.get_context<A>(req)
Crow 에서의 해결 방식 - 전체 호출 과정A::before_handle(req, res, ctx_A)B::before_handle(req, res, ctx_AB)C::before_handle(req, res, ctx_ABC)app.handle(req, res)C::after_handle(req, res, ctx_ABC)B::after_handle(req, res, ctx_AB)A::after_handle(req, res, ctx_A)
Dependency 해결 (1)
Session MW 는 Cookie MW 에 의존한다면
app<Cookie, Session> - OKapp<Session> - errorapp<Session, Cookie> - error
의존 : all_ctx.get<Cookie> 를 사용한다 .
Dependency 해결 (2)
App<A, B, C> app;A::before_handle(req, res, ctx_A)B::before_handle(req, res, ctx_AB)C::before_handle(req, res, ctx_ABC)app.handle(req, res)C::after_handle(req, res, ctx_ABC)B::after_handle(req, res, ctx_AB)A::after_handle(req, res, ctx_A)
Dependency 해결 (3)
ctx_A :: get<A> - OKctx_A :: get<B> - error
ctx_AB :: get<A> - OKctx_AB :: get<B> - OK
ctx_AB::get
template <typename T>typename T::context& get() { return static_cast<typename T::context&>(*this);}
상속한 MW 의 context 타입으로만 캐스팅 가능
다른 MW 과 연관이 없는 MW 라면 ?
template <typename AllContext>before_handle(req, res,
context& ctx, AllContext& all_ctx)
대부분의 경우before_handle(req, res, context&
ctx)
SFINAE
Substitution failure is not an error.
템플릿 인자를 대입하는 과정에서 발생하는 에러는 에러로 간주하지 않는다 . ( 모두 실패하는 경우에는 에러 )
또는 이를 이용한 프로그래밍 기법을 지칭함
SFINAE 예제template <typename T>true_type has_iterator_checker(typename T::iterator *); template <typename T>false_type has_iterator_checker(...);
decltype(has_iterator_checker<int>()) === false_typedecltype(has_iterator_checker<string>()) === true_type
template <typename MW>struct check_before_handle_arity_3{ template <typename T, void(T::*)(request&,response&,MW::context&)
= &T::before_handle
> struct get {};};
template <typename T>struct is_before_handle_arity_3_impl{ template <typename C> static std::true_type f(check_before_handle_arity_3<T>::get<C>*);
template <typename C> static std::false_type f(...);
public: static const bool value = decltype(f<T>(nullptr))::value;};
std::enable_if
SFINAE 를 이용하기 편하게 만들어둔 도구조건부로 구현을 선택template <typename T>enable_if<is_integral<T>::value> func(T x) …
handler 형태를 고를 때 사용 ( 생략 )http://en.cppreference.com/w/cpp/types/enable_if
CookieParser - Example Middleware
CookieParser::contextuo_map<string, string> jar,
cookies_to_add;get_cookie(key)set_cookie(key, value)
before - 파싱 + jar 에 저장after - Set-Cookie 헤더 추가
Q&A for Middleware
App<A, B, C> app;
context inheritance chainMiddleware call chain
CookieParser
SFINAE
Crow 서버 구조
고려해야 할 점
멀티 쓰레드 작업 배분Connection 객체의 생성 , 소멸 관리HTTP 타임 아웃 (Keep-Alive)
초기 버전
boost::asio + joyent/http-parser
asio::io_service 하나concurrency 개수 만큼 io_service.run()shared_ptr<Connection> 으로 수명 관리asio::deadline_timer 로 타임아웃 처리
초당 처리 횟수가 100,000 이상 ..
shared_ptr 의 atomic 연산이 성능 부하deadline_timer 내부 자료구조 업데이트 부하
여러 개의 싱글 쓰레드 서버의 결합
N 개의 작업용 쓰레드 (asio::io_service ✕ N)
요청이 들어올 때마다 어느 쓰레드가 처리할지 RR 로 분배
해당 요청에 대한 모든 처리는담당한 쓰레드 안에서만 이루어짐
deadline_timer 성능 저하 문제
각 쓰레드 별로 thread_local 한 타이머를 구현관련 코드 : dumb_timer_queue.h
shared_ptr 제거
Connection 객체를 하나의 쓰레드가 담당atomic 연산 불필요
변경 후 성능 4 배 향상쓰레드간 통신 전혀 없음
* shared_ptr 을 쓰지말란 얘기는 아닙니다 .
그 외 사용된
C++11 기능들 소개
std::chrono
std::chrono::system_clocksteady_clockhigh_resolution_clock
( 거의 ) 같은 인터페이스 제공
std::chrono 사용법
auto t1 = clock::now();do_something();auto t2 = clock::now();
if (t2 - t1 > seconds(10)) …duration_cast<seconds>(t2 - t1).count()
system_clockto_time_t, from_time_t 제공
steady_clock무조건 증가함이 보장됨
high_resolution_clock제일 높은 해상도를 제공
( 가능한 짧은 시간 간격 )
operator “”
C++11std::chrono::seconds(10)std::chrono::minutes(20)std::string(“hello”)C++1410s20min“hello”s
POST request
CROW_ROUTE(app, “/update_score”)
.methods(“POST”_method)
([](const crow::request& req){ auto json = crow::json::load(req.body); ...});
operator “” _method
constexpr crow::HTTPMethod operator "" _method(const char* str, size_t len)
{return
crow::black_magic::is_equ_p(str, "GET", 3) ? crow::HTTPMethod::GET :
…};
enum class
enum class HTTPMethod{
GET,POST,
...};
int m = HTTPMethod::GET; // error!
decltype, declval<T>
decltype(X)X 의 선언형
declval<T>()컴파일 타임에만 사용할 수 있는 T 의 객체형
template <typename A, typename B>auto add(A a, B b) -> decltype(a+b){ return a+b; }
template <typename A, typename B>decltype(declval<A>() + declval<B>()) add(A a, B b)
C++14: return type deduction
< 끝 >( 내용 발표는 )
C++14 - minor patch to 11
Function return type deduction Relaxed constexpr Variable template Lambda capture expression Generic lambda auto lambda = [a = 3](auto x, auto y) {return a + x + y;};
C++14 - minor patch to 11
그외 …01010b, 1’234’5678’9 == 123456789make_unique...
C++14 - minor patch 는 훼이크
auto p = [](auto& s, auto...x){[](...){}((s<<x,1)...);};p(cout, 42, "hello?");
위 코드의 실행 결과를 설명하시오 . (3 점 )
VS2015 RC C++11 지원http://blogs.msdn.com/b/vcblog/archive/2015/04/29/c-11-14-17-features-in-vs-2015-rc.aspx
ref-qualifiers( 멤버함수 뒤 &&)
Inheriting constructors
u8" 한글 ", char16_t , char32_t
struct B1 { B1( int ); }; struct D1 : B1 { using B1::B1; };