36
並行・並列への C++ としてのアプローチ

Boost9 session

Embed Size (px)

Citation preview

並行・並列へのC++としてのアプローチ

自己紹介

@Flast_RO

http://ftp2.jp.vim.org/pub/vimとかミラーしてても金は降ってこないので転がり込む先を探してる初めて触ったのは巫女でしたCryoliteの敵

C(ryの敵

Agenda

並行と並列、スレッドとかBoost.Context

C++標準委員会での動き

並行と並列の違い

並行 (concurrent):複数の処理単位が論理的に独立して動作すること並列 (parallel):実際に複数の処理単位が同時に動作している様子

ほとんどの場合混同されてるし、大方通じるので問題な… …いのでは ?

プロセスとスレッド

どちらも少なくとも並行を実現最近のマルチコア環境ならコアの数だけプロセスもスレッドも並列に走るようにOSがしてくれる

プロセスは独立したプロセス空間を持つが、スレッドはスレッド固有のリソース以外はプロセス内で共有スケジューリングはOSが担当プラットフォーム依存のAPIを使用しないといけないプロセス: fork, CreateProcess ...

スレッド: POSIX Thread, CreateThread ...

はー、またプラットフォーム依存 ...

sasso-sasso-

Boost

プロセスInterprocess, Process(sandbox) ...

スレッドThread, InterThreads(sandbox) ...

その他Asio, MPI ...

Boost

プロセスInterprocess, Process(sandbox) ...

スレッドThread, InterThreads(sandbox) ...

その他Asio, MPI ...

Boost.Thread v2

1.50.0から v1は提供されず v2に完全移行(予定)C++11 complianceが中心Move semanticsが Boost.MoveベースになるBoost.DateTimeベースの this_thread::sleepに加え

Boost.Chronoベースの this_thread::sleep_(for|until)

Boost.Thread v3

1.55.0から v3へ移行予定1.50.0 から 1.54.0は v3への移行期間#define BOOST_THREAD_VERSION 3

マクロで互換性等を細かく設定できるe.g. #define BOOST_THREAD_DONT_PROVIDE_FUTURE

ifdef → boost::unique_future

else → boost::future

ところで、スレッドにも種類がある

カーネルスレッドとユーザースレッド

スケジューリングを誰がやるかが一番のポイントカーネルスレッドは知らないうちに実行コンテキストが変わっているユーザースレッドはプログラマが明示的にコンテキストスイッチを仕込まなければならない

ユーザースレッド単体では並行しかできないが、カーネルスレッドは並列に動作する可能性高いユーザースレッドのコンテキストスイッチは自分でアセンブリを書く必要がある

ユーザースレッドの目的

カーネルスレッドはコンテキストスイッチのコストが非常に大きいスケジューリングする必要がある場合一連の流れを細切れにしてコールバックとかにするより処理の流れがわかりやすい

Agenda

並行と並列、スレッドとかBoost.Context

C++標準委員会での動き

Boost.Context

ユーザーレベルでのコンテキストスイッチ機構を提供するライブラリ作者はOliver Kowalke

http://ok73.ok.funpic.de/boost/libs/context/doc/html/

現在サポートされている triple

上記ドキュメントの Tested Platformsの項を参考にする/trunk/libs/context/src/asm/ にあるファイルから推測する

Boost.Context

mini-review時点ではコンテキストに関する高級な処理があったが trunkに入ってから殆どの機能が削られたe.g. context linking / stack unwinding / high-level API

1.50.0のリリースには含まれないことになりそう現に release branchからは削除されている

ctx::fcontext_t orig, fc;

void f(intptr_t){ std::cout << "in context" << std::endl; ctx::jump_fcontext(&fc, &orig, 0);}

int main(){ const size_t ctxsize = ctx::default_stacksize();

ctx::stack_allocator alloc; fc.fc_stack.base = alloc.allocate(ctxsize); fc.fc_stack.limit = static_cast<char *>(fc.fc_stack.base) - ctxsize; ctx::make_fcontext(&fc, f);

std::cout << "pre jump" << std::endl; ctx::jump_fcontext(&orig, &fc, 0); std::cout << "post jump" << std::endl;

alloc.deallocate(fc.fc_stack.base, ctxsize);}

ちょっとした注意

TLSは使ってはいけない多分 SEGVとかそういう話だと思う

スタックのデアロケートを自動化しようとコンテキスト内で開放してはいけないDeallocateが戻ると SEGV

コンテキスト内ではスコープを意識して使わないと dtorが呼ばれないコードになってしまうエントリーポイントをそのまま出ると exitが呼ばれる

で、結局何が嬉しいんだろう

ユーザースレッドの目的

カーネルスレッドはコンテキストスイッチのコストが非常に大きいスケジューリングする必要がある場合一連の流れを細切れにしてコールバックとかにするより処理の流れがわかりやすい

IOレイテンシの隠蔽

ユーザースレッドの目的

カーネルスレッドはコンテキストスイッチのコストが非常に大きいスケジューリングする必要がある場合一連の流れを細切れにしてコールバックとかにするより処理の流れがわかりやすい

IOレイテンシの隠蔽

今回はこれを例にとる

で、結局何が嬉しいんだろう

→ statefulな操作は stateを持ち回る必要がなくなる

簡単な例

一定間隔でタイマイベントが発生する毎回少しづつ変化させたい例えば 3回で 1周

後々全く違う処理を同じイベントで行うかもしれないから callbackのインターフェースは共通化して vectorとかに突っ込みたい

正直こんな例だとあんまり利点を見いだせないかも ...

int state = 0;void timer_handler(){ switch (state) { case 0: ... process step 0 ... break; case 1: ... process step 1 ... break; case 2: ... process step 2 ... break; } state = (state + 1) % 3;}

function<void()> callback_timer;callback_timer = timer_handler;

while ((event = peek_event()) != FINISH){ switch (event) { ... snip ...

case TIMER: callback_timer(); break;

... snip ... }}

例えばコールバックベースだと

呼ばれる毎に共有できる形でどこかに

stateを保持しないといけな

timer_handlerを複数登録できるようにするには ...

void timer_handler(shared_ptr<int> ps){ switch (*ps) { case 0: ... process step 0 ... break; case 1: ... process step 1 ... break; case 2: ... process step 2 ... break; } *ps = (*ps + 1) % 3;}

vector<function<void()>> timer_callbacks;… snip...timer_callbacks.push_back( bind(timer_handler, make_shared<int>(0));… snip...while ((event = peek_event()) != FINISH){ switch (event) { ... snip ... case TIMER: for (auto &c : timer_callbacks) c(); break; ... snip ... }}

例えばコールバックベースだと

shared_ptr等で固有の

状態を持ち回る必要が

ユーザースレッドを導入するとfcontext_t fctx;// stackとか make_fcontextとか

vector<fcontext_t> timer_callbacks;… snip...timer_callbacks.push_back(move(fctx));

while ((event = peek_event()) != FINISH){ switch (event) { ... snip ... case TIMER: for (auto &ctx : timer_callbacks) jump_fcontext(&fo, &ctx, &ctx); break; ... snip ... }}

void timer_handler(fcontext_t *ctx){ while (true) { ... process step 0 … ctx = jump_fcontext(ctx, &fo, 0);

... process step 1 … ctx = jump_fcontext(ctx, &fo, 0);

... process step 2 … ctx = jump_fcontext(ctx, &fo, 0); }}

シングルスレッドの処理の

流れと同じように書ける

固有の状態はコンテキスト

自身が持ってるのでコンテキストの管理だけ

行えば良い

しかし fcontext_tを直接扱うのはやりづらい ...

高級な実装

正直Boost.Contextは抽象化されてるとはいえ常用するには低級すぎる現在Oliverが Boost.Contextをベースとした

Boost.Coroutineを提案して Formal Review待ちhttp://ok73.ok.funpic.de/boost/libs/coroutine/doc/html/index.html

GSoC2006で Eric Nieblerが書いた Boost.Coroutineと基本インターフェースは同じ

ctx::fcontext_t orig, fc;

void f(intptr_t){ std::cout << "in context (pre jump)" << std::endl; ctx::jump_fcontext(&fc, &orig, 0); std::cout << "in context (post jump)" << std::endl; ctx::jump_fcontext(&fc, &orig, 0);}

int main(){ const size_t ctxsize = ctx::default_stacksize();

ctx::stack_allocator alloc; fc.fc_stack.base = alloc.allocate(ctxsize); fc.fc_stack.limit = static_cast<char *>(fc.fc_stack.base) - ctxsize; ctx::make_fcontext(&fc, f);

std::cout << "pre jump" << std::endl; ctx::jump_fcontext(&orig, &fc, 0); std::cout << "now yield" << std::endl; ctx::jump_fcontext(&orig, &fc, 0); std::cout << "post jump" << std::endl;

alloc.deallocate(fc.fc_stack.base, ctxsize);}

Boost.Context

最後に jumpを忘れてはいけないし、トップレベルの dtorは呼ばれない

typedef coro::coroutine<void()> coro_t;

void f(coro_t::self &self){ std::cout << "in coroutine (pre yield)" << std::endl; self.yeild(); std::cout << "in coroutine (post yield)" << std::endl;}

int main(){ coro_t c(f);

std::cout << "pre jump" << std::endl; c(); std::cout << “now yield” << std::endl; c(); std::cout << "post jump" << std::endl;}

Boost.Coroutine

最後に yieldを呼ぶ必要はないし dtorも呼ばれる

Agenda

並行と並列、スレッドとかBoost.Context

C++標準委員会での動き

C++標準委員会での動き

Study Group 1

並行と並列に関する Study Group

n3328 Resumable Functions

言語仕様としてユーザースレッドを導入する提案resumableと awaitの 2つの contextual keywords

n3356 C++ Mutable Threads

Mutable Threads の提案

n3361 C++ Language Constructs for Parallel Programming

Intel® CilkTM Plus の紹介 (提案は次のmeetingらしい

C++標準委員会での動き

n3378 A preliminary proposal for work executors

Workerを投げると適当にスケジューリングして裏で実行するexecuterの提案

Workerの戻り値は投げた時に返ってくる futureで取得

しかしこれらが実際に入るのは早くとも C++2x頃なのでは

まとめ

Boost.Contextがコンテキストスイッチを抽象化しているのでアセンブリとか知らなくても何か遊べそう実際のプロダクトに使用されると面白そう

C++標準でも並行・並列の関心は高まっているが、投入はまだ先

就職先を考えねばならない

Question?