SHA-1の高速化tips
2007/9/15
[email protected]
1/15
目次
前置き
初級編
地道に速度を稼ぐ
中級編
自己書換えによるお気軽ループ/インライン展開
マニアック編
Firefoxの特性を使った高速化
2/15
前置き
高速化
本当に必要?
プロファイルした?
アルゴリズムの見直しは?
メンテ大変だけどコストに見合う?
小手先技術はすぐ廃れるけど覚悟はいい?
以上,すべてyesなら次へ
3/15
初級編(1/6)
Blowfishの暗号化コアを例に
コアのリファレンス実装 : 8byte入力, 8byte出力
encCore
var
var
var
for
: function(inp) {
xL = inp[0];
xR = inp[1];
tmp;
(var i = 0; i < this.N; i++) {
xL ^= this.P[i];
xR ^= this.F(xL);
tmp = xL;
xL = xR;
xR = tmp;
}
tmp = xL;
xL = xR;
xR = tmp;
xR = xR ^ this.P[this.N];
xL = xL ^ this.P[this.N + 1];
if (xL < 0) xL += 4294967296;
if (xR < 0) xR += 4294967296;
return [xL, xR];
IE 6
処理時間 134
Fx2.0
162
高速化率 100% 100%
@PentiumD2.8GHz
}
4/15
初級編(2/6)
入出力を同一に
function (inp) { return out; } → function(inout);
encCore
var
var
var
for
: function(inout) {
xL = inout[0];
xR = inout[1];
tmp;
(var i = 0; i < this.N; i++) {
xL ^= this.P[i];
xR ^= this.F(xL);
tmp = xL;
xL = xR;
xR = tmp;
}
tmp = xL;
xL = xR;
xR = tmp;
xR = xR ^ this.P[this.N];
xL = xL ^ this.P[this.N + 1];
if (xL < 0) xL += 4294967296;
if (xR < 0) xR += 4294967296;
inout[0] = xL;
inout[1] = xR;
IE 6
処理時間 134
Fx2.0
155
高速化率 100% 104%
}
5/15
初級編(3/6)
thisのalias作成
this.N → Nなど
encCore
var
var
var
var
for
: function(inout) {
xL = inout[0];
xR = inout[1];
tmp;
N = this.N, P = this.P;
(var i = 0; i < N; i++) {
xL ^= P[i];
xR ^= this.F(xL);
tmp = xL;
xL = xR;
xR = tmp;
IE 6
処理時間 128
Fx2.0
155
高速化率 104% 104%
}
tmp = xL;
xL = xR;
xR = tmp;
xR = xR ^ P[N];
xL = xL ^ P[N + 1];
if (xL < 0) xL += 4294967296;
if (xR < 0) xR += 4294967296;
inout[0] = xL; inout[1] = xR;
}
6/15
初級編(4/6)
1回unloopによるswapの除去
block暗号化における常套手段
encCore
var
var
var
var
for
: function(inout) {
xL = inout[0];
xR = inout[1];
tmp;
N = this.N, P = this.P;
(var i = 0; i < N; i += 2) {
xL ^= P[i];
xR ^= this.F(xL);
xR ^= P[i + 1];
xL ^= this.F(xR);
IE 6
処理時間 126
Fx2.0
153
高速化率 106% 106%
}
xL = xL ^ P[N];
xR = xR ^ P[N + 1];
if (xL < 0) xL += 4294967296;
if (xR < 0) xR += 4294967296;
inout[0] = xR; inout[1] = xL;
}
7/15
初級編(5/6)
関数呼び出しを止める
速いけど汚い
for (var i = 0; i < N; i += 2) {
var a, b, c, d, x, y;
xL ^= P[i];
x
c
x
y
= xL; d = x & 255; x >>>= 8;
= x & 255; x >>>= 8; b = x & 255;
>>>= 8; a = x & 255;
= ((S0[a] + S1[b]) ^ S2[c]) + S3[d];
処理時間
IE 6
Fx2.0
73
129
高速化率 183% 125%
xR ^= y;
xR ^= P[i + 1];
x = xR; d = x & 255; x >>>= 8;
c = x & 255; x >>>= 8; b = x & 255;
x >>>= 8; a = x & 255;
y = ((S0[a] + S1[b]) ^ S2[c]) + S3[d];
xL ^= y;
}
8/15
初級編(6/6)
ループ展開し,変数伝搬も修正
速いけど読めない
でもどうしても速度が欲しいならありかも
var a, b, c, d, x, y;
x = xL ^= P[9];
d = x & 255; x >>>= 8;
xR ^= ((S0[a] + S1[b])
x = xR ^= P[8];
d = x & 255; x >>>= 8;
xL ^= ((S0[a] + S1[b])
x = xL ^= P[7];
d = x & 255; x >>>= 8;
xR ^= ((S0[a] + S1[b])
x = xR ^= P[6];
d = x & 255; x >>>= 8;
xL ^= ((S0[a] + S1[b])
c = x & 255; x >>>= 8; b = x & 255; x >>>= 8; a = x & 255;
^ S2[c]) + S3[d];
c = x & 255; x >>>= 8; b = x & 255; x >>>= 8; a = x & 255;
^ S2[c]) + S3[d];
c = x & 255; x >>>= 8; b = x & 255; x >>>= 8; a = x & 255;
^ S2[c]) + S3[d];
c = x & 255; x >>>= 8; b = x & 255; x >>>= 8; a = x & 255;
^ S2[c]) + S3[d];
処理時間
IE 6
Fx2.0
53
103
高速化率 252% 157%
(中略)
d = x & 255; x >>>= 8;
xR ^= ((S0[a] + S1[b])
x = xR ^= P[4];
d = x & 255; x >>>= 8;
xL ^= ((S0[a] + S1[b])
x = xL ^= P[3];
d = x & 255; x >>>= 8;
xR ^= ((S0[a] + S1[b])
x = xR ^= P[2];
d = x & 255; x >>>= 8;
xL ^= ((S0[a] + S1[b])
c = x & 255; x >>>= 8; b = x & 255; x >>>= 8; a = x & 255;
^ S2[c]) + S3[d];
c = x & 255; x >>>= 8; b = x & 255; x >>>= 8; a = x & 255;
^ S2[c]) + S3[d];
c = x & 255; x >>>= 8; b = x & 255; x >>>= 8; a = x & 255;
^ S2[c]) + S3[d];
c = x & 255; x >>>= 8; b = x & 255; x >>>= 8; a = x & 255;
^ S2[c]) + S3[d];
9/15
中級編(1/3)
JavaScriptは関数もobject
なら実行前に書き換えてみよう
[email protected]
var f = targetFunction.toString().split('\n');
与えられた関数をtoString()で文字列にする
文字列を解析し書き換える
eval()を使って関数を定義し直す
これらを起動時に一度呼ぶ
10/15
中級編(2/3)
作ってみた
まじめなJavaScriptパーサは大変なので超限定版
unrollLoop(出力関数, 入力関数);
与えられた関数内のforループを展開する関数
forceInline(出力関数, 入力関数, 展開関数);
与えられた関数が呼び出す関数を展開する関数
これらを起動時に一度呼ぶ
optimize()
11/15
中級編(3/3)
ベンチマーク
下記コードに1行加えるだけ
optimize('encCore2', encCore, 'this.F');
encCore
var
var
var
var
for
: function(inout) {
xL = inout[0];
xR = inout[1];
tmp;
N = this.N, P = this.P;
(var i = 0; i < N; i += 2) {
xL ^= P[i];
xR ^= this.F(xL);
xR ^= P[i + 1];
xL ^= this.F(xR);
}
xL = xL ^ P[N];
xR = xR ^ P[N + 1];
if (xL < 0) xL += 4294967296;
if (xR < 0) xR += 4294967296;
inout[0] = xR; inout[1] = xL;
}
IE 6
Fx2.0
処理時間 126
153
手動最速
103
処理時間
53
IE 6
Fx2.0
73
101
いい線
互角
12/15
マニアック編(1/3)
ところでFxはIEの50~70%の速度しか出ない?
block暗号,ハッシュ関数系の処理では似た傾向
現象
var x = 0;
var count = 1000000;
for (var i = 0; i < count; i++) {
x += i;
}
なんの変哲もないこのコードはFxはIEの7分の1程度の
速度しかでない
countが小さいときはFxも速い
13/15
マニアック編(2/3)
原因はFxのSTORE_INT()の実装にあり
if (INT_FITS_IN_JSVAL(i)) {
v_ = INT_TO_JSVAL(i);
} else {
ok = js_NewDoubleValue(cx, (jsdouble)(i), &v_);
if (!ok) goto out;
}
絶対値が2^30-1を超える数値にはメモリ確保が伴う
大抵のLLの実装では整数を効率よく扱うために32bitのうち
1bitを判別フラグに利用している
IEは違うようだ
14/15
マニアック編(3/3)
解決方法
32bit変数を16bit x 2として扱い,
最大値が2^30にならないように実装する
これでFxでSHA-1をIEなみの速度にもっていける
ただしコードは本当に読めない(^^;
例(x ^ y)
 x = [xH:xL], y = [yH:yL] として
xH ^ yH, xL ^ yL を計算する
詳細は
http://labs.cybozu.co.jp/blog/mitsunari/
15/15
ダウンロード

光成滋生 - サイボウズ・ラボ