Deep Dive into
instanceof
2018-12-15
JJUG CCC 2018 Fall
In od on
Hiroshi Saito @saidie
➔ SmartNews 1.5yrs
広告配信プラットフォーム (Java)
➔ DeNA 3yrs
Web サーバサイド (Perl, Ruby on Rails)
➔ Ph.D Student 5yrs
理論神経科学
Java 初心者!!
Sma w
世界中の良質な情報を必要な人に送り届ける /
Delivering the world’s quality information to the
people who need it
SmartNews Ads
”Ads as Content”
In od on
Performance is important
● 大量の広告リクエスト、ビーコン
● ハイパフォーマンスを求められる配信システム
In od on
Java 初心者が通る道(?)
instanceof を使ってレビューで指摘される
OOP 的に良くないだけでなくパフォーマンス的にも
良くない
In od on
Why?
一見難しそうに見えるが Set などを使えばいいのでは?
E
N
R
S
M
T
W
A
Z
Research
Res h
Java コンパイラによる変換
.java .class JVM
javac execute
Res h
classの中身
ConstantPool Field Attribute
+
Res h
JVMはスタックマシン
● 命令の引数はスタックに積んである
● 結果はスタックに入る
“Hello,
world!”
Stack
instanceof String
1
Stack
コンパイル時に決まっている
ConstantPoolから表引き
pop push
Res h
classの中身 (再掲)
1. ldc: stack に “Hello, world!” を push
2. instanceof を実行
3. jump (空の if 文)
4. return
Res h
JVM
● Oracle HotSpot JVM
● Eclipse OpenJ9
今日見ていくのは HotSpot JVM
バージョン: jdk-11.0.1+13
Res h
ソースコード
hg clone https://hg.openjdk.java.net/jdk-updates/jdk11u
Res h
interpreter を探せ
share/interpreter
● TemplateInterpreter: CPU依存
○ 普段使われてるもの
● CppInterpreter: CPU非依存
○ 今日は主にこっちの話
JVM internals
share/oops/ にある C++ の class
● Klass ⇐ Java のクラス/interface
○ ex) Object クラスの Klass, Integer クラスの Klass
● ConstantPool
○ ある Klass から参照される定数のルックアップテーブル
○ Klass*の配列を持っている
Div I J
基本的なデータ構造
Div I J
バイトコードインタプリタ概要
1. pc の位置にある命令を読む
2. バイトコードに応じて処理を行う
○ 巨大な switch 文
3. pc を更新する
4. 1 に戻る
(実際にはスレッデッドコード)
概念図
pc next pc
bytecode
Div I J
BytecodeInterpreter::runWithCheck:L2266
Div I J
instanceof の処理
oop*
“Hello,world!”
Stack
SP
pc
bytecode 0xc1 index
ConstantPool
Klass*
String
Klass*が得られる
Div I J
ConstantPoolのindex
ConstantPoolからKlass*
StackからKlass*
subtype check
Div I J
Klass::is_subtype_of(Klass*)
Div I J
Fast and Decisive Class Checking in the Hotspot VM
John Rose & Cliff Click, 2001
以下のような思想の下で考案された
● CPU のオペレーションを少なくしたい
● Memory access を少なくしたい
● VM call をなくしたい
Klass.hpp にあるとあるコメント
Div I J
subtype check 継承の場合
単純にsuper classを一つ一つチェックする場合
深さに応じた時間がかかる
Div I J
継承ツリー
Object
Number
ArrayList
Integer AbstractList
AbstractCollection
0
2
1
3
Double AbstractSet
HashSet
Div I J
primary supers では super depth が重複しない
Primary supers: super class のリスト
● Integer: [Object, Number]
● ArrayList: [Object, AbstractCollection, AbstractList]
Super depth: 継承の深さ
● Object: 0
● Number: 1
● AbstractCollection: 1
● AbstractList: 2
super depth の位置を調べるだけで良い
Div I J
interfaceは二部グラフ
Collection
Serializable
ArrayList
Integer
AbstractList Iterable
AbstractSet
HashSet
多対多なので難しい
List
Set
Div I J
Binary matrix
Klass に連番をつけて binary matrix を作る
クラスの動的load/unloadへの対応が難しい
Serializable Collection Iterable List Set
Integer o
AbstractSet o o o o
AbstractList o o o o
HashSet o o o o
ArrayList o o o o
Div I J
線形探索 + Cache
● 数がそんなに多くなければそれほど遅くない
● 前回のinstanceofで参照されたクラスをキャッシュし
ておき、次回は最初にキャッシュをチェックする
CollectionSerializable Iterable ListCloneable RandomAccess
ArrayList の secondary supers
Div I J
ここまでのまとめ
● クラスに対する is_subtype_of
○ 継承ツリーにおける深さで一発で求められる
● interfaceに対する is_subtype_of
○ 前回ヒットしたクラスをチェックする
○ チェックが失敗したら線形探索
Div I J
Div I J
primary_supersをKlassに含める
primary_supers を固定長にするとKlass自体に埋
め込めて、メモリアクセスが減らせる
現状の実装だと supers の長さは 8
▷super depth < 8までしか扱えない
より深いものは?
▷interfaceと同じ扱いにする == 線形探索対象
Div I J
● super_depthはクラスロード時に確定する
● どちらの条件を使うかも確定する
範囲チェックと T との比較を一括で実現できる
load時に決まる
Div I J
Klassのメモリレイアウト
0 1 2 3 4 5 6 7 8 9 A B C D E F
Layout helper Klass ID
Super Check
Offset
Symbol*
name
Array<Klass*>*
Secondary Supers
Klass*
Secondary Super
Cache
Klass*
Primary Supers[0]
super depth < 88 <= super depth
Klass*
Primary Supers[1]
Klass*
Primary Supers[6]
Klass*
Primary Supers[7]
参照すべきメモリ位置が事前に分かる!
Div I J
Klass::is_subtype_of(Klass*)
primary supers / キャッシュの位置
primary super / キャッシュ
== primary super / キャッシュ
super depth < 8 の場合
線形探索
Div I J
x86の場合
cpu/x86/macroAssembler_x86.cpp: check_klass_subtype 関数
MOV: メモリ → レジスタ
CMP: レジスタ⇔メモリ
CMP: レジスタ⇔即値
MOV: Arrayの位置や長さ
REPNE: 線形探索
Div I J
Experiments
C_10 → C_09 → … → C_02 → C_01 → Object
fast slow
Div I J
Results
JIT をオフにして JMH で計測した結果
Summary
Sum y
instanceof 実装まとめ
● 継承ツリー深さが8未満の具象クラス
○ 数回のCPUオペレーションとメモリアクセス
● 深さが8以上 or interface
○ 前回のinstanceofと同じであればキャッシュが効いて高速
○ キャッシュミスしたら線形探索
■ 効率的なCPU命令がある
Sum y
Conclusion
継承が深いクラスやinterfaceを使ったinstanceofを使う場
合はできるだけキャッシュを効かせよう
instanceof を使わなくて済むようきちんと設計しよう
JVM のコード読むの結構楽しいよ
We want more!!
https://smartnews.workable.com/

Deep dive into instanceof