|
事業仕分けによる次世代スーパーコンピューターの開発予算削減について、どうお考えですか?
|
Java RTSによる金融アプリケーションの作成はじめに標準のJavaには欠けているものがあり、リアルタイムアプリケーションで使用するのに理想的な言語とは言えません(4ページ目補足解説1「リアルタイムとは」を参照)。1つを挙げるなら、ガベージコレクタ(古いオブジェクトを回収してヒープ領域を解放する内部JVMスレッド)は不測のタイミングで動作し、その実行時間も不特定であるため、アプリケーションの処理にレイテンシと不確定性が生じます。 しかし、標準Javaアプリケーションで問題となるのはガベージコレクタだけではありません。たとえばJavaコードを実行中のコンピュータに合わせて最適化するjust-in-time(JIT)コンパイラはいつでも呼び出し可能であり、コードが最適化済み(JITコンパイル済み)の場合でも呼び出される可能性があります。 また、一般にJava SE(Java Platform, Standard Edition)では、コードで指定したスレッドの優先順位が尊重されません。たとえば、スレッドが最高の優先順位で実行されるように指定しても、実際にはそうならない可能性があります。したがって、他のアプリケーションスレッドがJavaアプリケーションの実行に割り込んできて、不確定性に拍車をかけることがあります。SunのJava Real-time System(Java RTS)は、こういった問題点を解決することを狙っています。 2006年以来、Sunは金融業界(銀行、証券会社、証券取引所)の多数の顧客にJava RTSがタイムクリティカルな金融アプリケーションにもたらすメリットを訴えてきました。Java RTSを利用することで、1つのシステム上にリアルタイムのコンポーネントと非リアルタイムのコンポーネントを同居させ、データを共有することができます。リアルタイムのコンポーネントは、予測性とアプリケーション動作の確定性を向上させます。 Java RTSのバージョン2.0には、ミリ秒以下の有限のレイテンシで動作するリアルタイムガベージコレクタと、20マイクロ秒以下の有限のレイテンシで動作するRTSJ準拠プログラミングモデルが含まれています(詳細については4ページ目補足解説2「Java RTS仮想マシン」を参照)。Java RTS 2.0は、Intel、AMD、またはSPARCプロセッサ搭載のSolaris 10システムのみで動作し、Java 5に完全に準拠します。(Java RTS 2.0の詳細については、筆者が以前にDevXに掲載したリアルタイムJavaに関する記事「Go Inside the Java Real-time System」を参照)。 この記事では、ウォール街の企業や金融機関で一般的なユースケースにおけるJava RTSについて詳しく説明します。 Java RTSと金融システム金融業界の企業が利用するシステムは、金融市場の動きに随時反応しなければならないため、リアルタイムの動作を必要とします。金融市場は常に動き続けています。この変化のペースが速いか遅いかにかかわらず、市場の急変とトレンドに対して厳密な時間内に対応できなければ、利益を得たり損失を回避したりすることはできません。ウォール街の常識では、市場の勝敗はミリ秒単位の差で決し、投資銀行はその勝負に勝てば年間1億米ドルの利益を上積みできる可能性があるとされています。 金融システムでは、データの受信側(トレーディングシステムなど)とデータの生成側(市場データフィードなど)の両方が、リアルタイム処理能力を必須とするタイムクリティカルなソフトウェアシステムで構成されます。金融システムの利用企業にとって、Javaガベージコレクタやその他の活動によってもたらされる不確定のレイテンシは重大な投資ロスを意味します。逆に、このようなレイテンシを取り除き、確定性を強化すれば、ライバルに対して著しく優位に立つことができます。 Java RTSは、リスクを取り除くことを目的としています。リアルタイム環境においてJava RTSが標準Javaに比べてどのような利点を持つのかを証明するため、本稿では、標準JavaとJava RTSの両方で動作するサンプルの指値/逆指値注文システムを作成しました(指値/逆指値の詳細については4ページ目補足説明3「指値/逆指値注文取引」を参照)。 Java RTS金融アプリケーションのデモ指値/逆指値注文システムは、以下の3つのコンポーネントで構成されます(図1を参照)。
図1 取引システムのメインコンポーネント ![]() 取引エンジン(この記事の重点項目)では、以下の2つのメインスレッドが動作します。
図2 OrderManagerスレッド ![]() リアルタイムではない取引エンジン実装では、外部イベント(ガベージコレクションなど)がタイムクリティカルなループに割り込んできて、余分なレイテンシを発生させる可能性があります。その遅延の間に市場の値動きがあって、システムで1つ以上の無条件注文(オープンオーダー)の範囲内にいったん株価が入ってから範囲外にまた戻すことが起こりえます。事実、この現象は本稿のデモで発生します。 このシナリオでは、注文の機会を失ったことは運用機関の金銭的損失を意味します。理想的な取引のタイミングを逃したのに、顧客の注文を引き受けなければならないからです(図3を参照)。ガベージコレクションの割り込みが取引に影響を与える可能性は微々たるものですが、リスクは非常に大きなものです。次のデモでは、リスクが現実に起こりうることを示します。 図3 理想的な取引タイミングの逸失 ![]() しかし、同じ取引エンジンを(スレッドモデルにちょっとした変更を加えてから)Java RTSで実行すると、JVMはタイムクリティカルなコード内でデッドラインに対応することができ、取引の機会は失われません。エンジンは、市場のすべてのティックを取得し、すべての取引機会に応じます。その結果、市場の動きに確実に反応し、条件が満たされたときに顧客の注文を正確に取引できるシステムが手に入ります。 取引エンジンのタイムクリティカルな部分取引エンジンのオーダーブックは、HashMapで構成されます。HashMapはSubBookクラスのオブジェクトで構成され、このオブジェクトは株価を示す2つのLinkedListで構成されます。1つのLinkedListは買い注文用(上昇時)で、もう1つのLinkedListは売り注文用(下落時)です(図4を参照)。 図4 オーダーブックの取引エンジン ![]() このデータ構造のコードを以下のリストに示します。 class OrderEntry { private boolean active; private double price; private long quantity; private StringBuffer symbol; private int type; } class SubBook { LinkedList<OrderEntry> buy; LinkedList<OrderEntry> sell; } HashMap<StringBuffer,SubBook> orderBook = new HashMap<StringBuffer,SubBook>(INITIAL_CAPACITY); 以下のリストは、OrderManagerのタイムクリティカルなループを標準Javaで作成したものです。ここでは、オーダーブックを最新の株価と比較します。 public class OrderManager implements Runnable { ... public void run() { // 株価を注文価格と比較するループ while ( true ) { for ( int i = 0; i < orderBookKeys.length; i++ ) { StringBuffer symbol = orderBookKeys[i]; StringBuffer sPrice = marketMgr.marketBook.get(symbol); if ( sPrice == null ) continue; double marketPrice = Double.parseDouble( sPrice.toString() ); // この銘柄コードのサブブックを取得する SubBook sb = orderBook.get(symbol); // 売り注文リストを検索する for ( int x = 0; x < sb.sell.size(); x++ ) { OrderEntry entry = sb.sell.get(x); if ( checkForTrade( entry, marketPrice ) == true ) break; } // 買い注文リストを検索する for ( int x = 0; x < sb.buy.size(); x++ ) { OrderEntry entry = sb.buy.get(x); if ( checkForTrade( entry, marketPrice ) == true ) break; } } Thread.sleep(1); // 1ミリ秒スリープする } } ... } 1ミリ秒ごとにそれぞれの株の売り注文と買い注文が、市場の株価と比較されます。条件を満たす場合は、取引が執行されます。このデモでは、取引に関するメッセージが送信され、指値と約定した価格の差がユーザーインターフェイスコンポーネントによって画面にチャートとして表示されます。ほとんどの場合、差はゼロですが、ガベージコレクションの実行による休止時間が差(損失)を生む可能性があります。 ループのリアルタイムバージョンは、上記のコードとほとんど同じです。ちょっとした変更として、
public class OrderManager implements Runnable { ... public void run() { // 株価を注文価格と比較するループ while ( true ) { for ( int i = 0; i < orderBookKeys.length; i++ ) { StringBuffer symbol = orderBookKeys[i]; StringBuffer sPrice = marketMgr.marketBook.get(symbol); if ( sPrice == null ) continue; double marketPrice = Double.parseDouble( sPrice.toString() ); // この銘柄コードのサブブックを取得する SubBook sb = orderBook.get(symbol); // 売り注文リストを検索する for ( int x = 0; x < sb.sell.size(); x++ ) { OrderEntry entry = sb.sell.get(x); if ( checkForTrade( entry, marketPrice ) == true ) break; } // 買い注文リストを検索する for ( int x = 0; x < sb.buy.size(); x++ ) { OrderEntry entry = sb.buy.get(x); if ( checkForTrade( entry, marketPrice ) == true ) break; } } // 次の処理サイクルの開始まで待機する RealtimeThread.waitForNextPeriod(); } } ... } どちらのバージョンのOrderManagerもRunnableインターフェイスを実装して、スレッドの作成を要求します。リアルタイムバージョンの取引エンジンではjavax.realtime.RealtimeThreadが作成されますが、標準Javaバージョンではjava.lang.Threadです(以下のリストを参照)。RealtimeThreadを作成するときに時間と優先順位を指定しますが、この優先順位はJava RTSに本当に実装されているものです。 // 実行可能コードを作成する OrderManager orderMgr = new OrderManager(); // スレッドの優先順位を決定する long maxPriority = PriorityScheduler.instance().getMaxPriority(); PriorityParameters sched = new PriorityParameters( maxPriority ); // スレッドの時間を決定する RelativeTime one_millisecond = new RelativeTime(1,0); PeriodicParameters period = new PeriodicParameters(one_millisecond); // リアルタイムスレッドを上記のパラメータで作成する RealtimeThread orderThread = new RealtimeThread(sched,period,null,null,null,orderMgr); デモの実行結果とチャートデモを実行するには、株価更新アプリケーション、取引エンジン(リアルタイムと非リアルタイムの2バージョン)、およびJavaFXベースのチャートアプリケーションが必要です(すべて本稿冒頭のリンクからダウンロード可能)。株価データを転送するために標準Java EE JMSプロバイダが必要です。このデモでは、SunのJava EE 5アプリケーションサーバに付属するものを使用しました。設定を行うため、JMSトピックの送り先をjms/QuoteUpdatesという名前で作成してください。 OrderManagerの非リアルタイム版とリアルタイム版の違いを表示するため、チャート作成アプリケーションを実行します。このJavaFXアプリケーションでは、オープンソースのJFreeChartを使用します。 (メモ:このチャートアプリケーションのJavaFXバージョンを作成していただいたSunのJim Clarke氏に感謝します。) 図5に、非リアルタイムバージョンの取引エンジンからの出力を示します。多くの取引機会の喪失、金銭的損失、タイムクリティカルループの時間の揺れ(割り込みがあった証拠)、およびガベージコレクションイベント(グレーの垂直バー)があったことがわかります。全体として、これはサンプルの金融機関が資金を失ったことを意味します。 リアルタイムバージョンからの出力は、かなり違います(図6を参照)。失われた取引機会や金銭的損失がないことに注意してください。タイムクリティカルなループの時間は一定です(割り込みがない証拠)。また、ガベージコレクションイベント(グレーの垂直バー)は発生していますが、これはクリティカルな処理に影響していません。 デモアプリケーションの実行デモを実行するには複数のステップが必要です。最初に実行するときは、準備に手間がかかります。最初に、Java RTS Version 2の評価版をダウンロードし、インストールします。Solaris 10が稼働するPC(x86/x64またはSPARCプロセッサ)にインストールする必要があります。やむをえない事情がある場合は、VMwareなどの仮想サーバソフトウェア上で稼働するSolaris 10にインストールすることもできますが、リアルタイムの動作は得られない可能性が大です。 次に、JMSプロバイダを設定し、実行します(jms/QuoteUpdatesトピックが定義されている必要があります)。取引の通知はJMX MBean通知としてチャートコンポーネントに送信されるため、このコンポーネントをJMSプロバイダとしてアプリケーションサーバと一緒に実行するのが最適です。 次に、以下のコマンドを使ってチャートアプリケーションを実行します(アプリケーションサーバを実行するサーバのIPアドレスは実際のものに置き換えます)。 java -jar dist/RTfx.jar com.sun.oss.trader.fx.Main -- (チャートアプリケーションは任意のコンピュータで実行できます。取引システムがあるSolaris 10コンピュータ上で実行しなければならないわけではありません)。 次に、取引エンジン(非リアルタイム版またはリアルタイム版)をSolarisのNetBeans内で開始します。Java RTSアプリケーションの開発は、任意のプラットフォームでNetBeansまたはEclipseを使って行えますが、SunからはSolaris上でJava RTSアプリケーションをデバッグするための専用NetBeansプラグインが提供されています。取引エンジンは、次のコマンドを使ってコマンドラインからでも実行できます。
<path to JavaRTS>/bin/java ?jar dist/RTTradingEngine.jar java ?jar dist/TradingEngine.jar 株式情報の更新を開始するには、Quote Publisherアプリケーションのディレクトリに移動し、次のコマンドを実行します(JMSプロバイダのJARファイルがclasspathに設定されていることが前提)。 java ?jar dist/QuotePublisher.jar 以上の順序でアプリケーションを開始すると、間もなく取引データが次々とチャートに表示されます。通常、非リアルタイムバージョンでは、ガベージコレクションイベントが損失(市場ティックの喪失)と同時に起こることが、取引がチャート化されるにつれてわかります。チャートが負の領域に急降下し、ラインが赤に変わってそのことを示します。リアルタイムバージョンの場合、取引は直線を形成し、市場ティックの喪失は見られません。 最も重要なことは、リアルタイムバージョンでもガベージコレクションが発生しているという事実です。しかし、このイベントはタイムクリティカルな取引コードに割り込みません。Java RTSによって、リアルタイムメソッドの処理時間が変動しないことが保証されます。 リアルタイムアプリケーションを比較的簡単に実現このサンプルアプリケーションは、Javaでのリアルタイムアプリケーションの開発が比較的簡単なことを証明しています。事実、最小限のコードを修正するだけで、取引エンジンをリアルタイム対応にすることができました。 ただし、どのリアルタイムアプリケーションにも共通することですが、独自のリアルタイムJavaアプリケーションを設計するには、さらに多くの作業が必要です。重要なイベントが適切な優先順位で処理されるように設定しなければならないからです。とはいえ、Javaコードの書き方が大きく変わることはありません。リアルタイム動作の部分はJava RTSに任せておけばよいからです。 補足説明補足説明1:リアルタイムとは「リアルタイム」という用語はコンピュータに関連する多くの文脈で使われますが、誤用もしばしば見られます。たとえば、処理速度の速いシステムを表現するために「リアルタイム」という言葉を使うケースがよくあります。しかし、外部のイベントにすばやく反応できることは、リアルタイムシステムの正確な描写ではありません。 また他のケースでは、イベント発生後にチェックを行ってイベントを検出するシステムに対して、イベント発生時にすぐ応答するシステムのことを「リアルタイム」と表現することもあります。一般的なインスタントメッセージングシステムは、本来そうではないのにリアルタイムと呼ばれているアプリケーションの一例です。このシステムの場合、長短さまざまなレイテンシによってメッセージに遅延が生じてもシステムの正確性には影響しないため、リアルタイムアプリケーションではありません。インスタントメッセージシステムでは、メッセージが最終的に配信されれば、期待どおりの動作が行われたことになります。 システムスループットの計測においても、リアルタイムという言葉の使い方にしばしば混乱が見られます。スループットは、ソフトウェアシステムが特定の時間内で処理できる要求、イベント、またはその他の操作の数を表します。スループットの高いシステムは、リアルタイム的な状況で利用されると非常にリアルタイムに近い感覚をユーザーに与えますが、本当の意味でリアルタイムなシステムあるとは限りません。 たとえば、毎秒数千件の要求に対応できるシステムでも、一部の要求の処理では1秒かかる場合があります。たとえ要求の大半を低レイテンシで処理できたとしても、このような高レイテンシの要求は異常値(許容可能な応答時間を超える値)をもたらします。このような異常値の強さや出現数は予測できないため、このような高スループットシステムはリアルタイムシステムではありません。 実際、高速コンピューティングとリアルタイムコンピューティングでは目指すところが異なります。高速コンピューティングの目的は特定のタスクセットの平均応答時間を最小化することですが、リアルタイムコンピューティングの目的は各タスクのタイムクリティカルな要件を個別に満たすことです。Giorgio C. Buttazzoの著書『Hard Real-Time Computing Systems』(Springer Science and Business Media, 2005)に書かれている「水深平均15cmの河で溺れた男」の逸話が参考になります。 リアルタイムシステムとはリアルタイムシステムを特徴付けるのは、デッドライン(実行を完了しなければならない最長時間)の存在です。これとは対照的に、非リアルタイムシステムの場合、応答が速いことやパフォーマンスが高いことは歓迎されますが、デッドラインはありません。多くの場合、リアルタイムソフトウェアのニーズは、リアルタイムソフトウェアを開発するためのフレームワークとなるリアルタイムオペレーティングシステムや同期プログラミング言語のコンテキストで扱われます。 リアルタイムシステムは大きく2つに分けられます。1つは時間制限を超えた場合に不正確な結果や不確定な結果を返すもので、「ハードリアルタイム」または「即時リアルタイム」と呼ばれます。もう1つはそのような結果を返さないもので、「ソフトリアルタイム」と呼ばれます。ハードリアルタイムシステムでは、操作の正確性は操作の論理的な正確性だけでなく操作の実行時間にも依存します。「デッドラインが短いシステム=ハードリアルタイムシステム」という誤った認識もよく見られますが、デッドラインの長短はハードかソフトかの区別に関係ありません。「デッドラインを超えた場合にシステムが異常な状態になる」というのがハードリアルタイムの要件です。 ソフトリアルタイムシステムは、一般的に、同時アクセスが難しく、なおかつ絶えず変化する環境で複数の接続システムを最新の状態に保つ必要がある場合に利用されます。ソフトリアルタイムのシナリオでは、瞬間的なぶれは許容されますが、予測性は必要とされます。 簡単にまとめれば、ハードリアルタイムシステムではイベントへの制限時間内での応答が求められますが、ソフトリアルタイムシステムではデッドラインからの逸脱がある程度まで許容されます。 リアルタイムシステムの実現とは、大まかに言えば、現実の世界で起きるイベントに応答して、計測可能なデッドラインに達する前にタスクを実行するシステムを実現することです。デッドラインの計測単位がマイクロ秒であれ日であれ、デッドラインの前にタスクが完了しさえすれば、それはリアルタイムのシステムです。別の言い方をすれば、現実世界でのイベント発生時点(たとえば物体がセンサーの前を横切ったとき、市場データフィードティックが到着したときなど)からコードの処理完了時点までに時間制限を設けるということです。リアルタイムシステムに求められる確定性を実現するためには、このデッドラインを守る予測可能かつ確定的な機能が必要になります。 補足説明2:Java RTS仮想マシンとSolaris 10Java RTSは、RTSJ準拠のJava仮想マシンです。以下の特徴があります。
現在、Java RTSはSolaris 10のみで動作します。これは、Solarisが真のリアルタイムオペレーティングシステムだからです。Solarisでは、ほとんど最初の段階から、特殊なカーネル機能によってリアルタイムのプロセスとスレッドがサポートされています。Solarisは以下のリアルタイム機能をサポートしています。
最後の2つについては、詳しい説明が必要です。Solarisでは、プロセッシングコアのサブセットを1セットにグループ化したプロセッサセットを定義できます。たとえば、4コアシステムにおいて2つのコアを含むプロセッサセットを定義し、このセット上でJava RTSアプリケーションを実行することができます。以降、このプロセッサセット内の2つのコアは、指定したJava RTSアプリケーションだけを実行します。全システムの一部を特定のアプリケーションの専用にするようなものです。 さらに、割り込み遮断によって、プロセッサセット内のコアは割り込み処理から遮断されます。その結果、2つのコアはJava RTSアプリケーションの実行だけに集中し、低レベルのシステム割り込みを含め、他のシステムスレッドから割り込まれることはなくなります。 補足説明3:指値/逆指値注文取引指値とは指値とは、特定の価格以下で有価証券を買う(または特定の価格以上で売る)ための注文です。指値を使うと、顧客は取引が執行される価格をある程度制御できます。 買い指値は、指値と同額かそれ以下の価格でのみ執行されます。たとえば、投資家が株を買いたいが1株あたり20ドルより多くは払いたくない場合、この株を最高20ドルで買う指値注文を出します。成行き注文ではなく指値注文を出すことにより、株価が急騰した場合も30ドルで買ってしまう事態には陥りません。反対に、売り指値は、指値と同額かそれ以上の価格でのみ執行されます。 株価が指値に達しないと、指値注文は執行されません。手続きが複雑になるため、証券会社によっては指値注文の執行には成行き注文よりも高い手数料を課しています。 逆指値とは逆指値(損切注文とも呼ばれる)は、有価証券の価格が指定した値段(逆指値)に達したときに買う、または売る注文です。指定した値段になると、逆指値注文は成行き注文として登録されます。逆指値は、投資家が市場の変動に直撃されるのを抑えるために利用されます。 売り逆指値は、価格が指値より安くなった後で、できるだけ有利な価格で売りたい場合に使用します。売り逆指値は、常に現在の株価を下回ります。たとえば、投資家が現在50ドルの値が付いている株を保有していて、株価の下落を懸念している場合、40ドルでの売り逆指値の注文を証券会社に出します。なんらかの理由で株価が40ドルまで下落すると、証券会社は次に付いた値で株を売却します。これで、投資家の損失は一定の限度で抑えられます(逆指値が購入時の価格以下の場合)。または、投資家の利益の少なくとも一部は確保されます(購入したときから逆指値注文を出したときまでの間に株価が上昇していた場合)。 買い逆指値は、一般に空売りの損失を一定の限度に抑えるため(またはそれまでの利益を守るため)に使用されます。買い逆指値は、常に現在の株価を上回ります。たとえば、投資家が株を空売り(株を借りて現在の株価ですぐに売ること)していて、借りた株をより安い価格で返して差額をポケットに入れようと株価の下落を期待している場合、株価が上がり過ぎたときに損失を出すのを避けるために買い逆指値を使うことがあります。 逆指値を使うと、顧客が株価の動きを積極的に監視する必要はありません。ただし、株価が逆指値に達すると注文が自動的に出されるため、株価の短期間の上下で逆指値が働いてしまう可能性があります。逆指値に達すると、逆指値注文は成行き注文になります。刻一刻と変化する市場では、取引が執行される価格が逆指値と大きく異なる場合があります。 証券取引所で売買される株や先物では、店頭取引(OTC)市場に比べてはるかに高い頻度で逆指値が使われます。 著者紹介Eric Bruno(Eric Bruno)
ニューヨークを拠点とするコンサルタント。大規模なWebアプリケーション、データベースシステム、リアルタイムトランザクションシステムをJavaとC++で開発した経験を持つ。詳しくは個人ページhttp://www.ericbruno.com/を参照。
|
japan.internet.com 10周年記念
インターネットコムマーケティングセミナー ROI を最適化するパフォーマンスマーケティングの最前線 【12/16(水)13時〜 東京・赤坂】 申込はコチラ>>
|