これからの
コンピューティングの変化と
Java
2016/9/3 きしだ なおき
自己紹介
● 午前中に起きれるようになりました。
今日の話
● ハードウェアが変わっていく
● Javaも変わらないとね
最近こんな経験ありますか?
● メモリやコア数は同じでいいけど速いCPUが
載ったマシンに買い換えよう
最近こんな経験ありますか?
● サーバーが遅いからサーバーを増やそう
最近こんな経験ありますか?
● サーバーが遅いからデータベースをメモリに
キャッシュしよう
処理を速くするには
● CPUを載せ換えたら勝手に速くなる時代は10
年前に終わった
● 並列度をあげる
● より近いところにデータを置く
ムーアの法則
● 18ヶ月(or24ヶ月)でトランジスタの数が倍に
なる
● 寸法半減→スピード2倍、消費電力1/4
https://en.wikipedia.org/wiki/Moore's_law
ムーアの法則の終焉
● 物理的に配置できない
– 5nm=水素原子50個分
● 電子が漏洩する
– 電気を余分に消耗する
● 歩留まりがあがらない
– 製造コスト増
微細化が進んでも今までとは違う
● コストが下がらない
● 低消費電力と高速化を同時に実現できない
● たくさん回路をつんでも、すべての回路に電気
を流せない
– ダークシリコン問題
データ・セントリック・システム
● データの移動に電力や時間が食われている
– ストレージ→メインメモリ→キャッシュメモリ
● データの移動を減らす必要がある
● データの近くで処理を行う
● 処理を行うのはCPUだけではなくなる
● いろいろなところで動くコードが必要になる
コンピュータの種類
● ノイマン型アーキテクチャ
● 非ノイマン型アーキテクチャ
ノイマン型アーキテクチャ
● メモリから命令をよびだして、命令にしたがっ
た回路で処理を行う
● CPU
● GPU
CPU
● 高機能・高性能・高粒度
● 割り込み、権限制御、仮想化、など実行以外の機能
● OSが実行できる
● 演算器はコアあたり10個程度
– 一チップに100個程度
● 明示的にメモリを制御できない
– いかにキャッシュに載せるか
= いかにメモリをまとめて扱うか
GPU
● GPU
– ちょうたくさんコアがある
– 同じ処理を行う
– 行列計算に向いてる
● GTX 1080
– 2560コア!
GPUの構成
● いくつかのコアでグループを作る
– 同時に同じ命令を実行する
– グループだけからアクセスできるメモリをもつ
● コアのグループが多数ある
● コアあたり数個の演算器
– 数千から数万の演算器
非ノイマン型アーキテクチャ
● ノイマン型じゃないコンピュータ全体
– FPGA
– ニューラルネット型コンピュータ
– 量子コンピュータ
FPGA
● Field Programmable Gate Array
– Field 現場で
– Programmable プログラム可能な
– Gate 論理素子が
– Array いっぱい並んだやつ
● 現場でプログラムできる論理回路
回路の入出力の組み合わせ
入力 出力
000 0
100 0
010 0
110 1
001 1
101 1
011 1
111 1
LUT(LookUp Table)
● 入出力をあらかじめメモリにもっておく
● 製品としては4入力LUTや6入力LUT
入力 出力
000 0
100 0
010 0
110 1
001 1
101 1
011 1
111 1
論理ブロック
● Logical Element(LE) Altera
● Logical Cell(LC) Xilinx
配線
● 論理ブロックが格子状に配置
● 周囲に配線
● アイランドスタイル
乗算回路とメモリ
● 乗算やメモリを論理ブロックの組み合わせで
実現すると効率がわるい
● 乗算回路やメモリ(SRAM)がのってる
– 演算器は数百から数千
FPGAの構成
FPGAなら
● 命令を読み込む必要なく、回路をやりたい処
理のとおり並べることができる
FPGAの利点
● 命令を読み込む必要がない
– 処理を行うまでのタイムラグが少ない
● 低レイテンシ
– 命令解析のための回路が不要
● 余分な回路がないので低消費電力
● 細かな並列化
Javaでいろいろやってみる
● JavaでCPU(並列)
● JavaでGPU
● JavaでFPGA
JavaでCPU(並列)
● Java8 Stream
int elementCount = 1_444_477;
float[] inputA = new float[elementCount];
float[] inputB = new float[elementCount];
float[] output = new float[elementCount];
IntStream.range(0, elementCount).parallel().forEach(i -> {
output[i] = inputA[i] * inputB[i];
});
JavaでGPU
● Aparapi
– JavaコードをOpenCLに変換
● OpenCLを呼び出す
– OpenCL:並列計算フレームワーク
● AMD始め、IntelやNVIDIAなどが参加
– JOCL(jogamp.org)
– JOCL(jocl.org)
– JavaCL
● Project Sumatra
– Stream処理を自動的にGPUで行う
– Java VMに組み込む
Aparapi
● A PARalell API
● 実行時にJavaコードをOpenCLに変換
● https://code.google.com/p/aparapi/
Aparapiコード
public class AparapiKernel extends Kernel{
float[] inputA;
float[] inputB;
float[] output;
@Override
public void run() {
int gid = getGlobalId();
output[gid] = inputA[gid] * inputB[gid];
}
public static void main(String[] args) {
AparapiKernel kernel = new AparapiKernel();
int elementCount = 1_444_477;
kernel.inputA = new float[elementCount];
kernel.inputB = new float[elementCount];
kernel.output = new float[elementCount];
fillBuffer(kernel.inputA);
fillBuffer(kernel.inputB);
kernel.execute(elementCount);
}
}
JOCL(jogamp.org)
● OpenCLを薄くラップ
● https://jogamp.org/jocl/www/
JOCLのコード
String KERNEL_CODE =
"kernel void add(global const float* inputA,"
+ " global const float* inputB,"
+ " global float* output,"
+ " uint numElements){"
+ " size_t gid = get_global_id(0);"
+ " if(gid >= numElements){"
+ " return;"
+ " }"
+ " output[gid] = inputA[gid] + inputB[gid];"
+ "}";
CLContext ctx = CLContext.create();
CLDevice device = ctx.getMaxFlopsDevice();
CLCommandQueue queue = device.createCommandQueue();
CLProgram program = ctx.createProgram(KERNEL_CODE).build();
int elementCount = 1_444_477;
int localWorkSize = Math.min(device.getMaxWorkGroupSize(), 256);
int globalWorkSize = ((elementCount + localWorkSize - 1) /
localWorkSize) * localWorkSize;
CLBuffer<FloatBuffer> clBufferA = ctx.createFloatBuffer(
elementCount, CLMemory.Mem.READ_ONLY);
CLBuffer<FloatBuffer> clBufferB = ctx.createFloatBuffer(
elementCount, CLMemory.Mem.READ_ONLY);
CLBuffer<FloatBuffer> clBufferC = ctx.createFloatBuffer(
elementCount, CLMemory.Mem.READ_WRITE);
fillBuffer(clBufferA.getBuffer());
fillBuffer(clBufferB.getBuffer());
CLKernel kernel = program.createCLKernel("add");
kernel
.putArgs(clBufferA, clBufferB, clBufferC)
.putArg(elementCount);
queue.putWriteBuffer(clBufferA, false)
.putWriteBuffer(clBufferB, false)
.put1DRangeKernel(kernel, 0, globalWorkSize, localWorkSize)
.putReadBuffer(clBufferC, true);
比較
● Aparapi
– めちゃ楽
– GPUの性能出しにくい
● JOCL
– ちょっと面倒
– GPUの性能出しやすい
ところでディープラーニング
実装してみました
※正しく動くようになったとは言ってない
ディープラーニング(深層学習)
● 階層の深いニューラルネット
● 最近、人工知能っていわれてるのは、ほぼこれ
● 学習には大量のデータを処理する必要がある
CPUを使って処理
● AlexNet
– 画像認識用ディープニューラルネット
– それまでの画像認識とは段違いの識別能力
– ディープラーニングを一躍有名に
● 学習には1400万枚の画像を読み込ませる必要がある。
● 普通にJavaで記述
● 処理能力:15枚/分
– 1400万枚だと600日!
Aparapiを使う
● JavaのコードをそのままGPU対応
● 15枚/分→90枚/分
● 1400万枚の画像処理が600日→100日!
JOCLを使う
● 90枚/分→298枚/分
● 1400万枚の画像処理が100日→34日!
なんでこんなに速くなったか
● Aparapi版
– 各層の処理前にCPUからGPUにデータを転送
– 処理後に結果をGPUからCPUに転送
● JOCL版
– 処理前に転送するデータは、前の層の結果
– そのままGPUに置きっぱなしにする
つまり
● 100日→34日
● 66日短縮
● 66日間はデータ転送だけだった
GPUローカルメモリを使う
● 298枚/分→300枚/分
● 1400万枚の画像処理が34日→33日
● 1日データ転送をしていた
GPUでの結果
● 90枚/分→300枚/分
● 1400万枚の画像処理が100日→33日
● 67日はデータの移動だけに電気代を払うこと
になっていた!
FPGAでやったら?
● Microsoftの実装
– GPUの半分のスループット
– 1/10の消費電力
– 電力あたりの性能は3倍
– http://techon.nikkeibp.co.jp/article/MAG/20150311/408682/
ASICで作ったら?
● Google TensorFlow Processing Unit
● 碁で人間より強くなる
Sumatra
● Java VMに組み込むことを目標
● 実装難しそう
● コード書くのもわかりにくそう
● 性能出しにくそう
● Java VMに組み込むほどメリットなさそう
– 性能欲しい人はOpenCL使うよね
と思ったら
● 「Sumatra is not in active development for
now.(2015/5/1) 」
http://mail.openjdk.java.net/pipermail/sumatra-dev/2015-May/000310.html
JavaでFPGA
● Synthesijer
– JavaコードからVHDL/VerirogHDLを生成
Synthesijer
● みよしさんが作ってるオープンソース
http://synthesijer.github.io/web/
public class Test {
public boolean flag;
private int count;
public void run(){
while(true){
count++;
if(count > 5_000_000){
count = 0;
flag = !flag;
}
}
}
}
@synthesijerhdl
public class Top {
private final Test test = new Test();
@auto
public boolean flag(){
return test.flag;
}
@auto
public void main(){
test.run();
}
}
Synthesijerが出力したコード
module Test
(
input clk,
input reset,
input flag_in,
input flag_we,
output flag_out,
output run_busy,
input run_req
);
wire clk_sig;
wire reset_sig;
wire flag_in_sig;
wire flag_we_sig;
wire flag_out_sig;
reg run_busy_sig = 1'b1;
wire run_req_sig;
reg class_flag_0000 = 1'b0;
wire class_flag_0000_mux;
wire tmp_0001;
reg signed [32-1 : 0] class_count_0001 = 0;
reg signed [32-1 : 0] unary_expr_00005 = 0;
reg binary_expr_00007 = 1'b0;
reg unary_expr_00011 = 1'b0;
wire run_req_flag;
reg run_req_local = 1'b0;
wire tmp_0002;
localparam run_method_IDLE = 32'd0;
localparam run_method_S_0000 = 32'd1;
localparam run_method_S_0001 = 32'd2;
localparam run_method_S_0002 = 32'd3;
localparam run_method_S_0003 = 32'd4;
localparam run_method_S_0004 = 32'd5;
すごく長い
Javaでもいろいろできる
でも今のままで足りるの?
足りない
● Javaの基本ライブラリが大きすぎる(そしてほとんど使わない)
● オブジェクトのメモリ効率が悪い
● さまざまなアーキテクチャに対応した値が扱えない
– 256bit整数型、float x 4型(SIMD命令用)
● 高機能データ構造がメモリにやさしくない
– Genericsが基本型を扱えない
● 配列がハードウェアにやさしくない
– 多次元配列
– 21億(int上限)を超える要素
– 読み込み専用配列
Java 9
● 来年春リリース予定
● モジュールシステム(Jigsaw)
● GCアルゴリズムのデフォルトがG1GCに
● JShell
モジュールシステム
● いままでの可視性制御
– public, package private, private
– public見えすぎ問題
● これからの可視性制御
– module export, public, package private, private
● Java自体のモジュール化
– フットプリントの最適化
G1GC
● いままでのメモリ領域
– New(Eden/Survivor), Tenuredが固定
● G1GC
– メモリを多数のブロックにわけて、動的にNew領
域、Tenured領域に割り当てる
JShell
● REPL
● 便利
Java9の先
Close To the Metal
Valhallaへの道
Project Valhalla
● Value Type
● Specialization
Value Type
● ユーザー定義基本型
● Codes like a class, works like an int!
Pointクラス
class Point{
final int x;
final int y;
}
Pointクラスの配列
Pointクラスの配列の効率化
ValueType版Point
value class Point{
final int x;
final int y;
}
ValueType版Pointの配列
Specialization
ArrayList<int>
Java8の美しくないクラス
● StreamとIntStream
– IntStream extends Stream<int>ってやりたい
– そもそも捨てたい
● OptionalとOptionalInt
– OptionalInt extends Optional<int>ってやりたい
– そもそも捨てたい
ValueType対応
● それぞれのValueTypeにあわせたコレクショ
ンを作るのは無理
Genericなクラス
class Box<T>{
T value;
Box(T v){
value = v;
}
T getValue(){
return T;
}
}
現在のコンパイル結果
class Box{
Object value;
Box(Object v){
value = v;
}
Object getValue(){
return value;
}
}
Specialize可能なクラス
class Box<any T>{
T value;
Box(T v){
value = v;
}
T getValue(){
return value;
}
}
Specialize対応のコンパイル結果
class Box{
Object*T value;
Box(Object*T v){
value = v;
}
Object*T getValue(){
return value;
}
}
Box<int>の場合
class Box${T=int}{
int value;
Box(int v){
value = v;
}
int getValue(){
return value;
}
}
Name Mangling
● 名前修飾
● Javaの入れ子クラス
– Hoge$Foo
● Specializedなクラス
– Box${T=I}
Java VMはどうするか
Javaのバイトコード
● aload/iload/lload/fload/dload
● astore/istore/lstore/fstore/dstore
● areturn/ireturn/lreturn/freturn/dreturn
演算のバイトコード
● iadd/isub/imul/idiv
● ladd/lsub/lmul/ldiv
● dadd/dsub/dmul/ddiv
● fadd/fsub/fmul/fdiv
バイトコードの統一
● 型をもったまま汎用の構文
● 新しい型を自然に拡張できる
● vload/vstore/vreturn
● vadd/vsub/vmul/vdiv
既存コードは省略形
● iload → vload :I
● daload → vaload :D
– さらにinvokeinterface Array.getElementの略に
できるかも!
バイトコードレベルの
Specialization不要
まとめ
● コンピュータは変わる
● Javaも変わる
● あんたはどうだい?

コンピューティングとJava~なにわTECH道