SHA-1の高速化tips 2007/9/15 光成滋生@サイボウズ・ラボ 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 なら実行前に書き換えてみよう オリジナルアイデアは奥さん@labs.cybozu.co.jp 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
© Copyright 2025 ExpyDoc