Skip to content

Instantly share code, notes, and snippets.

@tnzk
Forked from hryk/multithreading_magic.md
Created August 3, 2010 14:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tnzk/506491 to your computer and use it in GitHub Desktop.
Save tnzk/506491 to your computer and use it in GitHub Desktop.

マルチスレッドの魔法

要旨

この文書ではPieter HintjensとMartin Sustrikが並列アプリケーションの構築の難しさとそれがエンタープライズコンピューティングにおいて何を意味するか、ということについて解説します。

The authors argue that a lack of good tools for software designers means that neither chip vendors not large businesses will be able to fully benefit from more than 16 cores per CPU, let alone 64 or more.

まず著者らはチップベンダでもなく巨大なビジネスでもないソフトウェアエンジニアが、コア数が64以上はもちろん、16以上のコアが入ったCPUの恩恵を受けられるよいツールが不足している事を主張します。

They then examine an ideal solution, and explain how the 0MQ framework for concurrent software design is becoming this ideal solution. Finally they explain 0MQ's origins, and the team behind it.

そして理想的な解決方法の検討を行い、並列ソフトウェアのデザインのためのフレームワークである0MQがどのようにその理想的な解決法となったのかを解説します。最後に、0MQの起源及びチームに関して説明します。

マルチコアへ

数年前まで、並列プログラミングは高性能計算(HPC)と同義であり、マルチスレッディングはワードプロセッサがあなたが文書を編集している間に同時に文書のページ番号の振り直しを行う為にしていることだった。 マルチコアCPUは希少かつ高価であり、ハイエンドなサーバにしか入っていなかった。我々はスピードを得る為にシングルコアのクロックサイクルを挙げ続け、CPUは熱くなり続けていた。

今日ではマルチコアCPUは日用品になっている。クロック数は2-3ギガヘルツで安定しており、一つのチップが載せるコアの数は18から24ヶ月で倍になっている。ムーアの法則はまだ当てはまる。データセンターの中から外へと、マルチコアCPUの広がりは続いている。ネットブックやポータブルデバイスは2から4コアのCPUを持っているし、ハイエンドなサーバは64コアを持っている。この成長は無限に続いていくだろう。

いくつかの要因がこの進化を加速している。第一に、CPUベンダへの競争の要求がある。我々がその力を使えようと使えまいと、我々はより能力の高いCPUを買うのを好むだろう。第二に、クロックサイクルが頭打ちした事だ。CPUの設計者は彼らにより競争力をもたらし、彼らの設計をスケールさせる次の道をマルチコアに見いだしている。 三つ目に、ローエンドにおけるアンドロイドのようなマルチタスクOSの広がりによって、コア数の増加がすなわちパフォーマンスの増加につながるという事が挙げられる。 そして最後に、ハイエンドにおいて、ブレードコンピューターのスロットが非常に高価である為に(ある投資銀行によれば1年あたり50000ドルと見積もられている)ユーザがブレード当たりよりコア数が多いものを求めるようになっているという事だ。

ここ数年、未来が大規模なマルチコアの時代となる事は明白であったが、ソフトウェア工業は遅れをとっている。 "High Performance on Wall Street 2008"イベントでは、スピーカーは誰も彼も同じ事を言っていた。曰く、「我々のソフトウェアはハードウェアの進歩についていく事ができていない」と。

最も広く利用されているC, C++は並列化のサポートを提供していない。プログラマー達はスレッディングAPIを使って自分たちでなんとかしている。 並列処理をサポートしているJavaやPython, .Net, Rubyのような言語では、総当たり方式でやられている。 言語の実装に依存している。1ダースを超えるRubyのインタプリタがあり、例えば、"グリーンスレッド"を提供しているかもしれないし、あるいは本当のマルチスレッディングかもしれない。ロックに頼っているから、スケールしない。 Erlangのように新奇に振り切れた言語は正しくやっている。我々はこれが意味する所を後で詳しく見ることになるだろう。

この文章の最後に、全ての人がErlangを使うようにすればよいと言う事はできるが、それは現実的な解ではないだろう。多くの賢いツールがそうであるように、これは本当に頭のいい開発者だけが使いこなせるものだろう。しかし多くの開発者、これから益々マルチスレッドアプリケーションを書く機会が多くなる開発者は、平均的だ。

これから、伝統的な方法の何がいけなかったのか、Erlangの何が正しいのか、そして我々はこうした教訓をどのように全てのプログラミング言語に適用できたかについて話そう。全ての平均的なプログラマの為の並列性について。

痛みを伴う最新式

A technical article from Microsoft titled "Solving 11 Likely Problems In Your Multithreaded Code" demonstrates how painful the state of the art is. The article covers .Net programming but these same problems affect all developers building multithreaded applications in conventional languages.

2008年10月、Microsoftは"マルチスレッド・プログラムにありがちな11の問題への対処法"という記事を発表した。「最新式の技術がいかに痛みを伴うか」を示したこの記事は、.NET Frameworkを利用した開発について述べたものだ。しかし実際のところ、一般的なプログラミング言語でマルチスレッド・プログラムを書く者は皆同じような問題を抱えている。

The article says, "correctly engineered concurrent code must live by an extra set of rules when compared to its sequential counterpart.". This is putting it mildly. The developer enters a minefield of processor code reordering, data atomicity, and worse. Let's look at what those "extra rules" are. Note the rafts of new terminology the poor developer has to learn.

この記事によれば「並列処理をきちんと書くには、逐次処理を書くのとは異なり、いくつかの制約を守る必要がある」。 これはかなり控え目な言い方で、「マルチスレッド・プログラムを書く」ということは、プロセッサによる演算順序の入れ替えや、データの原子性の維持、さらにはもっと恐ろしい罠が潜む地雷原に足を踏み入れるということだ。 ではここで、その「恐ろしい罠」がどんなものか見てみよう。 専門用語がたくさん出てくるので、知らないものはちゃんと調べること。

  • 同期忘れ: 複数のスレッドがひとつのデータを共有する場合、各スレッドは別のスレッドの処理に口出しをすることになる。このことは**「競合状態」**を引き起こす: 不可解な無限ループがはじまったり、フリーズしたり、データが化けたりする。これらはタイミングや負荷に依存して発生するため非決定性を帯びていて、デバッグはおろか再現することすら難しい。開発者はロックやセマフォを使うか、コードをクリティカル・セクションとするか、そういった対処に迫られることになる。そのため、共有データに安全にアクセスできるのは一度にひとつのスレッドだけ、ということになる。
  • 間違った粒度設定: 「共有メモリにアクセスするすべてのコードをクリティカル・セクションにする」という安易な対処だけでは、まだうまくいかない。クリティカル・セクションが大きすぎれば他のスレッドの動作を極度に遅くしてしまうし、小さすぎれば共有データの整合性を保つことができなくなる。
  • 過剰なメモリアクセス: 32bit/64bitの値の読み書きは大抵は原子性を保って行なわれるが、常にというわけではない。開発者はコードの中の何もかもをロックするかクリティカル・セクションにするかして回ることもできるものの、そうするとアプリケーションは遅くなる。効率の良いコードを書くには、システムのメモリ・モデルとコンパイラがそれをどう利用するかを学ぶ必要がある。
  • ロック・フリーと演算順序の入れ替え: 近年のマルチスレッド開発者はよく訓練されていて、コード内のロックの数を減らす方法を探すことに自信を持っている。こういうコードを「ロック・フリー」とか「省ロック」なコードと呼ぶ。だがコンパイラやCPUは、ひそかに命令の実行順序を入れ替えることができる。計算の効率を上げるために行なわれるこの措置は、命令は必ずしも一貫した順番どおりに実行されるとは限らないということを意味する。動作中のコードが急に中断されることもある。これに対する解決策は、"メモリバリア"を追加するか、プロセッサがメモリをどのように使うかをより深くまで学ぶことだ。
  • ロックの護送船団: あまりにも多くのスレッドが同じデータへのロックを要求すると、アプリケーション全体のパフォーマンスは低下し、ほとんど停止したような状態になってしまう場合がある。ロックという仕組みは――いくつものロックをコードに仕込んではじめてわかることだが――本質的にスケールしない。Just when we need things to work properly, they do not. ロックの回数を減らすか、「ホットなロック」を減らすようにコードを再構成する(つまり実際に発生するコンフリクトを減らす)、といったことのほかに現実的な対策はない。
  • 2ステップ・ダンス: waking状態とwating状態の間を移り変わることを繰り返すだけで、何の処理も実行しないスレッドのこと。シグナルの実装に依存して、さまざまな原因で発生する。我々開発者としては幸運なことに、どのような対処法も存在しない。これが原因でアプリケーションの動作が極度に遅くなったら、上司に「Nステップ・ダンスですね」といって肩をすくめるしかない。
  • 優先度の逆転: 低優先度のスレッドが高優先度のスレッドの動作を止めてしまうこと。Microsoftの記事によれば、「このお話の教訓は、スレッドの優先度の変更はできるだけ避ける、ということ」だ。

この一覧では、ロックの隠れたコストには言及していない―― コンテキスト・スイッチやキャッシュの有効性の確認がいかに劇的にパフォーマンスを低下させるかには。

Erlangを学ぶことがとても良いことのように思えてきたはずだ。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment