クライアントJavaアプリケーションに おけるFork/Joinフレー …...Java SE...

5
ORACLE.COM/JAVAMAGAZINE ////////// JULY/AUGUST 2012 JAVA TECH 40 COMMUNITY JAVA IN ACTION ABOUT US blog //java architect / C PUの高速化が止まっています。3 GHzのCPUが登場してから10年 近く経ちますが、私はまだ4 GHzのマ シンすら入手できていません。これは、 ムーアの法則で算出される43 GHzに は程遠い数字です。CPUの処理能力は 大きく向上していますが、その要因は、 速度の向上ではなく、並列化にありま す。つまり、CPU数が増加し、CPUあた りのコア数が増加し、コアあたりのスレッ ド数が増加したのです。 この傾向はエンドユーザーにとっては 好ましい一方、アプリケーション開発者 にとっては新たな課題を生み出していま す。今や、Appleから12コアのデスクトッ プ・コンピュータも購入できますし、私の 小さなラップトップでさえ2つのコアを搭 載しています。時代は並列化に向かって います。その中で、並列コンピュータに対 応するコードを記述するための新たな手 法が求められています。 Javaでは以前より、並行プログラミ ングを実現する優れた機能として、 java. lang. Threadが提供されています。し かし、スレッドが効果的に機能するのは、 おもにI/O処理中心のタスクです。Web アプリケーションのホスティングのような サーバー・サイドのジョブには効果がある ものの、最近のデスクトップ・アプリケー ションでは、CPU処理中心のタスクにも 対応する必要があります。 CPU処理中心のタスクでコンピュー タの処理能力を最大限に活用することは はるかに難しく、特に汎用オペレーティン グ・システムでは、多数のさまざまなプロ セスがCPU時間を奪い合うため、さらに 難しくなります。このような状況に対応す るため、Java SE 7では、CPU処理中心 のタスクに有効な並行処理を実現する新 しいフレームワークが導入されました。 これがFork/Joinフレームワークです。 Java SE 7のFork/Joinフレーム ワークは、概念的には単純です。現在の スレッドをフォークして作業を分割し、分 割されたタスクをジョインして最終的な 結果を収集します。これ以降で説明する とおり、この新しいフレームワークを使 用したコーディングは非常に簡単です。 Fork/Joinの真価は、フレームワーク内 部での水面下の働きにあります。 Fork/Joinフレームワーク Fork/Joinフレームワークには、 Executorインタフェースと比較して、 次の3つの利点があります。 再帰的アルゴリズムとの適合性。特に 作業の規模を事前に予 測できない場合に適し ています。 ワーク・スティーリング処 理 の 実 現 。これにより、 複数のプロセッサに対 するワークロードが均一 化され、ロック競合が減 少します。従来のアルゴ リズムでは、プロセッサ の数が8基前後に達し た場合、コア数の増加に よる速度の向上よりも ロック競合のオーバー ヘッド の 影 響 が 大 きく なる可能性があります。 Fork/Joinでは、コア数 が100を超えても対応 できます。 将来的な互換性と移植 性。Fork/Joinフレー ムワークを使用する場合、基盤とな るハードウェアを意識せずにコードを 記述できます。Fork/Joinを使用した コードは、現代のデュアルコアのラップ トップやシングルコアの携帯電話から 将来の100コアのデスクトップに至る まで、任意のハードウェアにおいて最 大限の効率性を維持して 動作します。 単純な例 Fork/Joinフレームワーク の動作を理解するために、 単純なプログラムを例と して取り上げます。Fork/ Joinフレームワークを利 用するすべてのクラスで は、基本的に次のようなア ルゴリズムが使用されます リスト1参照)。作業量が しきい値を下回る場合は、 そのまま作業を行います。 作業量がしきい値以上 の場合は、作業を半分に 分割して再帰的に実行し、 両方が完了するまで待機 します。このアルゴリズム は、典型的な分割統治法で す。このアルゴリズムの実際の動作を示 すために、非常に大きなdouble型配列 の最小値を計算する単純な例を作成しま した(リスト2参照)。 リスト2computeメソッドでは、 スト1に示したアルゴリズムを実装して います。検索対象となるdouble値の数 BIO クライアントJavaアプリケーションに おけるFork/Joinフレームワーク Java SE 7のFork/Joinフレームワークは、CPUに負荷のかかるクライアント・サイド・アプリケーションに最適です。 基本的な アルゴリズム Fork/Joinフレー ムワークを利用す るすべてのクラス では、基本的に次 のようなアルゴリ ズムが使用されま す。 作業量がしき い値を下回る場合 は、そのまま作業 を行います。作業 量がしきい値以上 の場合は、作業を 半分に分割して再 帰的に実行し、両 方が完了するまで 待機します。 JOSH MARINACCI 写真: CHRIS PIETSCH / GETTY IMAGES

Transcript of クライアントJavaアプリケーションに おけるFork/Joinフレー …...Java SE...

Page 1: クライアントJavaアプリケーションに おけるFork/Joinフレー …...Java SE 7のFork/Joinフレームワークは、CPUに負荷のかかるクライアント・サイド・アプリケーションに最適です。

ORACLE.COM/JAVAMAGAZINE ////////// JULY/AUGUST 2012

JAVA TECH

40

COMMUNITY

JAVA IN

ACTION

ABOUT US

blog

//java architect /

CPUの高速化が止まっています。3 GHzのCPUが登場してから10年

近く経ちますが、私はまだ4 GHzのマシンすら入手できていません。これは、ムーアの法則で算出される43 GHzには程遠い数字です。CPUの処理能力は大きく向上していますが、その要因は、速度の向上ではなく、並列化にあります。つまり、CPU数が増加し、CPUあたりのコア数が増加し、コアあたりのスレッド数が増加したのです。

この傾向はエンドユーザーにとっては好ましい一方、アプリケーション開発者にとっては新たな課題を生み出しています。今や、Appleから12コアのデスクトップ・コンピュータも購入できますし、私の小さなラップトップでさえ2つのコアを搭載しています。時代は並列化に向かっています。その中で、並列コンピュータに対応するコードを記述するための新たな手法が求められています。Javaでは以前より、並行プログラミ

ングを実現する優れた機能として、java.lang.Threadが提供されています。しかし、スレッドが効果的に機能するのは、おもにI/O処理中心のタスクです。Webアプリケーションのホスティングのようなサーバー・サイドのジョブには効果がある

ものの、最近のデスクトップ・アプリケーションでは、CPU処理中心のタスクにも対応する必要があります。CPU処理中心のタスクでコンピュー

タの処理能力を最大限に活用することははるかに難しく、特に汎用オペレーティング・システムでは、多数のさまざまなプロセスがCPU時間を奪い合うため、さらに難しくなります。このような状況に対応するため、Java SE 7では、CPU処理中心のタスクに有効な並行処理を実現する新しいフレームワークが導入されました。これがFork/Joinフレームワークです。Java SE 7のFork/Joinフレーム

ワークは、概念的には単純です。現在のスレッドをフォークして作業を分割し、分割されたタスクをジョインして最終的な結果を収集します。これ以降で説明するとおり、この新しいフレームワークを使用したコーディングは非常に簡単です。Fork/Joinの真価は、フレームワーク内部での水面下の働きにあります。

Fork/JoinフレームワークF o r k / J o i nフレームワークには、Executorインタフェースと比較して、次の3つの利点があります。

■■ 再帰的アルゴリズムとの適合性。特に

作業の規模を事前に予測できない場合に適しています。

■■ ワーク・スティーリング処理の実現。これにより、複数のプロセッサに対するワークロードが均一化され、ロック競合が減少します。従来のアルゴリズムでは、プロセッサの数が8基前後に達した場合、コア数の増加による速度の向上よりもロック競合のオーバーヘッドの影響が大きくなる可能性があります。Fork/Joinでは、コア数が100を超えても対応できます。

■■ 将来的な互換性と移植性。Fork/Joinフレームワークを使用する場合、基盤となるハードウェアを意識せずにコードを記述できます。Fork/Joinを使用したコードは、現代のデュアルコアのラップトップやシングルコアの携帯電話から将来の100コアのデスクトップに至るまで、任意のハードウェアにおいて最

大限の効率性を維持して動作します。

単純な例Fork/Joinフレームワークの動作を理解するために、単純なプログラムを例として取り上げます。Fork/Joinフレームワークを利用するすべてのクラスでは、基本的に次のようなアルゴリズムが使用されます(リスト1参照)。作業量がしきい値を下回る場合は、そのまま作業を行います。作業量がしきい値以上

の場合は、作業を半分に分割して再帰的に実行し、両方が完了するまで待機します。このアルゴリズムは、典型的な分割統治法で

す。このアルゴリズムの実際の動作を示すために、非常に大きなdouble型配列の最小値を計算する単純な例を作成しました(リスト2参照)。リスト2のcomputeメソッドでは、リ

スト1に示したアルゴリズムを実装しています。検索対象となるdouble値の数

BIO

クライアントJavaアプリケーションに おけるFork/JoinフレームワークJava SE 7のFork/Joinフレームワークは、CPUに負荷のかかるクライアント・サイド・アプリケーションに最適です。

基本的な アルゴリズムFork/Joinフレームワークを利用するすべてのクラスでは、基本的に次のようなアルゴリズムが使用されます。作業量がしきい値を下回る場合は、そのまま作業を行います。作業量がしきい値以上の場合は、作業を半分に分割して再帰的に実行し、両方が完了するまで待機します。

JOSH MARINACCI

写真: CHRIS PIETSCH / GETTY IMAGES

Page 2: クライアントJavaアプリケーションに おけるFork/Joinフレー …...Java SE 7のFork/Joinフレームワークは、CPUに負荷のかかるクライアント・サイド・アプリケーションに最適です。

ORACLE.COM/JAVAMAGAZINE ////////// JULY/AUGUST 2012

JAVA TECH

41

COMMUNITY

JAVA IN

ACTION

ABOUT US

blog

//java architect / がしきい値(100)未満の場合は、そのまま最小値を計算します。しきい値以上の場合は、配列を2つに分割し、それぞれに対して再帰処理を行って、両方がジョイン(完了)するまで待機します。分割された配列の両方の結果を得てから、その最小値を計算して返します。このM i n i m um F i n d e rクラスは、

RecursiveTask<Double>インタフェースを実装しています。Fork/Joinフレームワークでは2つのインタフェースが定義されており、RecursiveTask<Double>インタフェースはそのうちの1つにあたります。もう1つのRecursiveActionインタフェースは、com-puteメソッドが何も返さないという点のみがRecursiveActionインタフェースと異なります。 ポイントは、join()メソッドにあります。join()メ

ソッドは、すべてのサブタスクが完了するまで待機します。図1に、リスト2のコードについての簡単なベンチマークを示します。このベンチマークでは、私のラップトップを使用し、コアが1つの場合と2つの場合とで、3,000万件の乱数に対する実行時間を比べました。まず、コアを1つで10回実行し、その平均を算出しました。次に、コアを

2つに切り替えて同様に10回の平均を算出し、さらに、この一連の操作を6回繰り返しました。同じ処理を何度も行って平均値を算出することで、結果の信頼性を確保しています。図1からわかるように、シングルコアでの実

行時間は190 ms前後、デュアルコアでの実行時間は122 ms前後です。つまり、コアが2つの場合の実行時間はコアが1つの場合のわずか64%です。コアが2つの場合は処理速度も2倍になることを期待してしまいますが、それには達しませんでした。とは言え、速度はかなり向上しています。では、デュアルコアがシングルコアのちょうど

2倍の処理速度(95 ms)にならなかったのはなぜでしょうか。理由はいくつかあります。その1つは、Fork/Joinフレームワークの利用にはオーバーヘッドが伴うためです。小規模なタスクでは、そのオーバーヘッドに見合うだけの効果が得られません。残念ながら、オーバーヘッドに見合うタスクのほとんどは、例として使用するにはあまりにも複雑すぎます。Fork/Joinに関するWeb上のチュートリアルの多くがフィボナッチ数列の計算の類であるのは、そのためです。しかし、フィボナッチ数列の再帰処理による計算は効率が悪いため、例としてふさわしくありません。計算速度は2倍にできませんし、そもそも再帰処理よりもずっと良い高速な計算方法があるからです。それでは、この例のプログラムをどのように改

良すれば、マルチコアによる効果がよりはっきりと現れるでしょうか。Fork/Joinのオーバーヘッドの影響が少なくなるように、アルゴリズムで扱う作業量を増やせばよいのです。そこで、リスト2のコードに1行追加し、配列の各値に対して乗算を3回ずつ行うようにしました。比較よりも乗算の方がはるかに時間がかかるため、乗算を追加することで計算時間が大幅に増加します。図2に、乗算を追加したコードの実行結果を示します。処理時間が延びて、私のラップトップにおける全体の実行時間は9分に達しました。デュアルコアの処理時間は、シングルコアの

図1

図2

compute() { if ( 作業.サイズ < しきい値 ) { return doWork(作業); } else { f1 = fork(分割した作業の前半) f2 = fork(分割した作業の後半) 2つのフォーク処理がジョインするまで待機 } }

リスト1 リスト 2

全てのリストをテキストで表示

Page 3: クライアントJavaアプリケーションに おけるFork/Joinフレー …...Java SE 7のFork/Joinフレームワークは、CPUに負荷のかかるクライアント・サイド・アプリケーションに最適です。

ORACLE.COM/JAVAMAGAZINE ////////// JULY/AUGUST 2012

JAVA TECH

42

COMMUNITY

JAVA IN

ACTION

ABOUT US

blog

//java architect / 処理時間の約55%でした。理想的な結果との差異は10%未満です。また、CPUの使用率は常時ほぼ100%でした。素晴らしい結果です。この例は、APIを学ぶためには適していました

が、実際のアプリケーション、特にクライアント・サイド・アプリケーションでは、このAPIをどのように活用できるでしょうか。まず思い浮かぶのは、グラフィックです。複雑なグラフィックのレンダリングは、多くの場合、並列化が極めて容易です。そこで、CPUに負荷のかかるグラフィックとしてよく知られているフラクタルを取り上げます。

並列計算によるマンデルブロ集合の作図マンデルブロ集合は、典型的なフラクタルとみなされることが多く、少なくとも、もっともよく知られたフラクタルです。マンデルブロ集合は、各ピクセルを個別に計算できるため、並列計算に適しています。 マンデルブロ集合には、興味深い特徴がもう1

つあります。それは、作業量が均一ではないという点です。マンデルブロ集合の1つのピクセルを計算するためには、単純な方程式を、値が特定のしきい値を上回るまで何度も繰り返し実行します。値がしきい値を上回ることを、エスケープと呼びます。各ピクセルの色は、値がエスケープするまでのループの回数に基づいています。ループが十分な回数実行された場合(この回数も、また別のしきい値です)、そのピクセルはエスケープしないとみなすことができ、ピクセルの色を黒にします。図3に典型的なマンデルブロ集合を示します。このマンデルブロ集合の画像について詳しく

説明します。中央部分はエスケープしないピクセルが占めています。この部分の各ピクセルでは、同じ回数だけ(しきい値に達するまで)ループが行われています。そのため、この部分のピクセルを計算するために必要な作業量は一定です。一

方、黒以外の色の付いたピクセルは、ループ回数が上限に達する前にエスケープしています。つまり、黒以外のピクセルで必要な作業量は、最大の作業量より少なくなっています。言うまでもなく、黒のピクセルに注目する必要はありません。注目するべきは、黒以外の色の付いたピクセルです。この領域では、各ピクセルのワークロードが大幅に異なっています。Fork/Joinフレームワークは、このようなばらつきのあるワークロードを効率的に並列処理するために最適な方法です。マンデルブロ集合の標準的な並列処理アル

ゴリズムでは、画像を複数のピクセルの行に分割し、いくつかの行の集合ごとに1つのスレッドを割り当てます。では、行の集合によって処理時間が異なる場合、何が起こるでしょうか。処理時間に差が生じるのは、このフラクタルの各行において、黒いピクセルの数が異なる場合です。標準的なスレッド・プールでは、スレッドの1つ

で他のスレッドよりも早く作業が完了した場合、先に完了したスレッドはアイドル状態となってただ待機します。一方、Fork/Joinのスレッド・プールではワークスティーリングという特徴的な機能

があるため、動作が異なります。スレッドの1つで作業が完了した場合、そのスレッドは別のスレッドから作業の一部をスティール(横取り)できます。Fork/Joinフレームワークでは、複数のスレッドに対する負荷分散が自動的に行われ、いずれのスレッドでも常に何らかの作業が行われるため、CPUの使用率が最大化されます。このマンデルブロ集合に対し

て、固定サイズのスレッド・プールを使用した場合とFork/Joinフレームワークを使用した場合とを比較します。図4に、4,000 x 4,000ピクセルのフラクタルに対して、固定サイズのスレッド・プールとFork/Joinフレームワークを使用した結果をグラフで示します。 ワークロードは、均一なワークロードとばらつ

きのあるワークロードの2種類に大別されます。均一なワークロードでは、すべてのあらゆる作業単位で完了までに必要な労力(時間)はほぼ同じです。一方、ばらつきのあるワークロードでは、作業量が異なり、その差異が非常に大きくなることもあります。ばらつきのあるワークロードに対する効果を、図4で確認できます。いずれの場合でもデュアルコアの方が高速ですが、Fork/Join方式はスレッド・プール方式よりも高速です。作業量にばらつきがあるため、スレッド・プール方式では、シングルコアと比較したデュアルコアの場合の速度向上はわずかです。デュアルコアのマシンにおいて、第一のスレッドで第二のスレッドより3倍速く処理が完了したとしても、固定サイズのスレッド・プール方式では、全体の処理時間は遅い方のスレッドの処理時間になります。一方、Fork/Join方式では、ワークスティーリングが実行されるため、ワークロードにばらつきがあっても待機による遅延は起こりません。オラクルにおける並行処理のエキスパートで

あるBrian Goetz氏は、次のように説明します。「サイズNの問題をp基のプロセッサで処理す

る場合、サイズN/pの問題に分割すると、かならずばらつきが生じます。いくつかのCPUが先に処理を完了し、アイドル状態になります。また、た

とえばサイズN/10*pの問題にさらに分割した場合は、ワーク・キューが1つであることが逐次処理のボトルネックとなります。複数のスレッドが、次の作業の取得を求めて競合するためです。開始時点で問題の負荷が均一化されている場合でも、キャッシュ・ミス、ページ・フォールト、GCなどのため、問題ごとに処理速度は異なります。その結果、ばらつきが生じます。Fork/Join方式では、ばらつきをなくそうと努力しなくても、ワークスティーリングによって自動的に均一化されます。この例に関して1つ補足しておきます。この

例では、ピクセルをバッファ画像に格納しました。そして、各ループでは処理結果のピクセルについてimage.setRGBが呼び出されます。image.setRGBの行をコメントアウトすると、シングルコアの場合でもテストの速度が向上し、平均時間は、5,000 msから3,900 msに短縮されました。バッファ画像の処理のどこかに、単に配列の

値を設定するよりもはるかに長い時間がかかる処理があったのです。これは、動作を遅くする原因として開発者が想定する内容が、かならずしも実際の原因ではないことを示しています。この例の場合、整数の配列を使用して処理し、完了後に配列を画像に変換することで、コードの全体的

図3 図4

最適な用途Fork/Join 方式は、再帰的アルゴリズムに適しており、 特に作業の規模を事前に予測できない場合に最適です。

Page 4: クライアントJavaアプリケーションに おけるFork/Joinフレー …...Java SE 7のFork/Joinフレームワークは、CPUに負荷のかかるクライアント・サイド・アプリケーションに最適です。

ORACLE.COM/JAVAMAGAZINE ////////// JULY/AUGUST 2012

JAVA TECH

43

COMMUNITY

JAVA IN

ACTION

ABOUT US

blog

//java architect / なパフォーマンスが大幅に改善します。ここで得られる教訓は、本当に動作が遅い箇所を特定するためには、常にコードをプロファイルする必要がある、ということです。

画像のセグメンテーション最後の例として、これまでとはやや異なる処理である、画像のセグメンテーションを取り上げます。画像圧縮からコンピュータ・ビジョンまで、画像処理の多くのアルゴリズムでは、まず最初に画像を類似したセグメントに分割します。この処理を、画像のセグメンテーションと呼びます。画像のセグメンテーションが完了した後は、顔認識や物体のエッジ検出など、さまざまな処理を続けることができます。そのため、画像のセグメンテーションを高速化することは重要です。画像のセグメンテーションを実現する単純な

方法の1つに、四分木グラフがあります。画像のセグメンテーションを実行するためには、まず画像内の大きな正方形領域(通常は画像全体)に注目し、その正方形内のすべてのピクセルが類

似しているかどうかを、しきい値に基づいて判断します。類似している場合は、その正方形を単一の色の値で表示できます。類似していない場合(セグメンテーションの初期では、類似していない場合がほとんどです)は、その正方形を4つの正方形に分割して、同じ処理を繰り返します。この再帰アルゴリズムによって、最終的に、正方形内のすべてのピクセルが類似するほど小さな正方形に分割されるか、1ピクセルの大きさの正方形(この正方形内のピクセルは、言うまでもなくそのピクセル自体と類似しています)に達します。この結果得られる正方形が入

れ子になったグラフを四分木と呼びます(3次元で同様の操作を行ったものは、八分木と呼ばれます)。 図5は、画像の上に四分木グラフを重ねたも

のです。図5からわかるとおり、描写が細かい領域ほど、描画される小さな正方形の数が多くなっています。四分木の大きな正方形は、画像のその部分に

類似しているピクセルが数多く存在することを表します。画像の中で多様なピクセルが存在する部分には、小さい正方形が並び、グラフがより細かくなっています。このことは、このグラフを作成するためのプロセスにばらつきがあることを意味します。画像の領域によって、処理にかかる時間が異なり、CPUに対する負荷も異なります。もっとも重要な点として、画像の中で特に処理に時間がかかる領域を事前に特定することはできません。そのため、作業を複数のCPUに均等に分割するためには、Fork/Joinフレームワークのワークスティーリング動作が不可欠です。ここで、画像のセグメンテーションを簡潔にまとめると、ばらつきのある画像データをしきい値と比較することによる再帰的なグラフ作成ということになります。まさに、Fork/Join方式に最適です。図5

画像処理にも最適新しいFork/Joinフレームワークは、画像の操作や生成など、CPUに負荷のかかるクライアント・サイド・アプリケーションに適しています。

YOUR LOCAL JAVA USER GROUP NEEDS YOU

Find your JUG here

Page 5: クライアントJavaアプリケーションに おけるFork/Joinフレー …...Java SE 7のFork/Joinフレームワークは、CPUに負荷のかかるクライアント・サイド・アプリケーションに最適です。

ORACLE.COM/JAVAMAGAZINE ////////// JULY/AUGUST 2012

JAVA TECH

44

COMMUNITY

JAVA IN

ACTION

ABOUT US

blog

//java architect /

それでは、デモ・アプリケーションとパフォーマンス・テストについて見てみましょう(リスト3参照)。この例では、Dev.Magの記事に掲載されたコードを利用しています。リスト3では、プロジェクト全体のコードではなく、本記事に関係する部分のみを示しています。なお、元のコードの並列化には、5分ほどしかかかりませんでした。Fork/Joinフレームワークは、再帰的アルゴリズムでの利用に非常に適していることがわかります。このアプリケーションをデュアルコアのラップ

トップで2,000 x 3,000ピクセルのテスト画像に対して実行しました。テストは、スレッド数の設定をさまざまに変えて行いました。図6に、さまざまなスレッド数とそれぞれの実行時間を示します。

図6からわかるように、スレッドを1つから2つに増やした場合は、処理速度が大幅に向上しています。スレッドを3つ以上に増やしても、速度は向上していません。物理的なコアが2つしかないため、当然の結果です。ただし、速度が大幅に低下することもありません。この結果は、Fork/Joinフレームワークの動作によるオーバーヘッドが最小限であることを示しています。.

まとめFork/Joinフレームワークは、Java仮想マシンの実行速度を向上させるものではありません。単に並列処理アルゴリズムを表現するための便利な手法であり、開発者自身で作業を分割する場合よりも効率的にコードを実行できます。Fork/Join方式の重要な点は、アルゴリズムのみを考慮すればよく、そのアルゴリズムの実装方法を考慮せずに済むことです。1つのコアでも100のコアでも、同じコードを使用できます。コア数を気にする必要はありません。システム効率が、今すぐ、そして将来にわたって、最大化されます。このような新しいFork/Joinフレームワークは、画像の操作や生成などCPUに負荷のかかるクライアント・サイド・アプリケーションに非常に適していると言えるでしょう。 </article>

LEARN MORE• Josh on Design• Fork/Joinフレームワークについて• 「お待たせしました ! Java 7 の登場」• 「Meet JavaMan」• 「Fork and Join:Java Can Excel at Painless Parallel Programming Too!」

図6

このリストは、紙面の都合上抜粋になっています。省略部分は. . .記号で示しています。コード・リストの全体は、本号のコード・リストをダウンロードしてご確認いただけます。

public class Node {... protected void compute() { if(w <= 1 ¦¦ h <= 1 ¦¦ measureDetail(grid,x,y,w,h) <= threshold) { color = average(grid,x,y,width,height); } else { children[0] = new Node(grid,x,y,w/2,h/2,threshold); children[1] = new Node(grid,x+w/2,y,w-w/2,h/2,threshold); children[2] = new Node(grid,x,y+h/2,w/2,h-h/2,threshold); children[3] = new Node(grid,x+w/2,y+h/2,w-w/2,h-h/2,threshold); invokeAll(children); } } Color average(int[][] grid, int x, int y, int w, int h) { int redSum = 0; int greenSum = 0; int blueSum = 0; int area = w*h; for(int i=x; i<x+w; i++) { for(int j=y; j<y+h; j++) { redSum += getRed(grid[i][j]); greenSum += getGreen(grid[i][j]); blueSum += getBlue(grid[i][j]); } } return new Color(redSum/area,greenSum/area,blueSum/area); } int measureDetail(int[][] grid, int x, int y, int w, int h) { Color avg = average(grid, x, y, w, h); int red = avg.getRed(); int green = avg.getGreen(); int blue = avg.getBlue(); int area = w * h; int colorSum = 0; for(int i=x; i<x+w; i++) { for(int j=y; j<y+h; j++) { colorSum += Math.abs(red-getRed(grid[i][j])); colorSum += Math.abs(green-getGreen(grid[i][j])); colorSum += Math.abs(blue-getBlue(grid[i][j])); } } return colorSum / (3 * area); }

リスト3

全てのリストをテキストで表示