眉村卓『引き潮のとき』感想

近所の図書館に眉村卓『引き潮のとき』全巻(1~5巻)の在庫があったので年末の時間を使って読んでみた。と感想を書きたいところだけれど、結構思い入れのある(一方で、これまで全巻読む機会が無かった)作品なので、まずはちょっと昔語りを。

『引き潮のとき』との出会い

自分がSFマガジンを読み始めたのが、谷甲州『終わりなき索敵』の連載が始まった1992年で、そのときに連載中の『引き潮のとき』を読んだのが眉村卓との初遭遇でした。ただ、その時点で『引き潮のとき』連載が既に100話を超えていて(当時のSFMは月刊だったので、つまり既に8~9年の間連載が続いていて)、当然ながらあらすじもかなり大胆不敵に圧縮されてたのでよく分からず、他の『司政官』シリーズについてもまだ読んでおらずという状態だったので、正直なところ最初は

読んでいて非常に心地よいものの、延々と自問自答を繰り返しているだけで話がどんどん先へ続いていって、一年経ってもストーリーが進んだのか進んでないのかよく分からない(そもそもあらすじにある最終目的の「星区ブロック化阻止」というのもよく分からない)。最終話も読んだけど、しかしこれはどう解釈したら良いのか…

という感じだったのですね。
ただ、その後に復刊されたハヤカワJA版『司政官』を読んだり、更にその後にハルキ文庫で復刊された『消滅の光輪』を読んだりして『司政官』シリーズの世界観や独特の自問自答文章スタイルにも慣れて、『司政官』シリーズのファンにはなったのですが、しかしこのシリーズと初遭遇した『引き潮のとき』だけは、「そもそもストーリーの最初の頃が全くわからない」という状態のままでずるずると来てしまったのです。なんといっても文庫化されなかったし。

bookmeter.com

bookmeter.com


で、10年ほど前に創元推理文庫で『司政官』シリーズが復刊されたときに、いよいよ文庫化されるか!と思ったのですが、されずじまい。

www.tsogen.co.jp

www.tsogen.co.jp

その当時だと古書で全巻揃えたら3万円ちょっとぐらいになっていて、そこまでかけて古書で揃えるよりも創元推理文庫で文庫化を待つか…と思っていたのですが、2019年に著者の眉村卓氏が死去。

www.asahi.com

そしてあれよあれよという間に古書価格が高騰(現在はだいぶ落ち着いてますが)

これはもう手を出せないな…古書で買っても権利者にお金が廻るわけでもないし…ということで、復刊待ちも古書で揃えるのも諦め、図書館で借りて読むことに。

ということで感想(ネタバレあり)

続きを読む

『アルスラーン戦記』完走、またはエラム史観の誘惑

ネタバレを含むのでTwitterではなくこちらで。

完走に至るまでの経緯

改めて書く必要も無いとはおもいますが、『アルスラーン戦記』について。
ざっくり言うと、架空世界における中世イラン風のパルス王国(ただし、イスラム教要素なし)を舞台とした大河ファンタジー小説シリーズです。

第一部(1巻から7巻)までは、パルスの王太子であるアルスラーンが、パルス王国へ侵攻してきたルシタニア王国(史実の十字軍に相当)を撃退し、王位に就くまでの物語が語られます。そして第二部(8巻~16巻)では、太古の昔に封印された「蛇王」ザッハークの復活とそれを阻止しようとするアルスラーン王という、第一部と比較するとファンタジー色が全面に出たストーリーとなります。


小説版『アルスラーン戦記』については中学生の頃(1990年代初頭)から読み始めていて、当時の自分にとっては『アラブから見た十字軍』のような視点や、「奴隷を開放するだけでは問題は解決しない。その後の衣食住も含めた解決が必要」といった主張は眩しく思えたものです。流石に2020年時点だと後者はともかく前者のキリスト教観は雑すぎる感じはありますが、しかしながら『アルスラーン戦記』で描かれた中世キリスト教観が踏み台になることで、あとに続く中世キリスト教を扱った諸作品においてより深いキリスト教の描き方が出来たのではないかとも思います*1

ただ、第二部に入ってからは、作者の書こうとしている物語と自分が求めている物語にかなり食い違いが出てきたというか、超自然の怪物や陰謀が大きな力を持つというストーリーなのでどうししても第一部とはノリがかなり違うのが気になって、いつしか新刊を買うのも止めてしまいました。
(あと、90年代中盤ぐらいからの田中芳樹作品は全体的になんというか「失速」していたという印象があって……)

という感じだったのですが、先日ちょっとしたきっかけで荒川弘コミック版『アルスラーン戦記』を読んでいたら、これがまた面白くて面白くて(現在、小説版の第一部中盤ぐらいまで進展してます)。

www.hanmoto.com


これでなんというか「アルスラーン読みたい欲求」が再点火したので小説版も1巻から読み直し、これまで読んでいなかった10巻以降もまとめ買いして読んでみたのですねという経緯。


以下、結末への言及を含むネタバレ注意。

*1:荒川弘コミック版『アルスラーン戦記』では、このあたりのフォローがちゃんと入っていて流石

続きを読む

『「就職氷河期」なんてあったんだろうか?』論について

ブコメだと書ききれないのでここで。

finalvent.cocolog-nifty.com

とあるもののちょっと微妙で。失業率から見てみるとまるで違った光景が見えてくるわけで

統計局ホームページ/労働力調査 長期時系列データ
労働力調査 長期時系列データ の「表3 (4) 年齢階級(5歳階級)別完全失業者数及び完全失業率」から年齢階層別の完全失業率をグラフ化したもの

f:id:ka-ka_xyz:20190407233331p:plain
年齢階層別完全失業率

これを見る限りだと

  1. 失業率を見ると1990年台中盤から2000年台前半までの所謂「氷河期」には24歳までの年齢で特に顕著に他の期間では見られない失業率の上昇と長期の高止まりが発生しており、特異な期間と言って良いのではないか
  2. 大卒者が含まれない年齢階層(15~19歳、グラフの青色系列)でも「氷河期」で失業率は高止まりしており、"安定雇用を求めて増加した大卒者"や"そもそも大学生が増えた分、就職先は争われるようになるだろう。"という話でもなさそう


といえるかなと。失業率だとここまではっきり見えている就職難が有効求人倍率で見えてこないあたりは本職の社会学者の人の守備範囲だと思うが、少なくとも失業率を見ないで有効求人倍率でだけ語るのは無理があると思う。
また、

思い返すと、私が子供のころや青年期でもいつも就職しやすかったわけでもない。当時、就職できなかった人はどうなったかというと、おそらく自営業になっていたのではないか。

とあるものの

https://www.chusho.meti.go.jp/pamflet/hakusyo/h17/hakusho/image/17330320.png
中小企業庁:2.中小企業を巡る環境の変化と開業率

を見ると、1970年台以降ずっと20代の自営業者は少なく、30代以降で増える(まあ元手とかは必要だろうし)データがあって、新規大卒者が自営業者になるというルートは70年台でもかなり珍しいものだったはず。30代以降でも1980年台からずっと減少傾向は続いていて自営業者が減少しているものの、新規大卒者云々とはまた別の話。

Javaコード内でjfrファイルを読もうとすると互換性に気をつけないといけない話

まとめ:

  • jfrファイルをjavaコードから読み込むときは、jfrの監視対象javaプロセスのバージョンに気をつける必要がある。
  • JMCから開く場合には特に気にする必要はない
  • ローカルでざっくり見た結果だから保証はしないよ

Java Flight Recorder (JFR)とは

Java Flight Recorder (JFR)は実行中のJavaプロセスのプロファイルを取得するツールです。
現在のところは本番環境で使用するには商業利用ライセンスが必要ですが、本番ではない環境でのパフォーマンス問題調査に無くてはならないものです。

Java Flight Recorderについて

注意:
Java Flight Recorderを本番で使用するには、商用ライセンスが必要です。

将来的にJava Mission Control (JMC)がオープンソースへ移行することでJFRの商用ライセンス縛りも外れるのではないか...と言われていますが、Oracleからその辺あんまり詳しいアナウンスは出ていないのでよくわかりません。
JMC Open Sourced! – Marcus Hirt


で、このJFRで生成される*.jfrファイルは、通常はJMCから読み込むのですが、実はJavaコード中で読み込むことも可能です。しかし...というのが本題。

jfrファイルを読み込む方法

1. jfr.jarを使用する (java8まで)

${JDK_HOME}/jre/lib/jfr.jar にclasspathを通すと、jfrファイルを読み込むAPIを参照できます。
読み込みはざっくりこんな感じ。

非公開APIなので注意すること。
あと、JFRファイルはデフォルトで圧縮がかかっているようなので、読み込む前に解凍処理を忘れずに。

2. java jfr APIを使用する

Java9以降、jfrを読み込むためのAPIが公開APIとして実装されました。
jdk.jfr (Java SE 9 & JDK 9 )

読み込みはざっくりこんな感じ

バージョン互換問題

Java9以降であれば公開APIを使えば良いんじゃないかと思っていましたが、思わぬ罠がありました。

測定条件は以下の通り

  • java1.8 (1.8.172)、java9 (9.0.1) 、java10 (10.0.1)でjavaアプリ(apache jmeter)を立ち上げ、JFR取得対象とする
  • java1.8、java9、java10それぞれのjcmdを使用して、上のそれぞれのアプリについてのJFRを取得する
  • 上で貼ったコードをjava9環境で実行する(java8 のjfr.jarへclasspathを通した状態)

結果は以下の通り

取得対象Java version jcmdのJava version jfr.jarによる読み込み jfr APIによる読み込み
java1.8 java1.8 問題なし エラー1
java1.8 java9 問題なし エラー1
java1.8 java10 問題なし エラー1
java 9 java1.8 エラー2 問題なし
java 9 java9 エラー2 問題なし
java 9 java10 エラー2 問題なし
java 10 java1.8 エラー3 問題なし
java 10 java9 エラー3 問題なし
java 10 java10 エラー3 問題なし

つまり、jfr.jarはjava9以降のプロセスから取得したjfrを読めないし、一方でjfr APIはjava1.8プロセスから取得したjfrを読めないという話ですね。そして、取得対象のバージョンには依存するものの取得する側のjcmd(多分JMCも)のバージョンには依存しない。
ローカル環境でしか検証していないけれど多分あってるはず。

エラー1の内容は以下の通り。

java.io.IOException: File version 0.9. Only Java Flight Recorder files of version 1.x can be read by this JDK.
	at jdk.jfr/jdk.jfr.internal.consumer.ChunkHeader.<init>(ChunkHeader.java:56)
	at jdk.jfr/jdk.jfr.internal.consumer.ChunkHeader.<init>(ChunkHeader.java:38)
	at jdk.jfr/jdk.jfr.consumer.ChunkParser.<init>(ChunkParser.java:35)
	at jdk.jfr/jdk.jfr.consumer.RecordingFile.findNext(RecordingFile.java:181)
	at jdk.jfr/jdk.jfr.consumer.RecordingFile.<init>(RecordingFile.java:63)
	at jdk.jfr/jdk.jfr.consumer.RecordingFile.readAllEvents(RecordingFile.java:168)
	at jfr_example.Jdk9JfrReader.read(Jdk9JfrReader.java:31)
	at jfr_example.Main.main(Main.java:23)

エラー2

java.lang.RuntimeException: oracle.jrockit.jfr.parser.ParseException: Bad descriptor section, id=65536
	at oracle.jrockit.jfr.parser.Parser$1.hasNext(Parser.java:165)
	at jfr_example.Jdk8JfrReader.read(Jdk8JfrReader.java:31)
	at jfr_example.Main.main(Main.java:18)
Caused by: oracle.jrockit.jfr.parser.ParseException: Bad descriptor section, id=65536
	at oracle.jrockit.jfr.parser.ChunkParser.readDescriptors(ChunkParser.java:555)
	at oracle.jrockit.jfr.parser.ChunkParser.begin(ChunkParser.java:230)
	at oracle.jrockit.jfr.parser.ChunkParser.<init>(ChunkParser.java:95)
	at oracle.jrockit.jfr.parser.Parser.next(Parser.java:124)
	at oracle.jrockit.jfr.parser.Parser$1.hasNext(Parser.java:163)
	... 2 more

エラー3

java.lang.RuntimeException: java.nio.BufferUnderflowException
	at oracle.jrockit.jfr.parser.Parser$1.hasNext(Parser.java:165)
	at jfr_example.Jdk8JfrReader.read(Jdk8JfrReader.java:31)
	at jfr_example.Main.main(Main.java:18)
Caused by: java.nio.BufferUnderflowException
	at java.base/java.nio.Buffer.nextGetIndex(Buffer.java:634)
	at java.base/java.nio.DirectByteBuffer.getInt(DirectByteBuffer.java:686)
	at oracle.jrockit.jfr.parser.MappedFLRInput.getInt(MappedFLRInput.java:79)
	at oracle.jrockit.jfr.parser.ChunkParser.readDescriptors(ChunkParser.java:552)
	at oracle.jrockit.jfr.parser.ChunkParser.begin(ChunkParser.java:230)
	at oracle.jrockit.jfr.parser.ChunkParser.<init>(ChunkParser.java:95)
	at oracle.jrockit.jfr.parser.Parser.next(Parser.java:124)
	at oracle.jrockit.jfr.parser.Parser$1.hasNext(Parser.java:163)
	... 2 more

なので、JFRをjavaから読む何かを書くときにはこの辺を気をつけないと「一年前に取得したJFRファイルが読めねー!」って事になるので注意が必要です。
あと、jfrファイルをJavaコードではなくJMCで開く場合には、特に問題は発生しません。

おーい磯野ー、Flashpoint Campaignsしようぜ2

前回に引き続き、Flashpoint Campaigns の紹介。"Pied Piper"シナリオのAAR。

store.steampowered.com

このAARについて

前回書いたとおり、このゲームでは一度部隊が交戦状態に入ってしまうと、なかなか指示に従ってくれません。
なので、プレイ中にプレイヤーが能動的に介入できる余地が地味というか、予備兵力の投入タイミングや間接砲撃のターゲット指定ぐらいかと。じゃあ何が面白いのかというと、事前に敵の進撃ルートを予測して兵力と予備兵力を配置していく過程がメインな気がします。事前の予想がハズレた場合に計画を修正していくあたりも面白いんですが、最初の読みが多分一番重要で面白い所。

ということで、このAARでは事前の作戦立案をメインに書いていきます。

Pied Piper シナリオについて

プレイヤーはNATO側。

開戦一日目。ソ連第一親衛戦車軍の遠距離偵察部隊は東西ドイツ国境線を突破し、ウェーザー川にかかるハーメルンの幹線道路橋を目指して進撃中。一方、NATO側が現在ハーメルンに展開している戦力は乏しいが、一時間程度で増援部隊が到着する予定。NATO側はソ連のウェーザー川渡河を阻止するために必要な可能な限りあらゆる手段を取ること。

敵の進撃ルート予想

f:id:ka-ka_xyz:20180401201039p:plain

幹線道路沿いの進撃ルートAを中心として、その両脇B, Cからもハーメルン市街と橋に向けて進撃してくると予想されます。
青矢印1は見方の増援ルート(詳細は後述)。

見方兵力

f:id:ka-ka_xyz:20180401201224p:plain

初期配置戦力は歩兵(+歩兵戦闘車)三個小隊と戦車三個小隊。ここで気をつけなければならないのが、戦車がレオパルド1であること。ゲームのシナリオである1989年においては明らかに二線級戦力であり、ソ連側の主力であるT-80と正面から打ち合ってはまず勝てません。

f:id:ka-ka_xyz:20180401203936p:plain
また、意思決定サイクルについても、NATO側16分に対しソ連側23分と、NATO側が優位では有るものの戦力差を覆すような大きな差は無いです。

f:id:ka-ka_xyz:20180401202106p:plain

ゲーム内時間で一時間ほど経過すると、M-109自走砲二個小隊が到着します。さらに20分後にレオパルド2が配備された戦車中隊(三個小隊+司令部)が到着し、さらにその20分後には歩兵三個小隊が到着します。増援はウェーザー川西岸に出現するので、戦車や歩兵については東岸へ移動して戦闘に加入するためにさらに30分程度の時間が必要です。

敵兵力

シナリオ開始時の情報によると、戦車80両以上+歩兵戦闘車70両程度を中心とした戦車旅団。

AAR: 作戦プラン1

上で書いたとおり、そもそも数的に劣勢である上に初期配置されているレオパルド1ではどうあがいてもT-80には太刀打ち出来ません...が時間をかせぐ必要があるということで、予想進撃ルートにそれぞれ歩兵+戦車各一個小隊のセットで防衛ラインを構築します。

f:id:ka-ka_xyz:20180401201835p:plain

また、ハーメルン東方を流れるウェーザー川支流にかかる橋は片っ端から爆破して時間を稼ぎます。(30分程度で復旧されるけれど)
防衛ラインAが突破されたら、防衛ラインBとCを下げてハーメルン市街で防衛ラインを作る...のは多分無理。現有戦力だと予備は作れないので、増援がくるまでとにかく死守。一時間半後に増援が来たらハーメルン市街外縁に防衛ラインを引き、迎撃する予定。

ということでシナリオスタート
1時間40分経過時の各防衛ラインの様子

f:id:ka-ka_xyz:20180401204452p:plain

侵攻ルートBのソ連部隊には戦車が存在せず、APCやIFVのみでした。また、効果的に構築された地雷原も相まってNATO軍の一方的勝利となっている模様(小さい煙が上がってるのが、撃破されたユニット)。ただし、ほぼ弾薬が尽きているので再補給の必要あり。

f:id:ka-ka_xyz:20180401204631p:plain
侵攻ルートA(画面右側)およびC(画面左側)について。ソ連軍のT-80戦車が現れてからはかなり形勢不利で突破されそう。ルートCから部隊を回して対応することに。

一方で、マップ西側には増援部隊が到着し、ハーメルン市街へ展開すべく高速移動中。ルートAが突破されるまでにハーメルン市街外縁に防御ラインを敷くことが出来るかどうかは微妙なところ。

3時間経過時の各防衛ラインの様子

増援部隊のレオパルド2中隊がハーメルン市街外縁に防御ラインを敷くことに成功。

f:id:ka-ka_xyz:20180408170805p:plain

500m以下の距離で打ち合うので、T-80を相手にすると損害が出ますが、市街地の防御補正と築城効果があるので何とか優勢です。

3時間47分経過時

ソ連側の損害が一定数を上回ったため、ゲーム終了。
f:id:ka-ka_xyz:20180408172103p:plain

評価的には大勝利。
グラフのバーは各兵科の数で青はNATO側、赤がソ連側。また、左側のバーが開始時、右側のバーが終了時のもの。
ソ連側は戦車以外の戦力がほぼ全滅状態。

AAR: 作戦プラン2

上で書いたとおり、そもそも数的に劣勢である上に初期配置されているレオパルド1ではどうあがいてもT-80には太刀打ち出来ません...が時間をかせぐ必要があるということで、ハーメルン市街に防御ラインを引き、もともと防御側に有利な市街地地形に築城の防御強化を加えて数の差をカバーしようという案。

結果

f:id:ka-ka_xyz:20180408170137p:plain

どうあがいても増援部隊がウェーザー川を渡る前にソ連部隊が市街地を制圧してしまう。渡河を防ぐ事はできるものの、橋の確保は無理。

おーい磯野ー、Flashpoint Campaignsしようぜ

store.steampowered.com

どんなゲームなの?

Flashpoint Campaignsは、1980年代末に発生していたかもしれない第三次大戦での、ヨーロッパ(西ドイツ)での陸戦を扱った戦術級シミュレーションゲームです。
ざっくばらんに言うとあれです。『レッドストーム・ライジング』とか『第三次世界大戦―チーム・ヤンキー出動』とかあの辺り。

レッド・ストーム作戦発動(ライジング)〈下〉 (文春文庫)

レッド・ストーム作戦発動(ライジング)〈下〉 (文春文庫)

第三次世界大戦―チーム・ヤンキー出動 (二見文庫―ザ・ミステリ・コレクション)

第三次世界大戦―チーム・ヤンキー出動 (二見文庫―ザ・ミステリ・コレクション)

ゲーム画面
f:id:ka-ka_xyz:20180325192340p:plain
アウトバーンのジャンクションへ殺到するワルシャワ条約機構軍(WPO)(赤ユニット)と、押し止めようとするNATO軍イギリス部隊(黄色ユニット)。NATO軍は主正面では押されつつあるが、WPO部隊の後方へ回り込んだ部隊による包囲網を完成しつつ有るところ。
1ヘクスがだいたい500m

ちなみに現在の実際の地形はこんな感じ

なかなかになまなましい。

システム面での特徴について

意思決定サイクル

ターン開始時にユニットへ命令を出し、次のターンが始まるまでの間はセミリアルタイムで時間が進行していきます。その間は命令を追加したり変更したりすることは出来ません。

f:id:ka-ka_xyz:20180325193218p:plain
で、画面右に表示されてたこのパネル(C3: Command, Control and Communication)なんですが、このゲームの一番のキモになります。
黄色いバーは、「NATO側は命令を発行するまでの時間間隔が17分である、更に次に命令を発行出来るまでの予想間隔は16分である」ことを意味しています。
一方、赤色のバーは「WPO側は命令を発行するまでの時間間隔が48分である。更に次に命令を発行出来るまでの予想間隔は36分である」ことを意味しています。将棋で例えるならば、NATO側はWPO側が一手打つ間に三手打てるということです。あるいはミリヲタ的に表現をするなら「NATO側はWPO側と比べてOODAループを三倍速く回せる」ということを意味します。

この意思決定サイクルの速さの差を見るとNATO側が圧倒的優位に見えますが、ところがどっこい、このシナリオではWPO側の戦力はNATO側の二倍程度あるので、NATO側が下手を打つとあっという間に数の力で押し切られます。
また、この値は電子攻撃(EW Hindrance)による通信妨害や司令部ユニットが攻撃を受けたり壊滅したりしているかどうか、あるいは各部隊が準備万端か(unit readiness)どうかなどにより大きく変動します。例えば、敵司令部ユニットを間接砲撃で混乱させたり、敵の後方へ浸透して司令部ユニットを戦闘に巻き込むことで相手の意思決定を麻痺させるといった戦術を取ることも出来ます。

組織階層

f:id:ka-ka_xyz:20180325204107p:plain
OB(Order of Battle)タブには組織階層が表示されます。このマップでは最高司令部ユニットが"TF Hell's Messanger"、その下に"4RTR (4th Royal Tank Regiment)"司令部ユニットがあり、更にその配下にA/4RTR, B/4RTR, CRT/4RTR 等の司令部ユニットが存在し、更にその配下には戦闘を行うための部隊が存在することが示されています。

司令部ユニットには通信範囲があり、配下の司令部や部隊をカバーしている必要があります。また、部隊が補給を受ける際には司令部ユニットの近くで行う必要があるため、効率的に部隊を使用するために組織階層を色々と変更する必要があります。
例えば歩兵大隊に戦車一個小隊を分散配置するとか、あるいは分散されている戦車小隊をまとめて中隊として使用するなどなど。そのマップでどのように戦闘を展開していくのか、計画を立てた上で組織階層を変更してください。

命令は聞いた...が、今すぐ実行するとは言っていない......

上の意思決定サイクルについての話で、「命令を下す」話をしましたが、このゲームでは命令を出したとしてすぐに実行されるとは限りません。
例えばある部隊が目の前の敵と打ち合っているときに移動命令が出たとしても、「そんなのより今は眼の前の敵と戦うほうが先」な感じでなかなか言うことを聞いてくれません。

基本的に、部隊を思うように動かせるのは交戦が始まる前までで、実際に交戦が始まってからはケリがつくまでの間は「部隊を動かせない」と思っておいたほうが良さげです。そのため、防御側は交戦が始まる前に如何に有利な地形(遮蔽物が多い森林や市街地ヘクスで、かつ標高が高くて敵の予想進路に対する見晴らしがよく、更に後退する際に射線が通らないような場所)を確保することが死活問題となります。そのような地形を確保できれば、小部隊で敵の大部隊を長時間拘束することが可能となります。逆に、有利な地形を確保できないままでなし崩し的に交戦が始まった場合...兵力が大きなWPO側にとっては一気に押せるチャンスとなりますが、兵力が少ないNATO側にとってはほぼ負けが確定する感じです。

あと、戦闘に巻き込まれていない予備兵力は超大事。

追記: 上で書いたのは部隊に"Hold"(確保)を指示した場合の挙動。"Screen"(前衛)を指示すると射撃後に移動してくれますが、これはこれで防衛ラインを作って一定時間保持するといった用途には使えません。

補給ルール

ユニットはそれぞれ燃料・弾薬をもっていて、20分~30分程度交戦すると弾薬が空になります。
司令部ユニットの近くにいると再補給が出来るのですが、問題は再補給中には攻撃に対してかなり脆弱となること。敵の射線が通らない場所まで後退して補給を受けるべきなのですが、上で書いたように後退命令を出しても即時に実行してくれるとは限りません。ということで、煙幕等を張ることで射線を一時的に遮ってそのすきに交代するか、リスクを承知でその場で再補給を行うかという判断を行う必要があって悩ましい。

戦車の強さ東西比較について

このゲームではあくまで80年台後半の欧州を扱っており、湾岸戦争のような西側戦車の圧倒的な性能の優位は再現されていません。長距離で射撃を行っているうちは西側第3世代戦車の方が優位ですが、ある程度距離が詰まると東側戦車との性能差は縮まっていきます。そして殆どのマップではWPO軍の戦車はNATO軍の数倍程度の数の優位があります。NATO側はこれを踏まえて「如何に効率的にキルゾーンを形成するか」という方針で戦車を使っていくべきです。


AAR書こうと思ったけどここで力尽きた

次回に続く

JavaでGPU演算(Aparapi)してみた

今更感は有るが、Aparapi触ってみた。特に何か使う予定は無いけど、寒さに負けて生賴範義展に行けなかったのでなんとなく。



(1/31追記) 社内LTで再利用.

4/17追記

と指摘を受けたのでgithubのリンク先を https://github.com/Syncleus/aparapi に修正。



Aparapiとは

github.com

aparapi.com

JavaVMからOpenCL API経由でGPU演算を行うライブラリ。GPU側へ持っていけるのはJava プリミティブ型の配列のみだけれど、簡単に書ける。

とりあえず、任意の画像をグレースケール化する処理をループで書いた場合とAparapiで書いた場合を比較。

JVM内でループを回した場合

  private void convertToGrayScaleInJvm(final int[] pixels) {
    for (int i = 0; i < pixels.length; i++) {
      int pixel = pixels[i];
      int alpha = pixel >> 24 & 0xFF;
      int red   = pixel >> 16 & 0xFF;
      int green = pixel >>  8 & 0xFF;
      int blue  = pixel & 0xFF;
      int y = (int)(0.298912 * red + 0.586611 * green + 0.114478 * blue);
      pixels[i] = alpha << 24  | y << 16 | y << 8 | y;
    }
  }

Aparapiを使ってGPU演算した場合

  private void convertToGrayScaleInGpu(final int[] pixels) {
    
    Kernel kernel = new Kernel() {
      @Override
      public void run() {
        //GPU world
        int i = getGlobalId();
        int pixel = pixels[i];
        int alpha = pixel >> 24 & 0xFF;
        int red   = pixel >> 16 & 0xFF;
        int green = pixel >>  8 & 0xFF;
        int blue  = pixel & 0xFF;
        int y = (int)(0.298912 * red + 0.586611 * green + 0.114478 * blue);
        pixels[i] = alpha << 24  | y << 16 | y << 8 | y;
      }};
      int size = pixels.length;
      kernel.execute(Range.create(size));
  }

グレースケール化の方法については

https://ofo.jp/osakana/cgtips/grayscale.phtmlofo.jp

を参考とした。
ソース全体はこちら

https://gist.github.com/ka-ka-xyz/232bc0368ebf3c44bb46a7e1cb03810b

Aparapi注意点

JNI経由でOpenCLへアクセスするためにはネイティブライブラリが必要らしい。

aparapi/QuickReference.pdf at master · aparapi/aparapi · GitHub

無くても動くけれど、GPUは使わないよ的な事が書かれてた。 *1
あと、このPDFではAparapi関連のシステムプロパティのキー名が`com.amd.aparapi` で始まっているけれど、現在のv1.4.1では`com.aparapi` になっているので注意。

今回はここからdllを取得したけれど、これで良いんだろうか...
github.com

dllなりsoなりが存在するフォルダをJVMシステムプロパティ `java.library.path` で指定すれば読み込まれる。

実際に動かしてみる

www.pexels.com

から取得した画像をあれこれリサイズしてグレースケールへ変換してみた。

カラー版
f:id:ka-ka_xyz:20150913194329j:plain

グレースケール化した画像
f:id:ka-ka_xyz:20180128235338j:plain

ほんとにGPU呼んでるの?

NVIDIS GeForce GTX 1050の負荷をWin標準のパフォーマンスモニタで見てみた画像。
f:id:ka-ka_xyz:20180128235601p:plain

実行時にGPUさんが忙しそうにしてたので、呼ばれてるんだろう。多分。

パフォーマンスはどうよ?

f:id:ka-ka_xyz:20180128235656p:plain

X軸が画像のピクセル数、Y軸が処理にかかった時間(ms)。この時間はあくまでグレースケール化の処理時間で、ファイルの読み書きについては勘定していない。青がGPU演算のパフォーマンス。オレンジがJVM内での演算パフォーマンス。画像ピクセル数をあれこれ振り、CPU・JVM各5回の処理時間を取得して平均値をグラフ化。

グラフを見ると、GPU処理のためのオーバーヘッドがかなり高く、画像のピクセル数が少ないとJVMの中でループ回してたほうが圧倒的に速い。
13500x8598pxぐらいのサイズだとまだJVM内の処理が僅かに速く、18000x11464pxではGPU側が速い。

GPU演算だと並列処理のはずなのに計算量がO(N)に見えるのが気になる。仮説として

  • 処理自体は瞬時に終わるもののGPU側のメモリとJavaヒープとの間でのデータ転送に大半の時間が消費されている

と憶測してる(未検証だけど、上で挙げたパフォーマンスモニタはそれっぽく見える)。

*1: 一方で 「Aparapi:任意の計算タスクを実行するための新たな “Pure JavaAPIhttps://www.infoq.com/jp/news/2010/10/aparapi-java-and-gpus みたいな記事もあり、正直良くわからない。けど理屈としてはJVMの外のデバイスへアクセスするにはネイティブライブラリは必要だと思う