谷本 心 in せろ部屋

はてなダイアリーから引っ越してきました

100万件ぐらいのレコードを扱ったらOOMEが出た話。

要約

技術的な話だけ教えて、という方のために先に結論だけ書いておきますと、PostgreSQLはクエリを実行した時点で全レコードの情報を一気に読んできてヒープを埋めてしまう場合がある、ということ話です。
たとえば、ResultSet#nextメソッドを使いながら処理を回すようなコードを書いて、少ないヒープでも処理できるようにするのは常套手段だと思いますが、そういうコードを書いていても一気にヒープを消費してしまうことがあるのです。詳しくはこのドキュメントを見てください。

https://jdbc.postgresql.org/documentation/head/query.html#query-with-cursor

ことの発端

ちょっと仕事でJava + jOOQ + PostgreSQLで、DBのデータを集計するようなバッチ処理を書いてまして、もちろん俺様の書いたコードにバグなんてあるはずがないわけですが、念のために性能試験をしておこうと思い、100万レコードのサンプルデータを投入してバッチのテストを走らせたら、OutOfMemoryErrorが起きたんですよ。

OutOfMemoryError。まさか、俺が?

戸田奈津子の字幕っぽいセリフが頭をよぎりつつ、何度か試してみてもやはりOutOfMemoryErrorが起きてしまいます。ヒープを2GBとかにすれば実行できますし、レコード数を50万とかに減らせば問題なく動くのですが、そもそも何万レコード扱おうがヒープを大量確保するようなコードなんて書いていないつもりだったので、真面目に問題を確認することにしました。

何がヒープを埋めているって言うんだい?

僕も素人ではないですから、即座に起動引数に -XX:+HeapDumpOnOutOfMemoryError をつけてテストを再実行し、ヒープダンプを取得しました。えーっと、取得したヒープダンプって何で読むのが良いんでしたっけ(←素人)

Java Mission Controlで読めたはず、と思ってググったら、今は名前が変わってJDK Mission Controlになっていたのですが(←素人)細かいことは気にせず、ダウンロードして実行して、File - Open Fileからヒープダンプのhprofファイルを読み込みました。
↓こんな感じでヒープを占めるオブジェクトの傾向が分かります。

f:id:cero-t:20200812214730p:plain
JMCでヒープダンプを開くとこういう情報が分かる

一番上にある byte[][] がヒープの7割も占めていて明らかに怪しいので、ダブルクリックして詳細を見ます。
↓こんな感じでどこから参照されているかが分かります。

f:id:cero-t:20200812214806p:plain
オブジェクトの親子関係も追跡できる

どうやら org.postgresql.core.Tuple.data から参照されているが分かり、このパッケージ名からやはりDBから読み込んだ100万レコードがヒープを埋めているであろうことがほぼ確定しました。

問題はどこで起きてるんだ!

今度はアプリケーションの方から問題を追跡します。デバッガで少し追ってみると、問題が起きている箇所がすぐに分かりました。

try (Stream<EMP> stream = dslContext.selectFrom(EMP).stream()) {
    stream.forEach(e -> {
        // 集計処理
    })
}

dslContextというのはjOOQのDslContextクラスのインスタンスです。この処理で select * from EMP が実行されて、その結果をStreamとして取得しているわけです。
ただここで // 集計処理 と書いた部分には入っておらず、forEachを呼び出した時点でOutOfMemoryErrorが発生していることが分かりました。

なんてこった、jOOQのstreamメソッドは逐次処理をするんじゃなくて全件まとめて取ってくるヤツなのかい? どんな幼稚な処理を書いたらそうなるんだい? jOOQだからってジョークじゃ済まないよ? と怒りに震えながら、ツイートをしました。

後にこれは完全にとばっちりだったことが分かったのですが、シンガプーラ使いさん(シンガプーラって何ですか?)と、jOOQの公式アカウント様からもリプライいただいてしまい、大変恐縮しております。申し訳ありませんでした。

fetchSize()が効かない、だと!?

公式アカウント様が「streamメソッドはLazyローディングだよ。場合によっては fetchSize メソッドを使うと良いよ」とおっしゃるので、まずそれを試してみました。

try (Stream<EMP> stream = dslContext.selectFrom(EMP).fetchSize(1000).stream()) {
    stream.forEach(e -> {
        // 集計処理
    })
}

しかし状況は変わりません。fetchSizeを1にしてもOOMEが発生してしまいました。

次にシンガプーラ使いさんから教えてもらった fetchLazy メソッドも試してみました。

var cursor = dslContext.selectFrom(BUDGET_REQUEST).fetchSize(1).fetchLazy();
while (cursor.hasNext()) {
    // 集計処理
}

それでも状況は変わりません。 この辺りで、問題はjOOQではなくJDBCドライバ周りなのではないかと考え始め、jOOQを外して検証することにしました。

JDBCで問題が再現しちまった!

僕もO/Rマッパーを自作するタイプの人間ですから、生JDBCで処理を書くのもお手の物です。こんなシンプルなコードを書いてみました。

try (Connection conn = dataSource.getConnection();
        PreparedStatement stmt = conn.prepareStatement("select * from EMP");
        ResultSet resultSet = stmt.executeQuery()) {
    while (resultSet.next()) {
        // 集計処理
    }
}

うんとこしょ、どっこいしょ、それでもOOMEは消えません。これでjOOQは関係なく、PostgreSQLJDBCドライバ周りの問題だろうと絞り込めました。

ドキュメント、あったよ!

先に書いたシンプルなコードですらOOMEが発生してしまう状況になってくると、私が何か勘違いしているのかも知れないな、なんて一人称まで謙虚になってきます。何かPostgreSQLでは特別なお作法があるのだろうかと思い、「postgresql jdbc lazy loading」でググってみたところ、みんな大好きStack Overflowにたどり着きました。

https://stackoverflow.com/questions/984073/java-jdbc-lazy-loaded-resultset

Another example: here's the documentation for the PostgreSQL behavior. If auto-commit is turned on, then the ResultSet will fetch all the rows at once, but if it's off, then you can use setFetchSize() as expected.

!?!?

ResultSet will fetch all the rows at once

まさに今回起きている現象そのものです。リンクされていたドキュメントを読んでみると、明確に書かれていました。

https://jdbc.postgresql.org/documentation/head/query.html#query-with-cursor

By default the driver collects all the results for the query at once.

はぁ〜!? なんでそんな実装にしとんねん!!!
問題の原因が分かった喜びよりも、この理不尽な実装に対する怒りの方が大きかったです(←理不尽な怒り)

これで解決だな

ドキュメントから、関係ありそうな部分を抜粋しますと。

Cursor based ResultSets cannot be used in all situations. There a number of restrictions which will make the driver silently fall back to fetching the whole ResultSet at once.

  • The Connection must not be in autocommit mode. The backend closes cursors at the end of transactions, so in autocommit mode the backend will have closed the cursor before anything can be fetched from it.

どうやらautoCommitがtrueになっている場合は、カーソルが使えないようです。そんなわけで Connection#setAutoCommit(false) したところ、、、jOOQの fetchSize() メソッドが効くようになって、OOMEが発生しなくなりました!

そもそもなぜautoCommitがtrueになっていたかと言うと、

  • バッチ本体では setAutoCommit(false) してから処理していたが、テストではロジックの中心部分だけ直接呼び出していた
  • テストでは面倒だったのでautoCommitをデフォルト値(true)のままにしていた

という、非常に運の悪い状況になっていました。 いやだって、autoCommitのtrue/falseでそんなに挙動が変わるなんて想像もしてなかったんだもん・・・。

さらに、Spring Boot + jOOQ + PostgreSQLで書いていたWebアプリケーションにおいてもautoCommitがtrueのままになっていました。トランザクションが効いている限りはautoCommitがtrueのままになることはあり得ないのですが、実はSpring Boot + jOOQの組み合わせでは、たとえ spring-boot-starter-jooq を使っていても、そのままでは @Transactional アノテーションが効かない状態になっているのでした。

https://www.baeldung.com/spring-boot-support-for-jooq

いやまさか、Spring Bootとの連携が公式に謳われているようなライブラリで、@Transcational が効かないだなんて想像もしてなかったんだもん・・・。

まとめるぞ!

そんなわけで、今回のまとめです。

  • PostgreSQLはfetchSizeを指定しないと、ResultSetに検索結果が全て入る
  • PostgreSQLはautoCommitがtrueだと、fetchSizeを指定しても効果がない
  • そもそもサボってautoCommitをtrueのままにしてたのが、すべてのきっかけだったわけじゃん
  • でもそのおかげでSpring BootとjOOQのトランザクションサポートの課題も見つかって良かったね!

という感じで、原因の追及と対応に丸一日かかりましたが、良い経験になりました。

メッシュルーターの選び方について整理した。

急に冷え込んだ昨今、さすがにこの寒さではメッシュの服など着ることができませんので、せめてルーターだけでもメッシュにしてはどうでしょうか。
なーんて、寒さに拍車を掛けかねない妖怪、@cero_t です。みなさまお風邪など召されておりませんでしょうか。

さて、今年のはじめ頃に、Linksys Velopというメッシュルーターを買いました。 cero-t.hatenadiary.jp

メッシュルーターというのは、複数のルーターで同じSSIDを使い、家中でどこにいても快適にWi-Fiが使えるというものです。既存のルーターを複数使って、ただただ同じSSIDを使うだけだと、電波の悪いルーターに繋ぎっぱなしになることがありますが、メッシュルーターであれば端末さえ対応していれば(大体対応してる)通信状況の良いSSIDが選択されます。

そんなわけで実際にメッシュルーターを使って色々試し、なるほどこれは便利だなという部分と、なんとこれは知らなかったなみたいな部分があり、様々な経験値を貯めていたところ、ちょうど実家のほうからも「Wi-Fiが不安定だから何とかしたい」という声が届きまして、このタイミングでメッシュルーターの選び方や構成について改めて整理することにしました。

製品選び

この1年で、だいぶメッシュルーター製品が増えてきています。GoogleからもGoogle Wifiに続きGoogle Nest Wifiという よく分からない 製品が出てきましたし、2019年末の現在は、Wi-Fi 6(802.11ax)対応がちょうど出てくるタイミングでもあります。
ただWi-Fi 6対応製品はまだまだ高価なのと、自宅にも実家にもWi-Fi 6対応デバイスがないので、今回はWi-Fi 6対応製品のことは考えないことにしました。

そうやって選択肢が増えてくると、どの製品を選んで良いか分からなくなりがちですので、まずは選び方について整理します。 既製品リストは、こちらのサイトが詳しいです。 24wireless.info

製品を選ぶ時に気をつけるポイントは、まず2つ、「有線LANの有無」と「契約している回線(のルーター)」です。

(Q1) 各部屋に有線LANが配線されているか?

1つめの問は、各部屋に有線LANが配線してあり、メッシュルーター間を有線でつなぐことができるかどうかです。

  • できる → 「有線バックホール」に対応した「デュアルバンド」製品を選ぶ
  • できない → 「トラインド」製品を選ぶ

有線LANを使ったメッシュルーター間の通信は「有線バックホール」または「イーサネットバックホール」と呼ばれています。有線LANが配線されている家であれば、この機能を使ったほうが通信の速度も品質も高くなります。ちなみに無線を使った通信は「無線バックホール」「Wi-Fiバックホール」と呼ばれています。

また、有線バックホールを使う場合に、基本的には「トライバンド」対応製品を選ぶ必要はなく、「デュアルバンド」対応製品を選べば良いようです。トライバンドのうち1バンドはメッシュルーター間の通信に使わるようで(そうでない製品もあるかも知れませんが)、割高なトライバンド製品を買っても性能がコストに見合わなくなるためです。
逆に有線LANが使えない場合には、トライバンド製品を選んだほうが無難でしょうね。

(Q2) インターネット回線はどれか?

2つめの問は、インターネット回線の接続方式についてです。接続方式とルーター機能の有無によって選ぶべき製品が変わります。

  1. フレッツ光eo光など、IPv4のPPPoE接続
  2. フレッツ光プラスなど、IPv6のIPoE接続
  3. auひかりやNURO光など、ホームゲートウェイルーター機能をOFFにできないタイプ

これによって、次のように選ぶ製品が変わります

  • 1 → どれを選んでも良い。高機能/高性能なルーターを選ぶとルーターの機能を活かせる
  • 2 → 「フレッツ網でのIPv6 IPoE接続」に対応した製品を選ぶ(Buffalo、ELECOM、Synologyのいずれか)
  • 3 → 「ブリッジモードでのメッシュNW構築」に対応した製品を選ぶ。ルーター自体の性能は活かしにくい

インターネットガチ勢の皆様におかれましては、フレッツ光を使っておきながらIPv4に甘んじることはまず考えられないと思いますので、フレッツ光においては選択肢の「1」は消えて「2」になりますので、Buffalo、ELECOM、Synologyの3社のうちのどれかを選ぶ形になります。

それ以外、たとえばeo光auひかり、NURO光などの場合は、どれを選んでも構わないことになります。
eo光はホームゲートウェイルーター機能をOFFにできますので、高機能/高性能なメッシュルーターを使うと良いでしょう。
一方、auひかりやNURO光なら安めのやつで良いかなという感覚です。

逆に言えば、メッシュルーターが持つQoS設定やアクセス制御、セキュリティ機能などを使いたい場合であれば、ISPが提供するホームゲートウェイルーター機能はOFFにする必要があります。ただ、その場合に 3 のようなISPを使っているとルーター機能はOFFにできない(あるいは二重ルーターにせざるを得ない)ので、ISPごと乗り換えてしまいましょう、みたいな話になるので注意してくださいね。

ちなみに僕はISPごと乗り換えることにしました。

接続構成

続いてメッシュルーターの台数を考えたいところですが、そもそもメッシュルーターをどのように構成するかによって、必要な台数は変わってきます。
先のQ1、Q2の結果によって、構成は次の3パターンに分けることができます。

f:id:cero-t:20191202204847p:plain
メッシュの接続構成。妖怪ザツナテガキのせいでこんな絵に・・・

すみません本当は綺麗な絵を描いていたはずなのですが、どこからともなく現れた妖怪ザツナテガキに襲われてしまって、絵がこんな悲惨なことになりました。。。ちくしょう、妖怪ザツナテガキめ。。。

パターン1 : 無線接続(Q1が「できない」、Q2は何でも良い)

無線で接続する場合、インターネット回線を家に引き込んでいる箇所にメッシュルーターを1台設置します。そして、中継機としてのメッシュルーターを1台ないし2台置いて、家の中にネットワークを構築します。
一般的な大きさのマンションなら合計2台、一般的な大きさの戸建てなら合計3台ぐらいが目安だと思います(一般とは?)

ちなみにホームゲートウェイルーター機能をONにする場合は、メッシュルーター側はブリッジモードにします。ホームゲートウェイルーター機能をOFFにする場合は、メッシュルーター側をルーターモードにします。いずれにせよ接続の構成は変わりません。

パターン2 : 有線接続 ルーターモード(Q1が「できる」、Q2は「1か2」)

有線で接続し、メッシュルータールーターモードで使う場合は、インターネット回線を家に引き込んでいる箇所にメッシュルーターを1台設置します。そして、HUBを経由して電波が届きにくい部屋にメッシュルーターを1台ないし2台設置します。
やはりこれでも一般的な大きさのマンションなら合計2台、一般的な大きさの戸建てなら合計3台ぐらいが目安だと思います。

パターン3 : 有線接続 ブリッジモード(Q1が「できる」、Q2は「3」)

有線で接続し、メッシュルーターをブリッジモードで使う場合は、インターネット回線を家に引き込んでいる箇所にメッシュルーターを設置する必要はなく、よく使う部屋だけ設置するか、家全体をうまくカバーできることを想定して2台ほど設置します。もちろん、インターネット回線を引き込んでいる場所をよく使うのであれば、そこに1台目のメッシュルーターを置いても構いません(HUB経由での接続)

この場合、わりと2台でもカバーできることが多いため、2台で様子を見てみて、どこか局所的に弱い箇所があれば3台目を追加する形が良いんじゃないかと思います。

コスパ高めな製品

最後に、僕が実際に買ったり、買おうと思っていた製品を紹介しておきます。僕はコスパ厨なので、コスパの良い製品のリストになります。

TP-Link Deco M5 V2 / Deco M4

とにかく安い。デュアルバンドとは言え、3台で2万円前後という価格なのは、この製品ぐらいじゃないですかね。
有線LANが配線してある家であれば、この製品が最有力候補になります。僕の実家にはM5を入れています。

NETGEAR Orbi Micro

とにかく安い、その2。トライバンド3台で3.5万円を切ってるし、何だったら先日セールで3万円を切っているのも見ました。
無線LANで使う時の有力候補。セールの時に買おうかと迷ってたら、売り切れちゃいました。てへぺろ

Synology MR2200ac

少しお高い。ただ、IPv6対応しているので、フレッツ光IPv6で使うならこれかなと思っているやつです。NASで著名なメーカーだけに、NAS機能も充実してて面白いです。

また、親となるルーターを Synology RT2600ac に変えて、ルーター機能を強化することもできます。

この辺の、用途に合わせてルーターの強弱をつけられるのは面白いですよね。

Linksys Velop

最初に買ったメッシュルーターです。モノは良いのですが、我が家は上で言うところの「パターン3」なので、ちょっと宝の持ち腐れとなってしまいました。
デュアルバンド3台にしたほうがコスパ良かったかなという感じです。

まとめにかえて

メッシュルーターは凄く快適に使えますし、値段も2万円前後まで下がってきたおかげで、かなり試しやすくなりました。
比較的新しいタイプの製品なので、製品のライフサイクルも早めだったりしますが、であれば最初から高い製品を買っちゃうのではなく、安めの製品で使い慣れてみて、折を見て上位の製品に買い換えるのもアリかと思います。良いなと思ったら、ぜひ試してみてください。

はぁ、それはそれとして、絵心って欲しいですよね。

それでは!

Dapr + JavaでHello worldしました。

先日、MicrosoftがDaprというプロダクトをOSSで公開しました。

www.publickey1.jp

Distributed Application Runtimeの略だそうで、なんかIstio的な感じなのかなーと思ったのですが「ステート管理」みたいな機能もある所が少し気になって、ドキュメントとかを眺めていると、ステート管理、メッセージング、アクターなど、ただのサイドカーよりも機能が多めになっていて、かつ、分散トレーシングの機能も持っている辺りを見て、あぁなるほどとなりました。ステート管理やメッセージングをプラットフォーム側が持つようになれば、AOPやBCIなしで分散トレーシングができるようになるし、これはアリだなという謎の納得感を得ました。

まぁ細かいことはさておき、Daprのアプローチがちょっと面白いなと思い、Daprそのものというよりも、このようなアプローチがゲームチェンジャーになるのかなと思うところがあったので、少し動かして確認してみようという気持ちになりました。
なんというか、こういう少し未来を先取りしたようなプロダクトが出てくると、良いなってなりますよね。「明日が好きな人だけが、地球を回す」という言葉がありましたが、地球を回しにいきたいと思います。ちょっと意味不明ですね。

そんなわけで、まずはHello worldまでやりました。

Daprのインストール

まず、DaprのCLIをインストールします。先にDockerをインストールしておく必要がありますが、このブログの読者で自分のPCにDockerが入ってない人とかいないですよね? さすがに?

Dockerさえ入っていれば、Macではこれを実行するだけです。

curl -fsSL https://raw.githubusercontent.com/dapr/cli/master/install/install.sh | /bin/bash

他のOSのインストール方法などは、ドキュメントを参照してください。 https://github.com/dapr/docs/blob/master/getting-started/environment-setup.md

このインストールが終わった時点で dapr --version とかをしても、まだエラーになります。先に初期化をする必要があります。

dapr init

これでDapr側は準備完了です。おもむろに dapr --version とかしても大丈夫です。

Micronautのインストール

Dapr上で動くサービスは、HTTPかgRPCで通信を受け取る必要があります。残念ながらJavaでは標準APIだけでHTTPサービスを作るのが面倒くさい(Socketサーバか、com.sunパッケージのHttpServerしかない)ので、Spring BootやMicronautなどを使うことになります。

今回はMicronautを使うことにしました。既に皆さんMicronautもインストール済みだとは思いますが、もし mn --version して command not found になった人は、心から反省して、Micronautのドキュメントを見てCLIをインストールしてください。 https://docs.micronaut.io/latest/guide/index.html#buildCLI

僕も反省しながら次のコマンドを打ちました。

brew update
brew install micronaut

これでMicronautも準備できました。

MicronautでHello worldアプリを作る

micronautでHello worldするWebアプリを作ります。

mn create-app hello-world

これで出来上がった hello-worldsrc/main/java/hello/world フォルダにコントローラークラスを作ります。ソースはほぼ公式ドキュメントからのコピペです。

package hello.world;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;

@Controller("/hello") 
public class HelloController {

    @Get(produces = MediaType.TEXT_PLAIN) 
    public String index() {
        return "Hello, Sofmap World"; 
    }
}

あとは hello-world フォルダで ./gradlew run すれば、Webサーバが立ち上がります。
curl localhost:8080/hello を実行して Hello, Sofmap World とメッセージが出れば完成です。

Dapr上でMicronautを実行

それでは、いま作ったMicronautアプリを、Dapr上で走らせます。コマンドは次のようになります。

dapr run --app-id hello-sofmap --app-port 8080 --port 8000 ./gradlew run

--app-port オプションは中で動作するMicronautが使うポート、--portオプションはDaprの外から呼び出す時のポートになります。そのオプションの後ろに実行コマンドを付ければ、そのコマンドがDapr上で実行されるわけです。とても簡単ですね。

上のコマンドを実行してDaprが起動したら、curlコマンドでDaprのエンドポイントを叩いて処理を実行します。

curl localhost:8000/v1.0/invoke/hello-sofmap/method/hello

これで無事 Hello, Sofmap World が表示されました!

まとめ

総じて、何らハマることなく、DaprのHello worldができました。
先ほど、11月の関ジャバでDaprについて話すことになったので、しばらく、他のステートやアクター、分散トレーシングなども触ってみたいと思います。

それでは、
行こうよ、まぶしい光の世界!

NEW ALIENWARE m15レポート。格ゲー性能は十分 #デルアンバサダー

DELLアンバサダープログラムのNEW ALIENWARE m15(以降、ALIENWARE)レポート第二弾、今回はゲーミングの性能です。 かの平清盛も「ゲームができずんばゲーミングノートにあらず」などと言ったことがよく知られていますね(※言ってない)

ということで、ALIENWAREのゲーミング性能について検証したいと思います。今回も、過去のレポートでも利用していた「Street Fighgter V: Arcade Edition(以降、ストV)」と「FIGHTING EX LAYER(以降、FEXL)」の2本を使い、高画質でも60fpsで安定するかどうかを検証します。
対戦格闘ゲームですから60fpsで動くことは必須であり、あとはどこまで画質を上げられるかの勝負となるわけです。

なお今回利用するALIENEWAREは、Core i7-9750HとGeForce RTX 2070 with MAX-Qを積んでいる、ハイエンドの一歩手前くらいの性能のゲーミングノートです。

ストVを試してみる

ストVを最高画質のフルHD(1920 x 1080)で動かしてみた動画がこちらです。左上に小さいのですがfpsが表示されていて、これが60fpsで安定するかどうかを確認します。

www.youtube.com

読み込み中やキャラクターセレクト画面では少しfpsが落ちますが、これは何の問題もありません。対戦開始後、スーパーコンボを出したあたりのfpsに注目すると、特に問題なく60fpsで安定していることが分かりました。
XPS 15 (GeForce GTX 1050)の時は57fpsくらい、XPS 15 2-in-1 (Core i7-8705G) では50fpsくらいまで下がってしまったことを考えると、さすがはRXT 2070という印象です。

続いて、せっかくの4Kディスプレイを活かすべく、最高画質のまま4K (3840 x 2160) でも動かしてみます。結果は、次の動画のようになりました。

www.youtube.com

さすがにこのスペックでも4Kでのプレイは辛いようで、対戦直後から50fpsぐらいまで下がり、スーパーコンボを出すと40fpsくらいまで下がってしまいました。
4Kでも画質を落とせば動くようになるかも知れませんが、画質を落とすくらいならむしろ解像度を落とした方が良いでしょうから、他の画質では試しませんでした。

FEXLも試してみる

続いて、FIGHTING EX LAYERでも試してみます。まずは最高画質、フルHD (1920 x 1080) です。

www.youtube.com

キャラクター選択、対戦、スーパーコンボ、フィニッシュとも、60fpsで安定しています。たまに61fps、59fpsとなりますが、測定誤差の範囲でしょう。期待していた通り、全く問題ありません。
XPS 2-in-1 では、対戦中に58fpsくらい、フィニッシュ時に48fpsくらいまで低下していましたから、やはり明らかな差があります。

さて、FEXLも4Kにすることができるので、最高画質のまま4K (3840 x 2160) で動かしてみます。

www.youtube.com

これはさすがに厳しくて、設定画面の時点でもう31〜34fpsくらいまで下がってしまいました。これでは対戦中に60fps稼働することが全く期待できないため、ここで止めることとしました。やはり最高画質4Kは、このスペックでは厳しいことが分かりました。
格ゲーを4K最高解像度で楽しむためには、RTX 2080を試すしかないんですかね? 機会があれば試してみたいですね?

液晶の遅延は?

さて、続いては気になる液晶の遅延や残像について調べたいと思います。

HORIのポータブルゲーミングモニターをHDMI接続して、セカンダリディスプレイ(複製表示)とします。そしてLCD Display Checkerの出力を両方の画面に映し、それをスロー撮影して遅延の状況を確認します。スロー撮影にはGoogle Pixel 3 XLの480fps撮影を利用しました。

www.youtube.com

コマ送りすると、おおむね0〜2コマ分ほどALIENWAREの出力が先行していることが分かります。480fpsの2コマ分だとすると1/240秒、つまり最大で0.25F(4.2ms)ほどALIENWAREの液晶の方が表示が早いことになります。
過去にXPS 15やXPS 15 2-in-1と比較した時には、HORIのモニターの方が0.25〜0.5Fほど表示が早かったことと比べると、結果が逆転しています。さすがはゲーミングノートの液晶というところでしょうか。
また、残像の残り方もHORIのモニターよりも少なく、いずれにせよゲーミング用として申し分ないモニターだと思います。

G-SYNCは対応していない

最後に、G-SYNCについてです。
結論から言うと、残念ながらALIENWAREのディスプレイはG-SYNCに対応していません。カタログに記載していないだけで、こっそり対応されたりしてないかな・・・と思って試してみたのですが、ゲーム側の設定で垂直同期をOFFにしてキャラを動かすと、普通にティアリングが発生してしまいました。ここだけは少し残念ですね。

まとめ

そんなわけで、2回に分けてNEW ALIENWARE m15のレポートをしてきました。

ゲームをするのに申し分ない性能で、有機ELの液晶がかなり綺麗で、キーボードも良好、ポート類なども全部ある、という辺りが好感触でした。唯一の欠点は、ディスプレイがG-SYNCに非対応なところでしょうかね。
また、重さも2.1kg程度なので、日常的に持ち歩けるかは判断が分かれるかも知れませんが、ゲーム機を持ち寄る対戦会のようなイベントに持っていく分には、何の抵抗もないという感じです。

f:id:cero-t:20191029113957j:plain
都内某所で行われているFEXL対戦会。ガチ勢だ!

ゲームに使うだけでなく、4K有機ELのおかげで動画鑑賞としても良いですし、癖のないNキーロールオーバー対応キーボードのおかげで、仕事や書き物にも使えるなという印象でした。
僕はいまMacをメインで使っていますが、もしメインをWindowsに切り替えるなら、性能面とキーボードの良さという理由で、ゲーミングノートにしたいと思っています。ALIENWAREはその選択肢の一つとして、決してアリエンワーではなく、候補の一つになるなと思いました。

f:id:cero-t:20191029113411p:plain
アリエンワーでググってみました。

はい、そんなわけで、
DELLさん、試用機のご提供、ありがとうございました!

NEW ALIENWARE m15レポート。このキーボードが好きかも知れない #デルアンバサダー

最近ちょくちょくノートPCのレポートを書いてる @cero_t です。今回もDELLアンバサダーとしてノートPCを試用できることになりましたので、そのレポートです。

これまでXPS 15とかXPS 15 2-in-1などのノートPCを試用してレビューしてきましたが、その際、いつも格ゲーを動かしたらどうか? ということを考えていました。

Dell XPS 15とThinkpad X1 Yogaを比較する(対戦格闘ゲーム編) #デルアンバサダー - 谷本 心 in せろ部屋

XPS 15 2-in-1 レビューその3 対戦格闘ゲームの性能 #デルアンバサダー - 谷本 心 in せろ部屋

ほら、俺様とかって、eスポーツプレイヤーなわけだし、やっぱり格ゲーの性能とか気になっちゃうじゃないですか? それだったら、同じDELLでもXPSよりもむしろゲーミングノートであるAlienwareの試用とかしてみたいじゃないですか?
、、、みたいに一人でイキったことを考えていたところ、そんな気持ちがDELL様に伝わったのかどうなのか、Alienwareのモニターに当選し、試用をする機会を得られました!!

そんなわけで、今回はNEW ALIENWARE m15(以降、ALIENWARE)のレポートをしたいと思います。

開封の儀と、簡単なスペックについて

まずは外観。ゲーミングノートとしては珍しく、白っぽいというか、グレーっぽいシルバーです。写真を撮る能力は神様が授けてくれなかったので、構図などへのツッコミは無用です。

f:id:cero-t:20191025083202j:plain
白っぽいゲーミングノート。左側の取っ手みたいな部分にもポート類がある

この色は、「ルナライト(シルバーホワイト)」という名前が付いており、そこはかとない厨二感を覚えます。ちなみにもう一色、黒ベースのカラーもあるのですが、そちらの名前は「ダークサイド オブ ザ ムーン(ダークグレー)」だそうです。命名やばすぎないですか? 大丈夫ですか?

基本的なスペックで言うと、第9世代のCore i7-9750Hに、GeForce RTX 2070 with Max-Qという、ガチハイエンドの一歩手前ですが、ゲームをするには問題ないであろうスペック。
あと、HDMI、miniディスプレイポート、有線LANポートみたいなものがきちんと揃っているのは、ゲーミングノートとして助かりますね。たまにこれらのポートがないゲーミングノートもあるのですよね。
重さはカタログによると2.1kgだそうで、ゲーミングノートとしてはやや軽い方になります。重さをどう感じるかは人次第ですが、MacBook ProiPadとMagic Keyboardを常に持ち歩いている僕としては、そのセットよりは軽いなという感じ方になります。

また、実際に電源を入れてみて、なんかやけにディスプレイが綺麗だなー、新品だからかなー、と思ってたら、有機ELの4Kディスプレイでした。有機ELってやっぱり綺麗なんですね。試しに、HORIのポータブルゲーミングモニターを並べてみました。

f:id:cero-t:20191025041827j:plain
ALIENWARE(左)とHORIのポータブルゲーミングモニター(右)

比べる相手が悪いのは確かですが、黒の暗さがまるで違っていて、明らかにALIENWAREのほうがコントラストがはっきりしています。これだとNetflixなどを観るのにも向いてそうですね。

なお、有機ELにすることで残像がどうなるのか、みたいなところは、また改めて検証したいと思います。

キーボードよくないですかこれ

キーボードの配列、見てください、これ。

f:id:cero-t:20191025090311j:plain
全体的に違和感のない配列のキーボード

XPSでは向かって右側の「む」や「Enter」の列のキーが細くなっていたのですが、ALIENWAREのキーボードはそこに妥協はありませんでした。いずれのキーも同じ幅になっているように見えます。
あと、良いなと思ったのが、スペースキーの右側。

f:id:cero-t:20191025090626j:plain
キーボード右下部分のアップ。余計なキーを減らしているのが良い

スペースキーの右側には「変換」「カタカナ」「CTRL」の3つしかありません。他のノートPCだと、ここに余裕があると、「Alt」とか「アプリケーションキー(右クリックがわりのやつ)」とか、ThinkPadだと「PrintScreen」なんかが配置されて、ごちゃごちゃしがちです。
機種によってもボタンの配置が違うので、なかなかどういうボタンがあるか覚えられず、僕はこの部分のキーをほとんど使うことがありません。むしろここのキーが少ないのが良いという理由で、US配列のキーボードを使っていたりします。

さて、話をALIENWAREに戻して、「カタカナ」を横に伸ばすというなかなか思い切ったデザインにしています。なんかもう「どうせこの部分は使わないよね」という意志が伝わってきました。この配列はちょっと気に入ったので、他のメーカーさんも真似してくれないかなという想いです。
ただ僕自身の好みでいうと、「右Ctrl」の位置は「Fn」になってて、Fnを押しながらカーソルの上下を押すと「Page Up」と「Page Down」になり、カーソルの左右を押すと「Home」「End」になるタイプが好きなのですが、それとは違ってPage UpやHomeなどは独立しています。

、、、なんか早口のオタクみたいな勢いでキーボードについての好みを語ってしまいましたが、まぁ実際早口のオタクなんですけど、このキー配列はなかなか良いぞ、ちょっと気に入ったぞという話でした。

ついでにNキーロールオーバーも試す

なお、このキーボードはアンチゴーストおよびNキーロールオーバーに対応しているそうなので、実際に試してみたところ、同時押しなども問題なくできました。

これまで他のノートPCのキーボードで問題になっていた、「ryu」「tyu」「esu」「oki」などの近い3キーの同時押しは、すべてきちんと3キーとも認識されました。 それ以外にも、4キー、5キーといくつかのキーの同時押しをランダムで試してみましたが、いずれも正常に認識されました。
このような同時押しが正しく認識されるかどうかは、ゲームでは「移動しながらアイテムを使う」とか「コマンド入力後にパンチボタン3つ同時押しをする」みたいな時などにも死活問題となります。いや、キーボードで格ゲーすんなよって話なんですけど。

また僕としては、テキストを書く時にも同時押しのせいでキー抜けが発生するとイライラしてしまうので、Nキーロールオーバー対応されていることをかなり重要視しています。
2019年現在、ビジネスノートではNキーロールオーバー対応されることはほとんどないのですが、いつか、このNキーロールオーバー対応の技術がふつうにビジネスノートでも使われるようになれば良いのにな、と願っています。

まとめと次回予告

なんだか半分以上、キーボードの話をしてしまいましたね。
全体的な感想としては、4K有機ELが綺麗、ポート類は全部ついてる、キーボードが良い感じ、という三拍子揃った感じでなかなか好印象でした。

えっ、ゲーミングノートを借りておいて、ぜんぜんゲームの話をしないの? という感じになってしまったので、次回は、ゲーミングの性能について全力で調べたいと思います。

参考)製品情報

Tobii視線追跡機能搭載Alienware m15ゲーミング ノートパソコン | Dell 日本

Macに出戻りました。

去年の3月頃に、MacBookからThinkPad X1 Yogaに乗り換えたという話を書きました。

MacBookからThinkpad X1 Yogaに乗り換えます。 - 谷本 心 in せろ部屋

その8ヶ月後、Surface Bookに乗り換えました。

ThinkpadからSurface Book with Performance Baseに乗り換えました - 谷本 心 in せろ部屋

MacからWindowsにした理由は、USB Type-AとかHDMIもまだ欲しかったことと、WindowsでもDockerとかWSLとかBabunとか使えばで割とふつうに開発できるんじゃないかと期待したことでした。その点については概ね期待通りであり、何ら問題ありませんでした。 そしてThinkPad X1 YogaにしろSurface Bookにしろ、ペンで手書きできることは予想以上に良くて、設計や執筆の初期段階でアイデアを練る時には、常にペンを使っていました。

手描きしたいなら紙で良いじゃんみたいな話もありますが、紙だとふつうになくすんですよ!

Windowsのココが辛い

そうやって1年以上はWindowsを使いながらも、細かな不満はありました。

  • キーボードの取りこぼしが起きる
  • Chromeの起動とか、IntelliJの起動が、なぜか遅い
  • スリープから復帰した時の挙動が微妙
  • タッチパッドMacBookほど最高じゃない
キーボードの取りこぼしが起きる

一番の不満は、OSの問題ではなく、ハードウェアの問題なんですが、キーボードの取りこぼしが起きることでした。

ThinkPadだと「ryu」の3つ同時押しで「u」が認識されません。Surface Bookだと「esu」の3つ同時押しで「u」が認識されません。別に同時押しがしたいわけではなく、普通に打鍵してる中でこのような取りこぼしがちょくちょく起きることが、凄くストレスになります。

これ、逆に聞きたいんですが、皆さんってこの現象あまり起きないのですか? おそらく僕の打鍵の癖として、前のキーから指を離す前に次のキーを打ってるせいでこの現象に遭うんでしょうけど、高速に打鍵してるとだいたいそんな感じになりませんか?

ゲーミングノートであればだいたいNキーロールオーバー対応されているため、このような問題は起きないのですが、ゲーミングノートはだいたい熱くてうるさいので、なかなか常用しようという気にはなれないのですよね。

Chromeの起動とか、IntelliJの起動が、なぜか遅い

なんかアプリの起動や操作にイチイチ引っかかるように感じていて、Chromeの起動になぜか数秒待たされたり、IntelliJの起動にずいぶん待たされたりしてしまって。性能的にはSurface Book(Core i7 6600U) の方が、MacBook Pro(Core i5 4258U) を完全に上回っているにも関わらず、ブラウザやIntelliJの起動はなぜかMacBook Proの方が早かったりして、何かおかしいんですよね。

検証はしてないのですが、Windows標準のセキュリティソフトであるWindows Defenderのせいかも知れません。Eclipseなんかも起動すると数分ぐらいWindows DefenderがCPUを使いっぱなしになったりするので。ノートンみたいなサードパーティ製のセキュリティソフトに変えれば解決する可能性はありますね。

あと、ゲーミングノート側はChromeの起動が早かったんですが、ゲーミングノートはだいたい熱くてうるさ(略

スリープから復帰した時の挙動が微妙

Surface BookにしろThinkPad Yogaにしろ、スリープ(スタンバイ)から復帰した時に、Bluetoothキーボードが効かなくなったり、サブディスプレイが映らなくなったり、あとそもそも画面を閉じてもきちんとスリープしてなくて、カバンの中でホッカホカになることも多かったです。

そもそもスリープとかせず、電源を切れみたいな話もあるかも知れませんけど、ノートPCをパカッと開いたらすぐに使い始めたいじゃないですか、やっぱり。

まぁゲーミングノートなら、電源を切ったとしてもすぐに起動するのでまだ良いのですが、ゲーミングノートはだいたい熱(略

タッチパッドMacBookほど最高じゃない

これもOS関係なく、ハードウェアの問題なんですが、やっぱりタッチパッドが今ひとつなんですよね。Surface BookタッチパッドWindowsの中では群を抜いた使いやすさではありますが、それでもMacBookには少し劣るため、どうしてもマウスを併用する感じになります。

この辺は本当に単純にハードウェアの問題なので、言ってみればMacBookWindowsを入れて運用しても良いわけですが、別にそこまでするほどWindowsのことを愛しているわけじゃないという感じです。

あとまぁ別にマウスを持ち歩いちゃえば良いじゃんと思いますし、Windows中心だった時は確かにマウスを持ち歩いていました。

MacBook Proに戻ったきっかけは、働く会社が増えたこと

そうやって細かい不満を抱えながらも、Windowsノートとは上手く付き合ってきたのですが、4月から新しい会社で働くようになり、そこで皆がMacBookで開発環境を作っていたので、自分も古いMacBook Pro (Late 2013) を引っ張り出してきて仕事をするようになりました。

そうするとね、快適なんですよね。

MacBookは別にNキーロールオーバー対応しているわけではない(たとえばKOIの3つを同時押しするとIを認識しない)のですが、両手でローマ字打ちしている限りは問題になる組み合わせは経験していないですし、アプリは変な引っかかりがなくきちんと動きますし、スリープからもきちんと復帰します。

2台持ちしていた時代もありました

MacBook Proを改めて使うようになってからも、しばらくはSurface BookMacBook Proの2台持ちをしていました。なんだかんだ言ってSurface Bookのほうが性能は良いですし、手描きメモとしても活用していたので、役割を分けて使っていました。あわせて3kg以上で、ACアダプタやキーボードを含めるともっと重いのですが、リュックで背負って歩く分にはさほど気になりませんでした。

ただ、前述の細かな不満のせいで徐々にWindowsを使う機会が減っていき、最終的に、iPadApple Pencilを買ったところで手描きメモとしての座も奪われたSurface Bookは、我が家の棚で余生を送ることとなりました。

もちろんMacにも不満がありますけどね

もちろんMacだと、Androidからファイルをコピーするのが果てしなく面倒だとか、Office系アプリの起動が異常に遅いだとか、特にOne Noteあたりの挙動が微妙だったりという不満もあります。それに最近のMacBookって、バタフライキーボードが酷評されてますし、タッチバーも微妙だし、Type-Cしかないしで、なかなか買い換える気にもなりません。

ただ、ペンを使うタブレットとしてiPadシリーズはやっぱり最高だし、iPhoneとPixel 3の2台持ちをしてるけどiPhoneの方が(Felicaの使い勝手を除き)使いやすいと感じるし、iPad / iPhoneとの連携を考えると明らかにMacの方が便利だし、という感じで今のところはAppleにロックインされている方が幸せかなという感じではあります。

とは言え、ゲームをする時とか、動画配信する時とか、Android端末からファイルコピーする時とかは、Windowsのゲーミングノートを使ってますけどね。総じてWindowsのほうが高性能のPCを低価格で購入しやすいですよね。

そんなわけで、MacWindowsを行ったりきたりして、最終的には両方とも使う感じにはなっていますが、次に買う一台は、MacBookかなぁと思っています。

16インチMacBook Pro、早く出ないかな・・・。

GraalVM + Javaで作ったバイナリをAWS Lambdaで動かす時にハマった所

仕事でAWS Lambdaを使う機会が増えてきたのですが、やはり書き慣れたJavaでLambdaを書きたいなと思うことが少なくありません。ただAWS LambdaでJavaアプリを動かすと、初回アクセスに十秒近く掛かるし、メモリ消費量も大きいしで、パフォーマンス的にも運用コスト的にも嬉しくありません。そう思ってるところで、JavaのアプリをGraalVMでネイティブビルドをすれば初回起動時間もメモリ消費量も抑えられると聞いたので、これに取り組んでみました。

ただ実際にやってみると、わりとエラーが頻発してしまって、トライ&エラーを繰り返しながら先に進むという感じになりました。せっかく色々と試したので、起きた問題とその解決策・回避策なんかをここにメモしておきます。

やったこととか参考にしたサイトとか

やったこと

まずJava8(1.8.0_202)と、Micronaut(1.1.3)のライブラリを使って、AWS LambdaのJavaランタイム向けのjarを作って動作確認をしました。Micronautはフレームワークとしては利用せず、あくまでMicronautのライブラリとプラグインだけを部分的に使う形で利用しています。

Javaランタイムできちんと動くことが確認できたら、次にGraalVM(19.0.2)を使ってnative-imageコマンドでバイナリを作り、AWS Lambdaのカスタムランタイムとして動作させました。

この辺りまでのやった内容は、GitHubに置いてあります。

https://github.com/cero-t/lambda-graal

さらにこれをベースにして、JavaのImageIOを使った画像のリサイズを行う処理を書こうとしているのですが、そこでハマってしまっているというのが、いまの状況です。

参考サイト

ひとつ目が @kencharos さんのQiita記事。今回GraalVMを使ってみようと思ったきっかけであり、よりどころであり、この記事なしでは何事も進みませんでした。感謝。

qiita.com

もうひとつが、Micronautで作ったファンクションをAWS Lambdaで動かす辺りのことを記述してあるドキュメント。若干、メンテナンス不足だったのでプルリクしたりしてました。

https://micronaut-projects.github.io/micronaut-aws/latest/guide/#customRuntimes

起きた問題と解決策・回避策

繰り返しの説明になりますが、AWS LambdaのJavaランタイムできちんと動作確認を行なってから、それをGraalVMでバイナリ化してAWS Lambdaのカスタムランタイムで動作させました。そこでエラーが起きた問題について、まとめています。

NullPointerExceptionが発生 / 戻り値がnullになる

現象

リフレクションを伴う処理の戻り値がnullになる。たとえば次のようなコードで問題が起きる。

HttpResponse<MyEntity> response = httpClient.exchange(URI, MyEntity.class);
MyEntity entity = response.body();

これで取得した entity の値がnullになる。他にも、Jacksonを使った処理などでも同じことが起きる。

原因

GraalVMでネイティブイメージを作成すると、リフレクションに必要な情報がなくなってしまうため。上の処理ではリフレクションを使ってMyEntityのインスタンス作成や値の設定を行なうが、リフレクションが使えないためMyEntityのインスタンスが作れない、のだと思う。

対策

リフレクション定義ファイルを作る。

まず src/main/graal フォルダに reflect.json を作成する。JSONのサンプルはこんな感じ。

lambda-graal/reflect.json at master · cero-t/lambda-graal · GitHub

次に build.gradle にannotationProcessorを追加する。

dependencies {
    annotationProcessor "io.micronaut:micronaut-graal:1.1.3"
    annotationProcessor "io.micronaut:micronaut-inject-java:1.1.3"
}

これでビルドを行うと、できあがったjarにリフレクション定義ファイルが含まれるようになる。リフレクション定義ファイルが正しく含まれてたかどうかの確認は、jarファイルを解凍して META-INF/native-image/io/micronaut/reflection-config.jsonnative-image.properties があることを確認すると良い。

所感

リフレクション対象になるクラスの列挙はわりと面倒なので、案件の規模が大きくなるとちょっとしんどいかも。ルールを決めて運用すれば、何とかなるのかな。

② Unsupported method java.lang.ClassLoader.findLoadedClass(String) is reachable が発生

現象

Jacksonを使った処理で次のエラーが発生する。

com.oracle.svm.core.jdk.UnsupportedFeatureError: Unsupported method java.lang.ClassLoader.findLoadedClass(String) is reachable: The declaring class of this element has been substituted, but this element is not present in the substitution class

原因

jackson-module-afterburner というモジュールが、実行時に動的にバイトコードを生成しようとするが、GraalVMで作成したバイナリではバイトコード生成ができないためエラーが発生する。

対策

jackson-module-afterburner を依存から除外する。このモジュールを使っているつもりはなくとも、たとえば aws-serverless-java-container-core などがこのモジュールを利用しているため、次のように記述して依存から除外する。

implementation('com.amazonaws.serverless:aws-serverless-java-container-core:1.3.1') {
    exclude group: "com.fasterxml.jackson.module", module: "jackson-module-afterburner"
}

所感

実行時にバイトコードをエンハンスするようなツールは、軒並み動かなそう。アノテーションプロセッサーで、コンパイル時にエンハンスするようにしなきゃいけないね。

java.lang.NoClassDefFoundError: org.apache.commons.logging.LogFactory が発生

現象

commons-logging を使ったライブラリの初期化時にエラーが発生する。たとえば aws-sdk-java などを使おうとするとこの問題が起きる。

Exception in thread "main" java.lang.NoClassDefFoundError: org.apache.commons.logging.LogFactory
    at org.apache.commons.logging.LogFactory.class$(LogFactory.java:1021)
    at org.apache.commons.logging.LogFactory.<clinit>(LogFactory.java:1674)
    at com.oracle.svm.core.hub.ClassInitializationInfo.invokeClassInitializer(ClassInitializationInfo.java:347)
    at com.oracle.svm.core.hub.ClassInitializationInfo.initialize(ClassInitializationInfo.java:267)
    at java.lang.Class.ensureInitialized(DynamicHub.java:437)
    at com.amazonaws.regions.AwsRegionProviderChain.<clinit>(AwsRegionProviderChain.java:33)

原因

きちんと原因を追っていないけど、slf4jやcommons-loggingのような実行時にロギングライブラリを選べるような仕組みは、なんとなくGraalVMと相性が良くなさそう。

回避策

commons-logging を使っているようなライブラリの利用を避ける。aws-sdk-javaの代わりに、micronaut-http-client を使うなど。

所感

--initialize-at-build-time とか reflect.json とかをきちんと設定すれば、commons-loggingを使えるようになるかも知れないのだけど、この辺りの挙動がまだ理解できてなくて、ちょっと先延ばしにしてる。

④ IOException: javax.imageio.IIOException: Can't create cache file! が発生

現象

ImageIO.read(in) メソッドを呼び出して画像をロードしようとすると、エラーが発生する。

IOException: javax.imageio.IIOException: Can't create cache file!
javax.imageio.IIOException: Can't create cache file!
    at javax.imageio.ImageIO.createImageInputStream(ImageIO.java:361)
    at javax.imageio.ImageIO.read(ImageIO.java:1397)

原因

ImageIOは、staticイニシャライザでキャッシュファイル(一時ファイル)のパスを環境変数から取得している。staticイニシャライザの処理はネイティブイメージのビルド時に行われて、実行時に環境変数が再評価されることはないない。そのため、ビルド時に決定したキャッシュファイルのパスが、実行時には利用できない、という問題が起きてしまう。

回避策

一時ファイルを使わない、つまりImageIOのキャッシュファイルを使わないようにする。自分の作ったFunctionに、次のようなコードを入れておく。

static {
    ImageIO.setUseCache(false);
}

所感

ImageIOを --initialize-at-run-time の対象にすれば、環境変数の参照を実行時に行えるようにできそうなのだけど、このオプションを指定したところ、他のクラスの初期化をコンパイル時に行うためか、エラーが起きてしまった。

ビルド時評価や、実行時評価の設定を、上手くできるようにならなきゃ。。。

java.lang.UnsatisfiedLinkError が発生

現象

ImageIOでJPEG画像を読み込もうとすると、次のようなエラーが発生する。

java.lang.UnsatisfiedLinkError: com.sun.imageio.plugins.jpeg.JPEGImageReader.initJPEGImageReader()J [symbol: Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_initJPEGImageReader or Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_initJPEGImageReader__]

原因

JPEGをロードするためのプラグインが、実行時には取得できないため。

回避策

いったん、JPEG画像を扱わないことにした。

所感

JPEGを扱わないわけにはいかないので、あとでまた向き合わなくてはいけない問題。

com.sunパッケージのライブラリはうまく扱えないのか? それとも、プラグイン構造のように、動的にクラスローディングを試みるような処理がいけないのか?

--initialize-at-build-time とかが上手く使えるようになったら、また戻ってくる。

java.lang.Error: Could not find class: null が発生

現象

BufferedImage.createGraphics() メソッドの呼び出し時に次のエラーが発生する。

Exception in thread "main" java.lang.Error: Could not find class: null
    at java.awt.GraphicsEnvironment.createGE(GraphicsEnvironment.java:117)
    at java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(GraphicsEnvironment.java:82)
    at java.awt.image.BufferedImage.createGraphics(BufferedImage.java:1181)

原因

次のコードがnullを返すため。

AccessController.doPrivileged(new GetPropertyAction("java.awt.graphicsenv", null));

doPrivileged はnativeメソッドなので詳細までは確認していないけど、動的なクラスローディングができない件と同じだと推測している。この辺はハマったまま、まだ回避策も解決策も見つけられていない。また進展があったらアップデートする。

全体的な所感とか

GraalVMで作ったネイティブイメージは、起動時間が早く、メモリの消費量も少ないので、その点は完全に期待通りです。AWS Lambdaみたいな、いわゆるサーバレスというか、ファンクション実行する環境との相性がとても良いという手応えがあります。

ただその一方で、Javaの世界で古来より用いられているテクニックである、リフレクションや、動的なクラスローディング、動的なバイトコードエンハンスメントなどが、問題を生んでしまっている面があります。この辺りはGraalVM自体の設定やバージョンアップで解決できる部分もあるでしょうけど、いくつかの問題は、ライブラリやフレームワークが実行時解決をするのではなく、コンパイル時解決できるようになっていかないと、根本解決しないのではないかと推測しています。

つまり、既存のライブラリやフレームワークなどが「GraalVM Ready」になっていくというムーブメントが起きるどうかが、GraalVMが使い物になっていくかどうかの分水嶺なのかなぁ、と思って注視しています。SpringもGraalVM対応することをロードマップに入れたりもしていますし、今後もぼちぼちウォッチしていきます。