谷本 心 in せろ部屋 このページをアンテナに追加 RSSフィード

2015-12-24

[][]JMXで情報を取得する時のベンチマーク

JMHを使って、JMX経由でMBeanの情報を取る際のパフォーマンスを測定してみた。

ベンチマークソースコードはこちら。

https://github.com/cero-t/Benchmarks/blob/master/src/main/java/ninja/cero/benchmark/JmxBenchmark.java


ベンチマーク環境はMacBook Pro Late 2013 (Core i5 2.4GHz) で、

他のアプリなども立ち上げっぱなしの環境なのでノイズは多めでだけど、

傾向を見たいだけなのであまり気にせず。


VirtualMachine.attachのパフォーマンス

VMオンデマンドアタッチする際のパフォーマンス。


No1 : VirtualMachine.attacheしてからシステムプロパティを取ってdetachする

No2 : キャッシュしていたVirtualMachineを使ってシステムプロパティを取得する

@Benchmark
public void no1_vmAttach() throws Exception {
    VirtualMachine vm = VirtualMachine.attach(PID);
    vm.getSystemProperties();
    vm.detach();
}

@Benchmark
public void no2_vmCachedGetProperties() throws Exception {
    vm.getSystemProperties();
}

結果

BenchmarkModeCntScoreErrorUnits
JmxBenchmark.no1_vmAttachthrpt10653.834± 108.790ops/s
JmxBenchmark.no2_vmCachedGetPropertiesthrpt102330.529± 270.268ops/s

アタッチありは1.5msec程度、キャッシュした場合は0.4msec程度。

ということで、アタッチに掛かる時間は1msec程度と推定。割とでかい。


JMXConnectorFactory.connectのパフォーマンス

続いて、VMに対するJMX接続を行う際のパフォーマンス。


No3 : JMXConnectorの取得処理と、クローズ処理をする

No4 : キャッシュしていたJMXConnectorを使って、MBeanServerConnectionの取得とMbean情報を取得する

public JMXConnector no3_vmCachedGetConnector() throws Exception {
    String connectorAddress = vm.getAgentProperties().getProperty("com.sun.management.jmxremote.localConnectorAddress");

    if (connectorAddress == null) {
        String agent = vm.getSystemProperties().getProperty("java.home") + File.separator + "lib" + File.separator + "management-agent.jar";
        vm.loadAgent(agent);
        connectorAddress = vm.getAgentProperties().getProperty("com.sun.management.jmxremote.localConnectorAddress");
    }

    JMXServiceURL serviceURL = new JMXServiceURL(connectorAddress);
    JMXConnector jmxConnector = JMXConnectorFactory.connect(serviceURL);
    jmxConnector.close();
}

@Benchmark
public void no4_connectorCachedGetMBeanCount() throws Exception {
    MBeanServerConnection connection = connector.getMBeanServerConnection();
    connection.getMBeanCount();
}

結果

BenchmarkModeCntScoreErrorUnits
JmxBenchmark.no3_vmCachedGetConnectorthrpt10612.652± 95.547ops/s
JmxBenchmark.no4_connectorCachedGetMBeanCountthrpt109047.803± 1480.741ops/s

JMXの接続と切断は1.6msec程度。おおまかVMに対するアタッチと同じぐらい。

接続したあとの、MBeanServerへの接続とMBean情報取得は0.1msecぐらいで、これは無視できる小さい。


ThreadMXBeanからThreadCountを取るパフォーマンス

今回の主目的はこれ。

ThreadMXBeanを使って情報を取るのと、

MBeanServerConnection.getAttributeで名前を指定して情報を取るのと、どっちが早いか。


No5 : MBeanServerConnection.getAttributeの名前指定でThreadCountを取得する

No6 : ThreadMXBeanを取得してから、getThreadCountで取得する

No7 : キャッシュしていたThreadMXBeanから、getThreadCountで取得する

@Benchmark
public void no5_connectorCachedThreadCount() throws Exception {
    MBeanServerConnection connection = connector.getMBeanServerConnection();
    Object count = connection.getAttribute(new ObjectName(ManagementFactory.THREAD_MXBEAN_NAME), "ThreadCount");
    sum += (Integer) count;
} 

@Benchmark
public void no6_connectorCachedGetThreadCount() throws Exception {
    MBeanServerConnection connection = connector.getMBeanServerConnection();
    ThreadMXBean threadBean = ManagementFactory.newPlatformMXBeanProxy(
            connection, ManagementFactory.THREAD_MXBEAN_NAME, ThreadMXBean.class);
    sum += threadBean.getThreadCount();
}

@Benchmark
public void no7_beanCachedGetThreadCount() throws Exception {
    sum += threadMXBean.getThreadCount();
}

結果

BenchmarkModeCntScoreErrorUnits
JmxBenchmark.no5_connectorCachedThreadCountthrpt108199.887± 1269.164ops/s
JmxBenchmark.no6_connectorCachedGetThreadCountthrpt102662.147± 585.627ops/s
JmxBenchmark.no7_beanCachedGetThreadCountthrpt108148.967± 1705.518ops/s

ThreadMXBeanを毎回取る(No6)は明らかにパフォーマンスが悪いけど、

ThreadMXBeanをキャッシュしている限りは、ThreadMXBeanから情報を取るのと、

MBeanServerConnection.getAttributeで取ることに性能差はなし。


まとめ

1. VirtualMachineへのattach/detachは時間が掛かるので、キャッシュすべき

2. JMX接続の確立/切断は時間が掛かるので、キャッシュすべき

3. MBeanServerへの接続は時間が掛からないので、無理にキャッシュしなくてよい(close処理もないのでリソース管理もしてない?)

4. MBeanServerConnection.getAttributeでもThreadMXBeanを使っても性能差はないので、ThreadMXBeanを無理にキャッシュしなくてよい


ベンチマークソースコード

https://github.com/cero-t/Benchmarks/blob/master/src/main/java/ninja/cero/benchmark/JmxBenchmark.java


現場からは以上です。

2013-11-07

[]なぜ僕はCaliperではなくJMHを選んだのか。

今日、会社のblogのほうにJMHのエントリーを書きました。

そう、今日のテーマはマイクロベンチマークです。

Javaのマイクロベンチマークツール「JMH」 - Taste of Tech Topics

Javaのマイクロベンチマークに興味がある人は、

GoogleのCaliperというマイクロベンチマークツールを既にご存知かも知れません。

しかし上記のエントリーではCaliperには全く触れていません。


なぜか。

いや、Caliperを試そうとしたら、うまく動かせなかったんですよ (^^;


そんな、まともに動かせない所や、ドキュメントの更新のないCaliperに幻滅した後

JMHを試してみたら、思いのほか素直で使いやすかったため上で紹介するに至りました。


ちょっとここでは、その辺りの舞台裏を紹介してみます。


Caliperをはじめてみようとしたけど・・・

Caliperのトップページには、Get started的な始め方が記載されています。

http://code.google.com/p/caliper/


Mavenプロジェクトを作って、pom.xmlに追記して、

トップページにあるサンプルソースコードをコピーして・・・


あれ、えっと・・・?


一体、どうやって実行するのでしょう? まさか実行の仕方を調べるためだけに、

30分近いチュートリアルビデオを見なければいけないのでしょうか?

さすがにそれはありえないので、ドキュメントを見てみましょう。


Caliperはドキュメントを更新する気がないのかね。

Google Codeのドキュメントと言えば、Wiki

https://code.google.com/p/caliper/w/list

早速、このWikiを見てみると・・・あぁ、UsersGuideというのがあるようです。


http://code.google.com/p/caliper/wiki/UsersGuide

何これ、目次だけでまだドキュメントが全然ありません。

QuickStartもないので、始めようがありませんでした。


この点、JMHも同じくドキュメントはほとんどありませんが

少なくともトップページで動かせるまでは面倒を見てくれるので

Caliperよりも親切だと言えます。


Code Samplesのコンパイルが通らない

トップページにあるサンプルだけではきっと動かないのだと思い、

リンクされてる「Very short tutorial examples.」を試してみることにしました。

https://code.google.com/p/caliper/source/browse/tutorial/Tutorial.java


うん、コンパイルが通らない (^^;


少し調べてみると、どうやらCaliperはバージョン1.0から大幅にAPIが変わって

アノテーションベースになるらしく、リンク先のサンプルはそれに合った内容となっています。


しかしいまMavenリポジトリにあるものは、命名規則ベースの古いバージョンで、

setUpやtimeXxxという名前のメソッドを探して実行するという、JUnit3のような思想になっています。


なんかこんな風に、複数のバージョンの情報が混在していて

きちんと情報が整理されていない辺りに、心が折れ始めました。


Caliperを動かしたら、いきなり30秒以上かかって死んだ。

その後もうしばらく調べてみて、どうやらCaliperMainクラスのmainメソッドから

ベンチマークを実行できることが分かりました。


ということで自分で簡単なベンチマークを書いてみたうえで

オプションなどつけずに実行してみました。


順調に進んでるかな・・・

Experiment selection: 
  Instruments:   [allocation, micro]
  User parameters:   {}
  Virtual machines:  [default]
  Selection type:    Full cartesian product

This selection yields 4 experiments.
Starting experiment 1 of 4: {instrument=allocation, method=WithInitialSize, vm=default, parameters={}}
Complete!
Starting experiment 2 of 4: {instrument=allocation, method=WithoutInitialSize, vm=default, parameters={}}
Complete!
Starting experiment 3 of 4: {instrument=micro, method=WithInitialSize, vm=default, parameters={}}
ERROR: Trial failed to complete (its results will not be included in the run):
  Trial exceeded the total allowable runtime (30s). The limit may be adjusted using the --time-limit flag.

・・・と思ったら

なんか30秒以上実行したせいでエラーとなりました。


引数を指定すれば30秒制限を突破できるようですが、

そもそも何を何回実行しようとしてエラーになったのか、さっぱり分からないので

30秒制限をオフにして良いのかどうかも分かりません。

だいぶ心が折れてきました。


Caliperでテスト終わったら、勝手に結果がアップロードされた。

先ほどのテストは、まだ続きがあります。

片方は30秒制限を受けましたが、もう一方は何やらきちんと終了したようです。

Starting experiment 4 of 4: {instrument=micro, method=WithoutInitialSize, vm=default, parameters={}}
Complete!

Execution complete: 1.151m.
Collected 45 measurements from:
  2 instrument(s)
  1 virtual machine(s)
  2 benchmark(s)
Results have been uploaded. View them at: https://microbenchmarks.appspot.com/runs/d94be1c2-8be0-48e7-8481-4492145eecd5

Process finished with exit code 0

えっと、結果は・・・1分と少々掛かって、えっと・・・

って、何!?

ベンチマークの結果がアップロードされたって?


そうなんです、普通に実行しただけなのに、ベンチマークの結果がGAEアップロードされました。


恐らくオプションなどを指定してオフライン実行などもできるでしょうし、

こうやってアップロードされることで、より詳しく分析などもできるなど

嬉しいこともたくさんあるのだと思います。


しかしながら、簡単にマイクロベンチマークを取りたいという僕の想いとは

あまりに掛け離れた思想をしているCaliperに愛想がつきて、心は完全に折れました。


それに比べて、JMHは素直です。

まずは @GenerateMicroBenchmark アノテーションさえつければ動きますし、

オプションなど指定しなくともきちんと動き、分かりやすい結果を出してくれます。


少なくともいまこの時点で始めるのであれば、

JMHの方が使いやすいと言えるのではないのかな、と思います。


批判、コメント、アドバイス、どしどしお待ちしています!

2013-07-12

[]定義的プログラミング

定義的プログラミングっていう言葉を思いついたから、メモしとく。

イメージ的にはこんなん。


Before: 普通の処理コード

public Dto process(Map<String, String> input) {
	Dto dto = new Dto();
	dto.userName = input.get("ユーザ名");
	dto.password = input.get("パスワード");

	ValidateUtils.checkRequired(dto.userName);
	ValidateUtils.checkLength(dto.userName, 0, 24);
	ValidateUtils.checkRequired(dto.password);
	ValidateUtils.checkLength(dto.password, 8, 24);
	ValidateUtils.checkAlphanumericSymbol(dto.password);

After: 定義的プログラミング

static {
  define("ユーザ名", "userName", REQUIRED, ALPHANUMERIC);
  define("パスワード", "password", NOT_REQUIRED, ALPHANUMERIC_SYMBOL);
}

public static void define(String columnName, String propertyName, FieldType... types) {
	nameMap.put(columnName, propertyName);
	typeMap.put(columnName, types);
}

public T process(Map<String, String> input, Class<T> clazz) {
	// がんばる感じのコード
}

仕様書に載っている表をそのままコードに落とすようなイメージなので

バグが減らせるし、仕様変更にも対応しやすい。

状態遷移のような、複雑な処理ほど効果的だと思う。


自分で実装する時はよくこんな感じにするのだけど、

なかなか後輩には伝えにくかった。

名前をつけておけば伝えやすくなるかなと思って、メモしといた。

2013-06-29

[]JJUGナイトセミナー「from old Java to modern Java

先日のJJUGナイトセミナーで

「from old Java to modern Java」という話をしてきました。

公式サイト : http://www.java-users.jp/?p=551

togetter : http://togetter.com/li/521481


Java以前のCライクな書き方から、J2SE 1.4、5.0・・・からJava SE 8まで

歴史を辿りながらソースコードの変遷を紹介しました。

このスライドは、日経ソフトウエア 2013年7月号に執筆させて頂いた

特集記事「そのコードは古い」のJava編のアイデアを活用したものです。

http://itpro.nikkeibp.co.jp/article/MAG/20130617/485621/

雑誌記事のほうでは、文法が変化した背景やメリットなどにも触れていますので

スライドだけでは物足りない方は、ぜひ雑誌を手にとって頂ければと思います。


また、SlideShareに公開したスライドは、たくさんの方に見ていただけたようで

はてブのホッテントリや、SlideShareのHot on Twitterに掲載され、閲覧数も1万回を超えました。

つぶやいてくださった皆さん、ありがとうございます!


そのおかげで、Javaから遠ざかっていた方や、Java以外の言語を使っている方、

ナイトセミナーにはいらっしゃらない方々にも

Javaのイマを垣間見てもらうことができたのは、良かったかなと思います。


あと、スライドの最後に to be continued... とか入れてしまっているので

もしかしたら、どこかで続編をやるかも知れませんね (^^;

2012-12-10

[]若作りするためのJavaコード

このエントリーは Java Advent Calendar 2012 の10日目として書きました。

なんか色々あって、公開が2日ぐらい遅れてしまってごめんなさい(><)


前日は、Hideki Kishida (@quicy) さんの「Xtend の Lambda とストリーム処理」です。

http://legacy-style.blogspot.jp/2012/12/xtend-lambda.html


翌日は、Katsumi Kokuzawa (@kokuzawa) さんの「WiiRemoteJで遊ぼう on OSX 10.8.7」です。

http://kokuzawa.github.com/blog/2012/12/11/wiiremotejdeyou-bou-on-osx-10-dot-8-7/


どちらも今風ですね!


そんな中、若干、加齢臭が気になり始めたこの頃の私としては、

若さアピールのためのJavaプログラミングとかちょっと気にしたりするわけですよね。

イマドキのプログラミングスタイルにしなきゃいかん、的なね。


そんな同世代、あるいはもう少し上の、プログラマの定年を超えたJavaエンジニアの皆さんにも

業界に入ったばかりで、古い定石など知らないJavaプログラマの皆さんにも、

ちょっと見てもらいたい、「若作りするためのJavaコード」です。


要するに、イマ風のコードを書くための注意点、ですね。

ランキングづけしていきましょう。


まずは第三位から。

第三位 : no native2ascii, no life

Javaではメッセージの外部化や国際化対応などのために、

メッセージをまとめたリソースファイルを作成して

Propertiesクラスを使って読み込むという事を、よくやります。


皆さんも一度ならず経験があると思いますが、こんな感じですよね。

public static void main(String[] args) throws IOException {
	Properties props = new Properties();
	InputStream in = Main.class.getResourceAsStream("/messages.properties");
	try {
		props.load(in);
	} finally {
		in.close();
	}
	System.out.println(props.getProperty("message"));
}

そして、ここで読み込むリソースファイルはUnicode変換しておく必要があるため

native2asciiコマンドや、それに相応する開発環境、プラグインなどを利用していたかと思います。

message=\u30c6\u30b9\u30c8

なんかもうこの一連の流れが、おっさんなわけですね。


「native2asciiが許されるのは、Java5までだよねー!」


みたいな、意外と元ネタが十分に知られていなくて、ネタだけ先行しているようなものは

けっこう滑るので注意が必要なわけですが、

Java6以降ならメッセージは別に日本語のままでも良いんです。

message=テスト

というのも、Properties#loadメソッドにReaderクラスを渡せるようになったからです。

public static void main(String[] args) throws IOException {
	Properties props = new Properties();
	InputStream in = Main.class.getResourceAsStream("/messages.properties");
	Reader reader; 
	try {
		// readerを使ってCharsetを明示!
		reader = new InputStreamReader(in, "UTF-8");
		props.load(reader);
	} finally {
		in.close();
	}
	System.out.println(props.getProperty("message"));
}

Java5までは、CharsetのないInputStreamしか利用できませんでしたが

Java6には、Charsetを指定したReaderを利用できるようになった所がポイントです。

というか、こんなの最初から用意しとけよと誰しも思っているでしょうけどね。


ちなみにPropertiesクラスを使っているフレームワーク側が

ReaderではなくInputStreamを使っていれば、結局Unicode変換しなきゃいけないんですけどね!

第二位 : リソースはfinallyでクローズする?

第二位は、「リソースはfinallyでクローズする」という鉄則です。

もう何年間も鉄則だ、定石だと教わってきました。

第三位に書いたソースも、きちんとその鉄則をきちんと守っています。素晴らしい!


「と思うやんかー? finally不要なんよー。」


テレビCMを文字ったネタは、意外とテレビを見ない昨今は滑りやすいので、これまた注意が必要で

どちらかと言えば、ネットの流行語を取り入れた方が良いわけですが、最近はこんな風に書きます。

public static void main(String[] args) throws IOException {
	Properties props = new Properties();
	try (InputStream in = Main.class.getResourceAsStream("/messages.properties");
			Reader reader = new InputStreamReader(in, "UTF-8");) {
		props.load(reader);
	}
	System.out.println(props.getProperty("message"));
}

Java7以降はtry-with-resourcesを使うことでリソースが自動的にクローズされるため、

わざわざfinallyでクローズする必要はありません。


もう十年以上も言われ続けてきたfinallyでのクローズも、いよいよおさらばですね!

第一位 : pubilc static void main(String[] args)

ところで、mainメソッドの宣言と言えば、

pubilc static void main(String[] args)

皆さん、こうですよね。


たまに、おいたしちゃって

pubilc static void main(String args[])

こんな形になっている宣言もあったりしますが、まぁこんなもんですよね。


でも、最近の若者は、こんな風に書くらしいですよ?

public static void main(String... args)

_人人人人人人人人人人人_

> 突然の可変長引数! <

 ̄^Y^Y^Y^Y^Y^Y^Y^Y^Y^ ̄


テンポが悪いと滑る感じにすらならないため、大けがをしかねないわけですが

確かにmainメソッドは、まさに可変長引数がぴったり合うところです。

長年 String[] args と宣言してくると、なかなか気づかなかったりしますよね。


いまだEclipseでのmainメソッドの自動生成は String[] args なわけですが

各IDEでどんなmainメソッドを作っているか調べてみたら、

ちょっとしたイマドキ判定ができるかも知れませんね!

まとめ

Before

public static void main(String[] args) throws IOException {
	Properties props = new Properties();
	InputStream in = Main.class.getResourceAsStream("/messages.properties");
	try {
		props.load(in);
	} finally {
		in.close();
	}
	System.out.println(props.getProperty("message"));
}

After

public static void main(String... args) throws IOException {
	Properties props = new Properties();
	try (InputStream in = Main.class.getResourceAsStream("/messages.properties");
			Reader reader = new InputStreamReader(in, "UTF-8");) {
		props.load(reader);
	}
	System.out.println(props.getProperty("message"));
}

・元ネタは知らないけど、ネタだけ知っている、というものは滑りやすい。

・テレビCMネタは、テレビを見ていない人が多い昨今は多用を避けること。

・突然の◯◯! は意外性とともにテンポ良く出すこと。


トラシューネタとか言っておいて、

全然違うネタを出して、ごめんなさいね!

今度また連載にでもしますよ!

おまけ

少しだけ宣伝ですが、

日経ソフトウエアの「Javaのイケてるコード、残念なコード」という連載にて

1月号、2月号の2回に分けて

「そのコーディングスタイルはもう古い、Javaの新定石を学ぶ」を執筆しました。

http://www.acroquest.co.jp/company/press/2012/1125p1.html

http://ec.nikkeibp.co.jp/item/backno/SW1176.html


今回書いたような内容を含む、Java5からJava7あたりまでに導入された機能を使った

イマドキの書き方を紹介していますので、ぜひそちらもご覧ください m(_ _)m