本記事は『なっとく! 並行処理プログラミング』の「第1章 並行処理」から抜粋したものです。掲載にあたって編集しています。
窓の外を見て、まわりにあるものをよく観察してみよう。そこから見えるものが直線的に、逐次的に動いているように見えるだろうか。それとも、独立して動作するさまざまなものが、複雑に絡み合って、同時に動いているように見えるだろうか。
人々は逐次的に考える傾向にある──TO-DOリストを順番にチェックしたり、物事を一度に1つずつ進めたりする。しかし、現実世界はもっとずっと複雑であり、逐次というよりはむしろ並行で、相互に関連する事象が同時に発生する。
混雑するスーパーマーケットの無秩序な慌ただしさから、サッカーチームの見事な連携プレー、刻々と変化する車の流れまで、並行処理は私たちのまわりにいくらでもある。自然界と同じように、複雑な現実の現象をモデル化し、シミュレートし、理解するには、コンピュータを並行化しなければならない。
コンピューティングを並行化すると、システムが一度に複数のタスクに対処できるようになる。その対象となるのはプログラムかもしれないし、コンピュータかもしれないし、コンピュータのネットワークかもしれない。並行コンピューティングがない場合、アプリケーションが周囲の世界の複雑さについていくことはできないだろう。
並行処理というトピックを詳しく調べていくと、いくつかの疑問が浮かぶかもしれない。何よりもまず、まだ納得がいかないかもしれない──なぜ並行処理を意識しなければならないのだろうか。
並行処理はなぜ重要か
並行処理はソフトウェアエンジニアリングに欠かせない。並行処理プログラミングはハイパフォーマンスアプリケーションや並行処理システムに必要であり、ソフトウェアエンジニアにとって決定的に重要なスキルとなっている。
並行処理プログラミングは新しい概念ではないが、近年大きな注目を集めている。現代のコンピュータシステムでは、コアとプロセッサの数は増える一方であり、並行処理プログラミングはソフトウェア開発にとって必須のスキルとなっている。企業は並行処理を熟知した開発者を探し求めている。というのも、コンピューティングリソースが限られているのにハイパフォーマンスが求められる、という問題を解決する唯一の方法になることが多いからだ。
並行処理の最も重要な利点──そして昔から、この分野の探索を開始する最初の理由は、システムパフォーマンス改善の可能性である。システムパフォーマンスの改善はどのようにして可能になったのだろうか。
システムパフォーマンスを改善する
パフォーマンスを向上させる必要があるなら、より高速なコンピュータを購入すればよいのでは? 数十年前はそうしていたが、結局わかったのは、より高速なコンピュータを購入することがもはや現実的な解決策ではないことだった。
ムーアの法則
1965年、インテルの共同創業者であるGordon Mooreがあるパターンを発見した。プロセッサの新しいモデルは前のモデルのだいたい2年後に登場しており、そのたびにトランジスタの数がほぼ2倍になっていた。Mooreは、トランジスタの数、ひいてはプロセッサのクロック速度が、24か月ごとに倍になると推定した。この見解はムーアの法則(Moore's law)として知られるようになった。ソフトウェアエンジニアにとって、ムーアの法則は2年待つだけでアプリケーションの速度が2倍になることを意味した。
問題は、この法則が2002年頃に変わったことだった。C++の第一人者としてよく知られているHerb Sutterが言ったように、「フリーランチは終わった」のである。そこで私たちが気付いたのは、プロセッサの物理的なサイズと処理速度(プロセッサの周波数)の基本的な関係だった。演算を実行するのに必要な時間は、回路の長さと光の速度に依存する。
簡単に言うと、集積できるトランジスタの数には限りがある(トランジスタはコンピュータ回路の基本的な構成要素である)。温度の上昇も大きな役割を果たす。パフォーマンスをさらに向上させるにあたって、プロセッサの周波数が上がることだけに頼るわけにはいかなかった。かくして、いわゆるマルチコア危機が始まった。
クロック速度に関する個々のプロセッサの進歩は物理的な制限によって止まったが、システムパフォーマンスの改善に対するニーズは止まらなかった。半導体メーカーの焦点がマルチプロセッサという形での水平拡張へと移行すると、ソフトウェアエンジニア、アーキテクト、言語開発者は複数の処理リソースを持つアーキテクチャへの対応を迫られた。
この話から得られる最も重要な結論は、「並列処理の何よりも重要な利点にして、昔からこの分野の探索を開始する最初の理由は、追加の処理リソースを有効活用できるような方法でシステムパフォーマンスを向上させることである」となる。このことは、次の2つの重要な疑問につながる──パフォーマンスはどのように計測するのだろうか。そして、パフォーマンスを向上させるにはどうすればよいのだろうか。
レイテンシとスループット
コンピューティングでは、コンピュータシステムをどう捉えるかに応じて、パフォーマンスをさまざまな方法で定量化できる。完了できる作業の量を増やす方法の1つは、個々のタスクの実行にかかる時間を短くすることである。
自宅と職場の間をオートバイで移動すると、片道1時間かかるとしよう。職場にいかに早く到着できるかが重要なので、これを目安にシステムパフォーマンスを計測する。運転速度を上げれば、それだけ早く職場に到着する。コンピューティングシステムでは、この指標をレイテンシ(latency)と呼ぶ。レイテンシは、あるタスクの開始から終了までにかかる時間の指標である。
さて、あなたが交通部門で働いていて、バス路線のパフォーマンスを向上させることが仕事だとしよう。ある人物をオフィスにより早く到着させることにだけ配慮するのではなく、単位時間あたりに自宅から職場まで移動できる人の数を増やしたい。この指標をスループット(throughput)と呼ぶ。スループットは、システムがある時間内に処理できるタスクの数である。
レイテンシとスループットの違いを理解することは非常に重要である。オートバイがバスの2倍の速さで移動するとしても、バスのスループットはオートバイの25倍である(オートバイがある距離を1時間で1人輸送するのに対し、バスは同じ距離を2時間で50人輸送する。時間で平均すると、1時間あたり25人である)。別の言い方をすれば、システムのスループットが高いほど、レイテンシが小さいとは限らない。パフォーマンスを最適化する際には、ある要素(スループットなど)を改善すると、別の要素(レイテンシなど)が悪化することがある。
並行処理はレイテンシを小さくするのに役立つことがある。たとえば、実行に時間がかかるタスクを、同時に実行される複数のより小さなタスクに分割すると、全体的な実行時間を短くすることができる。並行処理は、複数のタスクを同時に処理できるようにするため、スループットを向上させるのにも役立つ。
さらに、並行処理はレイテンシを隠してしまうのにも役立つ。電話を待っているときや、通勤のために地下鉄を待っているときには、ただ待っていることもできるし、処理リソースを使って何か他のことをしながら待っていることもできる。たとえば、地下鉄に乗っている間にメールを読むことができる。このように、実質的に複数のタスクを同時に実行し、待ち時間を有効活用すれば、遅延を隠してしまうことができる。レイテンシの隠蔽はレスポンシブなシステムへの鍵であり、待機を伴う問題に応用することができる。
したがって、並行処理を活用すれば、次の3つの主な方法でシステムパフォーマンスを向上させることができる。
- レイテンシを小さくする(つまり、作業単位を高速化できる)。
- レイテンシを隠蔽する(つまり、レイテンシが大きい演算を実行している間にシステムが他の作業を実行できる)。
- スループットを向上させる(つまり、システムがより多くの作業を行えるようになる)。
並行処理がシステムパフォーマンスにどのように適用されるのかを見てきたところで、並行処理のもう1つの用途を探ってみよう。本章の冒頭で示したように、周囲の世界をモデル化したい場合は、並行処理が必要である。ここでは、並行処理により、大規模な問題や複雑な問題を計算的にどのように解決できるのかをより具体的に見ていく。
複雑で大規模な問題を解決する
ソフトウェアエンジニアが現実に対処するシステムを開発するときに解決しなければならない問題の多くは非常に複雑である。このため、逐次システムを使って解決するのは現実的ではない。複雑さは、問題の規模から生じることもあれば、開発するシステムの特定の部分を理解することの難しさから生じることもある。
スケーラビリティ
問題の規模はスケーラビリティ(scalability)に関係している。スケーラビリティとは、リソースを追加することでパフォーマンスを改善できるというシステムの特性のことである。システムのスケーラビリティを向上させる方法は、垂直と水平の2種類に分けることができる。
垂直スケーリングは、メモリの量を増やして既存の処理リソースをアップグレードするか、プロセッサをより高性能なものに交換することでシステムのパフォーマンスを向上させるというものであり、スケールアップとも呼ばれる。この場合、個々のプロセッサの速度を引き上げるのは非常に難しく、パフォーマンスが頭打ちになりやすいため、スケーラビリティは制限される。より高性能な処理リソースへのアップグレード(たとえば、スーパーコンピュータの購入)にはコストもかかる。トップクラスのクラウドインスタンスやハードウェアのメリットは小さくなる一方だが、支払わなければならない費用は高くなる一方だからだ。
特定の作業にかかる処理時間を短くすれば、ある程度の効果は得られるが、最終的にはシステムのスケールアウトが必要になる。水平スケーリングは、既存の処理リソースと新しい処理リソースの間で負荷を分散させることで、プログラムやシステムのパフォーマンスを向上させるというもので、スケールアウトとも呼ばれる。処理リソースの数を増やすことができる限り、システムパフォーマンスを向上させることができる。この場合、スケーラビリティの問題は垂直スケーリングの場合ほどすぐには発生しない。
業界は水平スケーリングに舵を切った。リアルタイムシステム、大容量データ、冗長化による信頼性に対する需要と、クラウド/SaaS環境への移行によってリソースが共有され、リソース使用率が改善されたことが、こうした傾向に拍車をかけた。
水平スケーリングでは、システムの並行化が要求されるため、1台のコンピュータでは不十分かもしれない。コンピュータクラスタと呼ばれる複数の相互接続されたマシンでは、データ処理タスクが妥当な時間内に解決される。
分離
大規模な問題には、複雑さという側面もある。残念ながら、システムの複雑さがひとりでに低下することはなく、エンジニア側に何かしら努力が求められる。企業は自分たちの製品をより強力で機能的なものにしたいと考えている。このため、コードベース、インフラ、メンテナンス作業の複雑さは否応なしに増加することになる。エンジニアは、システムを単純化し、相互にやり取りするよりシンプルで独立したユニットに分割するために、さまざまなアーキテクチャアプローチを調べて実装しなければならない。
ソフトウェアエンジニアリングでは、職務の分離はほぼ例外なく歓迎される。分割統治(divide and conquer)という基本的なエンジニアリング原則に従うと、疎結合システムが作成される。関連のあるコード(密結合コンポーネント)をグループにまとめ、関連のないコード(疎結合コンポーネント)を切り離すと、アプリケーションの理解とテストが容易になり、少なくとも理論上は、バグの数が少なくなる。
並行処理については、分離戦略という見方もある。機能をモジュールまたは並行処理の単位で分割すると、個々の要素が特定の機能に焦点を合わせるようになり、それらのメンテナンスが容易になり、システム全体の複雑さが低下する。ソフトウェアエンジニアは、何を行うかと、それをいつ行うかを切り離す。このようにすると、アプリケーションのパフォーマンス、スケーラビリティ、信頼性、内部構造が劇的に改善される。
並行処理は重要であり、現代のコンピューティングシステム、オペレーティングシステム(OS)、大規模な分散クラスタで広く使われている。並行処理は、現実世界をモデル化し、ユーザーと開発者の観点からシステム効率を最大化するのに役立つ。並行処理により、開発者は大規模で複雑な問題を解決できるようになる。
並行処理の世界を探っていくうちに、コンピュータシステムとその機能に対するあなたの考え方は変わるだろう。本書では、並行処理のさまざまな層について学びながら、この分野の全体像を明らかにしていく。