[インデックス 18470] ファイルの概要
コミット
commit 44c252bda29585b4ee9f18e77e68c658207e6f0a
Author: Nick Craig-Wood <nick@craig-wood.com>
Date: Wed Feb 12 13:24:52 2014 -0500
An ARM version of sha1block.go with a big improvement in throughput
(up to 2.8x).
This is a partially unrolled version which performs better for small
hashes and only sacrifices a small amount of ultimate speed to a fully
unrolled version which uses 5k of code.
Code size
Before 1636 bytes
After 1880 bytes
15% larger
Benchmarks on Samsung Exynos 5 ARMv7 Chromebook
benchmark old ns/op new ns/op delta
BenchmarkHash8Bytes 1907 1136 -40.43%
BenchmarkHash1K 20280 7547 -62.79%
BenchmarkHash8K 148469 52576 -64.59%
benchmark old MB/s new MB/s speedup
BenchmarkHash8Bytes 4.19 7.04 1.68x
BenchmarkHash1K 50.49 135.68 2.69x
BenchmarkHash8K 55.18 155.81 2.82x
LGTM=dave, agl
R=dave, bradfitz, agl, adg, nick
CC=golang-codereviews
https://golang.org/cl/56990044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/44c252bda29585b4ee9f18e77e68c658207e6f0a
元コミット内容
このコミットは、Go言語のcrypto/sha1
パッケージにARMアーキテクチャ向けのSHA-1ハッシュ計算の最適化されたアセンブリ実装を追加するものです。具体的には、sha1block.go
の既存のGo実装に代わるsha1block_arm.s
という新しいファイルが導入され、スループットが最大2.8倍向上しています。
この最適化は、部分的にアンロールされた(unrolled)バージョンであり、小さなハッシュに対してより良いパフォーマンスを発揮し、完全にアンロールされたバージョン(コードサイズが5KBになる)と比較して、最終的な速度のわずかな犠牲でコードサイズの増加を抑えています。
ベンチマーク結果は、Samsung Exynos 5 ARMv7 Chromebook上で、特に大きなデータサイズ(1KB、8KB)において、処理速度が大幅に向上していることを示しています。
変更の背景
Go言語の標準ライブラリに含まれる暗号化パッケージは、様々なプラットフォームで効率的に動作することが求められます。特に、モバイルデバイスや組み込みシステムで広く利用されているARMアーキテクチャにおいて、ハッシュ計算のようなCPU負荷の高い処理のパフォーマンスは重要です。
このコミットの背景には、Goのcrypto/sha1
パッケージの既存のGo実装がARMプロセッサ上で十分な性能を発揮していなかったという課題がありました。SHA-1のような暗号学的ハッシュ関数は、データ整合性の検証、デジタル署名、パスワードの保存など、多くのアプリケーションで利用されます。これらの処理が高速化されることで、Goアプリケーション全体の応答性や効率が向上します。
アセンブリ言語による最適化は、特定のCPUアーキテクチャの特性(レジスタの利用、命令セット、パイプライン処理など)を最大限に活用し、Go言語で書かれたコードよりも高いパフォーマンスを引き出すための一般的な手法です。このコミットは、ARMv7アーキテクチャに特化した最適化を導入することで、GoのSHA-1実装のボトルネックを解消し、ARMベースのシステムでのGoアプリケーションの競争力を高めることを目的としています。
前提知識の解説
SHA-1 (Secure Hash Algorithm 1)
SHA-1は、アメリカ国家安全保障局(NSA)によって設計された暗号学的ハッシュ関数です。任意の長さの入力データから、160ビット(20バイト)の固定長のハッシュ値(メッセージダイジェスト)を生成します。このハッシュ値は、入力データが少しでも変更されると大きく変化するという性質(雪崩効果)を持ち、データの改ざん検出に利用されます。
SHA-1の計算プロセスは、入力データを512ビット(64バイト)のブロックに分割し、各ブロックに対して80ラウンドの複雑なビット演算と論理演算を繰り返すことで行われます。各ラウンドでは、5つの32ビットレジスタ(A, B, C, D, E)の状態が更新され、最終的にこれらのレジスタの値がハッシュ値となります。
ARMアーキテクチャ
ARM(Advanced RISC Machine)は、モバイルデバイス、組み込みシステム、IoTデバイスなどで広く採用されているRISC(Reduced Instruction Set Computer)ベースのプロセッサアーキテクチャです。低消費電力と高い性能効率が特徴です。ARMプロセッサは、特定のタスクを高速化するためのSIMD(Single Instruction, Multiple Data)命令や暗号化拡張命令など、様々な拡張機能を持っています。
アセンブリ言語
アセンブリ言語は、特定のCPUアーキテクチャの機械語命令と1対1に対応する低レベルのプログラミング言語です。アセンブリ言語を使用することで、プログラマはCPUのレジスタ、メモリ、命令セットを直接制御し、非常に最適化されたコードを書くことができます。Go言語では、パフォーマンスが重要な部分(特に暗号化やシステムコールなど)で、Goアセンブリ言語(Plan 9アセンブラの構文に基づく)が利用されることがあります。
ループアンローリング (Loop Unrolling)
ループアンローリングは、プログラムの最適化手法の一つです。ループの各イテレーションで実行される処理を複数回分、ループ本体に展開することで、ループのオーバーヘッド(ループカウンタの更新、条件分岐の評価など)を削減し、命令レベルの並列性を高めることを目的とします。
SHA-1のような固定回数(80ラウンド)のループを持つアルゴリズムでは、ループアンローリングは非常に効果的です。例えば、80回のループを1回ずつ実行する代わりに、5回分の処理をまとめて1つの大きなブロックとして実行し、それを16回繰り返すといった形です。これにより、CPUのパイプラインがより効率的に利用され、キャッシュミスが減少し、全体的な実行速度が向上します。
ただし、ループアンローリングはコードサイズを増加させるというデメリットもあります。このコミットでは、「部分的にアンロールされたバージョン」を採用することで、コードサイズの増加を抑えつつ、パフォーマンス向上を図っています。
技術的詳細
このコミットの主要な技術的詳細は、Goのcrypto/sha1
パッケージにおけるSHA-1ブロック処理のARMアセンブリ実装です。
SHA-1ブロック処理の構造
SHA-1アルゴリズムのコアは、512ビット(64バイト)の入力ブロックを処理し、5つの32ビットレジスタ(A, B, C, D, E)の状態を更新する80ラウンドの処理です。これらのラウンドは、以下の4つのタイプに分類されます。
- ラウンド 0-15: タイプ1。入力データ(
w[i]
)をロードし、計算に含めます。 - ラウンド 16-19: タイプ1。データはロードせず、以前の
w
値からシャッフルして新しいw
値を生成します。 - ラウンド 20-39: タイプ2。データはロードせず、シャッフルした
w
値を使用します。 - ラウンド 40-59: タイプ3。データはロードせず、シャッフルした
w
値を使用します。 - ラウンド 60-79: タイプ4。データはロードせず、シャッフルした
w
値を使用します。
各ラウンドでは、以下の処理が行われます。
- データのロードまたはシャッフル。
b
,c
,d
レジスタに対するラウンドごとの関数(FUNC1
,FUNC2
,FUNC3
,FUNC4
)の計算。- 結果を
a
,b
,c
,d
,e
の5つのレジスタに混合し、レジスタをローテーション。
アセンブリ実装の最適化
sha1block_arm.s
では、以下の最適化が施されています。
-
レジスタ割り当て: ARMプロセッサのレジスタを効率的に使用しています。
data
(R0): 入力データへのポインタconst
(R1): SHAラウンドの定数a
,b
,c
,d
,e
(R2-R6): SHA-1アキュムレータt0
,t1
,t2
(R7, R8, R11): 一時レジスタctr
(R12): ループカウンタw
(R14):w
バッファへのポインタ 特に、a
,b
,c
,d
,e
レジスタのローテーションは、明示的なMOV
命令ではなく、ラウンドマクロへの引数をローテーションさせることで実装されており、命令数を削減しています。
-
マクロの活用: SHA-1の各ラウンドの処理を
LOAD
,SHUFFLE
,FUNC1
〜FUNC4
,MIX
,ROUND1
〜ROUND4
といったマクロとして定義しています。これにより、コードの可読性を保ちつつ、繰り返しパターンを効率的に記述し、コンパイル時に展開されることでインライン化の恩恵を受けます。LOAD(e)
: 入力データから4バイトを読み込み、32ビットワードに変換してw
バッファに格納し、e
レジスタに加算します。バイトオーダーの変換(ビッグエンディアンからリトルエンディアン、またはその逆)も考慮されています。SHUFFLE(e)
: SHA-1のメッセージスケジュール関数を実装しています。以前のw
値から新しいw
値を計算し、e
レジスタに加算します。FUNC1
〜FUNC4
: SHA-1のラウンド関数を実装しています。これらはビット単位の論理演算(AND, ORR, EOR, MVN)を組み合わせています。MIX
: 各ラウンドの最後に、レジスタa
を左に5ビットローテートし、b
を左に30ビットローテートし、e
にラウンド関数と定数を加算する処理を行います。
-
部分的なループアンローリング: コミットメッセージにあるように、この実装は「部分的にアンロールされたバージョン」です。
loop1
,loop2
,loop3
,loop4
というラベルで示されるように、各ラウンドタイプで複数のROUND
マクロ呼び出しが連続して記述されており、これがアンローリングされた部分です。例えば、loop1
ではROUND1
が5回連続で呼び出されています。これにより、ループのオーバーヘッドを削減し、命令の並列実行を促進します。 -
スタックフレームの管理:
p_end
,p_data
,w_buf
,saved
といったオフセットでスタックフレームを定義し、関数呼び出し規約に従ってレジスタと一時データを効率的に保存・復元しています。特に、SHA-1の5つのアキュムレータレジスタ(a,b,c,d,e)は、各ブロック処理の開始時にロードされ、終了時に保存されます。
ベンチマーク結果の分析
コミットメッセージに記載されているベンチマーク結果は、この最適化の有効性を示しています。
ns/op
(nanoseconds per operation): 処理にかかる時間。値が小さいほど高速。MB/s
(megabytes per second): 1秒あたりの処理量(スループット)。値が大きいほど高速。
特に注目すべきは、BenchmarkHash1K
とBenchmarkHash8K
におけるMB/s
の劇的な向上です。それぞれ2.69倍、2.82倍のスループット向上を達成しており、これは大きなデータブロックのハッシュ計算において、新しいARMアセンブリ実装が既存のGo実装よりもはるかに効率的であることを示しています。BenchmarkHash8Bytes
のような小さなデータサイズでも1.68倍の向上を見せており、部分的なアンローリングが小さなハッシュにも効果的であることがわかります。
コードサイズは15%増加していますが、パフォーマンスの向上と比較すると許容範囲内と判断されています。
コアとなるコードの変更箇所
このコミットでは、主に以下の3つのファイルが変更されています。
src/pkg/crypto/sha1/sha1block.go
src/pkg/crypto/sha1/sha1block_arm.s
(新規追加)src/pkg/crypto/sha1/sha1block_decl.go
src/pkg/crypto/sha1/sha1block.go
--- a/src/pkg/crypto/sha1/sha1block.go
+++ b/src/pkg/crypto/sha1/sha1block.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build !amd64,!386
+// +build !amd64,!386,!arm
// SHA1 block step.
// In its own file so that a faster assembly or C version
このファイルは、Goで書かれたSHA-1ブロック処理のデフォルト実装です。変更点は、ビルドタグに!arm
が追加されたことです。これは、「amd64
、386
、またはarm
以外のアーキテクチャの場合にこのファイルを使用する」という意味になります。これにより、ARMアーキテクチャでは新しく追加されるアセンブリ実装が優先的に使用されるようになります。
src/pkg/crypto/sha1/sha1block_arm.s
new file mode 100644
index 0000000000..5917e8b24b
--- /dev/null
+++ b/src/pkg/crypto/sha1/sha1block_arm.s
@@ -0,0 +1,217 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+//
+// ARM version of md5block.go
+
+#include "../../../cmd/ld/textflag.h"
+
+// SHA1 block routine. See sha1block.go for Go equivalent.
+//
+// There are 80 rounds of 4 types:
+// - rounds 0-15 are type 1 and load data (ROUND1 macro).
+// - rounds 16-19 are type 1 and do not load data (ROUND1x macro).
+// - rounds 20-39 are type 2 and do not load data (ROUND2 macro).
+// - rounds 40-59 are type 3 and do not load data (ROUND3 macro).
+// - rounds 60-79 are type 4 and do not load data (ROUND4 macro).
+//
+// Each round loads or shuffles the data, then computes a per-round
+// function of b, c, d, and then mixes the result into and rotates the
+// five registers a, b, c, d, e holding the intermediate results.
+//
+// The register rotation is implemented by rotating the arguments to
+// the round macros instead of by explicit move instructions.
+
+// Register definitions
+data = 0 // Pointer to incoming data
+const = 1 // Current constant for SHA round
+a = 2 // SHA1 accumulator
+b = 3 // SHA1 accumulator
+c = 4 // SHA1 accumulator
+d = 5 // SHA1 accumulator
+e = 6 // SHA1 accumulator
+t0 = 7 // Temporary
+t1 = 8 // Temporary
+// r9, r10 are forbidden
+// r11 is OK provided you check the assembler that no synthetic instructions use it
+t2 = 11 // Temporary
+ctr = 12 // loop counter
+w = 14 // point to w buffer
+
+// func block(dig *digest, p []byte)
+// 0(FP) is *digest
+// 4(FP) is p.array (struct Slice)
+// 8(FP) is p.len
+//12(FP) is p.cap
+//
+// Stack frame
+p_end = -4 // -4(SP) pointer to the end of data
+p_data = p_end - 4 // -8(SP) current data pointer
+w_buf = p_data - 4*80 // -328(SP) 80 words temporary buffer w uint32[80]
+saved = w_buf - 4*5 // -348(SP) saved sha1 registers a,b,c,d,e - these must be last
+// Total size +4 for saved LR is 352
+
+ // w[i] = p[j]<<24 | p[j+1]<<16 | p[j+2]<<8 | p[j+3]
+ // e += w[i]
+#define LOAD(e) \
+ MOVBU 2(R(data)), R(t0) ; \
+ MOVBU 3(R(data)), R(t1) ; \
+ MOVBU 1(R(data)), R(t2) ; \
+ ORR R(t0)<<8, R(t1), R(t0) ; \
+ MOVBU.P 4(R(data)), R(t1) ; \
+ ORR R(t2)<<16, R(t0), R(t0) ; \
+ ORR R(t1)<<24, R(t0), R(t0) ; \
+ MOVW.P R(t0), 4(R(w))\t\t ; \
+ ADD R(t0), R(e), R(e)
+
+ // tmp := w[(i-3)&0xf] ^ w[(i-8)&0xf] ^ w[(i-14)&0xf] ^ w[(i)&0xf]
+ // w[i&0xf] = tmp<<1 | tmp>>(32-1)
+ // e += w[i&0xf]
+#define SHUFFLE(e) \
+ MOVW (-16*4)(R(w)), R(t0) ; \
+ MOVW (-14*4)(R(w)), R(t1) ; \
+ MOVW (-8*4)(R(w)), R(t2) ; \
+ EOR R(t0), R(t1), R(t0) ; \
+ MOVW (-3*4)(R(w)), R(t1) ; \
+ EOR R(t2), R(t0), R(t0) ; \
+ EOR R(t0), R(t1), R(t0) ; \
+ MOVW R(t0)@>(32-1), R(t0) ; \
+ MOVW.P R(t0), 4(R(w))\t ; \
+ ADD R(t0), R(e), R(e)
+
+ // t1 = (b & c) | ((~b) & d)
+#define FUNC1(a, b, c, d, e) \
+ MVN R(b), R(t1)\t ; \
+ AND R(b), R(c), R(t0) ; \
+ AND R(d), R(t1), R(t1) ; \
+ ORR R(t0), R(t1), R(t1)
+
+ // t1 = b ^ c ^ d
+#define FUNC2(a, b, c, d, e) \
+ EOR R(b), R(c), R(t1) ; \
+ EOR R(d), R(t1), R(t1)
+
+ // t1 = (b & c) | (b & d) | (c & d) =
+ // t1 = (b & c) | ((b | c) & d)
+#define FUNC3(a, b, c, d, e) \
+ ORR R(b), R(c), R(t0) ; \
+ AND R(b), R(c), R(t1) ; \
+ AND R(d), R(t0), R(t0) ; \
+ ORR R(t0), R(t1), R(t1)
+
+#define FUNC4 FUNC2
+
+ // a5 := a<<5 | a>>(32-5)
+ // b = b<<30 | b>>(32-30)
+ // e = a5 + t1 + e + const
+#define MIX(a, b, c, d, e) \
+ ADD R(t1), R(e), R(e)\t ; \
+ MOVW R(b)@>(32-30), R(b)\t ; \
+ ADD R(a)@>(32-5), R(e), R(e) ; \
+ ADD R(const), R(e), R(e)
+
+#define ROUND1(a, b, c, d, e) \
+ LOAD(e)\t\t; \
+ FUNC1(a, b, c, d, e)\t; \
+ MIX(a, b, c, d, e)
+
+#define ROUND1x(a, b, c, d, e) \
+ SHUFFLE(e)\t; \
+ FUNC1(a, b, c, d, e)\t; \
+ MIX(a, b, c, d, e)
+
+#define ROUND2(a, b, c, d, e) \
+ SHUFFLE(e)\t; \
+ FUNC2(a, b, c, d, e)\t; \
+ MIX(a, b, c, d, e)
+
+#define ROUND3(a, b, c, d, e) \
+ SHUFFLE(e)\t; \
+ FUNC3(a, b, c, d, e)\t; \
+ MIX(a, b, c, d, e)
+
+#define ROUND4(a, b, c, d, e) \
+ SHUFFLE(e)\t; \
+ FUNC4(a, b, c, d, e)\t; \
+ MIX(a, b, c, d, e)
+
+
+// func block(dig *digest, p []byte)
+TEXT ·block(SB), 0, $352-16
+ MOVW p+4(FP), R(data) // pointer to the data
+ MOVW p_len+8(FP), R(t0) // number of bytes
+ ADD R(data), R(t0)
+ MOVW R(t0), p_end(SP) // pointer to end of data
+
+ // Load up initial SHA1 accumulator
+ MOVW dig+0(FP), R(t0)
+ MOVM.IA (R(t0)), [R(a),R(b),R(c),R(d),R(e)]
+
+loop:
+ // Save registers at SP+4 onwards
+ MOVM.IB [R(a),R(b),R(c),R(d),R(e)], (R13)
+
+ MOVW $w_buf(SP), R(w)
+ MOVW $0x5A827999, R(const)
+ MOVW $3, R(ctr)
+loop1: ROUND1(a, b, c, d, e)
+ ROUND1(e, a, b, c, d)
+ ROUND1(d, e, a, b, c)
+ ROUND1(c, d, e, a, b)
+ ROUND1(b, c, d, e, a)
+ SUB.S $1, R(ctr)
+ BNE loop1
+
+ ROUND1(a, b, c, d, e)
+ ROUND1x(e, a, b, c, d)
+ ROUND1x(d, e, a, b, c)
+ ROUND1x(c, d, e, a, b)
+ ROUND1x(b, c, d, e, a)
+
+ MOVW $0x6ED9EBA1, R(const)
+ MOVW $4, R(ctr)
+loop2: ROUND2(a, b, c, d, e)
+ ROUND2(e, a, b, c, d)
+ ROUND2(d, e, a, b, c)
+ ROUND2(c, d, e, a, b)
+ ROUND2(b, c, d, e, a)
+ SUB.S $1, R(ctr)
+ BNE loop2
+
+ MOVW $0x8F1BBCDC, R(const)
+ MOVW $4, R(ctr)
+loop3: ROUND3(a, b, c, d, e)
+ ROUND3(e, a, b, c, d)
+ ROUND3(d, e, a, b, c)
+ ROUND3(c, d, e, a, b)
+ ROUND3(b, c, d, e, a)
+ SUB.S $1, R(ctr)
+ BNE loop3
+
+ MOVW $0xCA62C1D6, R(const)
+ MOVW $4, R(ctr)
+loop4: ROUND4(a, b, c, d, e)
+ ROUND4(e, a, b, c, d)
+ ROUND4(d, e, a, b, c)
+ ROUND4(c, d, e, a, b)
+ ROUND4(b, c, d, e, a)
+ SUB.S $1, R(ctr)
+ BNE loop4
+
+ // Accumulate - restoring registers from SP+4
+ MOVM.IB (R13), [R(t0),R(t1),R(t2),R(ctr),R(w)]
+ ADD R(t0), R(a)
+ ADD R(t1), R(b)
+ ADD R(t2), R(c)
+ ADD R(ctr), R(d)
+ ADD R(w), R(e)
+
+ MOVW p_end(SP), R(t0)
+ CMP R(t0), R(data)
+ BLO loop
+
+ // Save final SHA1 accumulator
+ MOVW dig+0(FP), R(t0)
+ MOVM.IA [R(a),R(b),R(c),R(d),R(e)], (R(t0))
+
+ RET
このファイルは新規追加されたもので、ARMアーキテクチャ向けのSHA-1ブロック処理のアセンブリ実装です。
- ヘッダー: Goアセンブリの標準的なヘッダーと、
../../../cmd/ld/textflag.h
のインクルードが含まれています。 - レジスタ定義: SHA-1計算に必要なレジスタ(
data
,const
,a
〜e
,t0
〜t2
,ctr
,w
)にシンボリックな名前を割り当てています。 - 関数シグネチャ:
func block(dig *digest, p []byte)
というGo関数のアセンブリ実装であることを示しています。引数とスタックフレームのレイアウトがコメントで説明されています。 - マクロ定義:
LOAD
,SHUFFLE
,FUNC1
〜FUNC4
,MIX
,ROUND1
〜ROUND4
といったマクロが定義されています。これらはSHA-1の各ステップをARMアセンブリ命令で実装しており、レジスタ操作、ビットシフト、論理演算、加算などが含まれます。特に、LOAD
マクロはバイトオーダーの変換とw
バッファへの格納を効率的に行っています。 - メインルーチン (
TEXT ·block(SB)
):- 入力データポインタと長さをレジスタにロードし、データの終端ポインタを計算します。
- SHA-1の初期アキュムレータ値(
dig
構造体から)をレジスタa
〜e
にロードします。 loop
ラベルで示されるメインループは、入力データブロックを順次処理します。- 各ブロック処理の開始時に、現在のSHA-1アキュムレータレジスタの値をスタックに保存します(
MOVM.IB
)。 - SHA-1の80ラウンドは、
loop1
,loop2
,loop3
,loop4
という部分的にアンロールされたループで実装されています。各ループは、対応するROUND
マクロを複数回呼び出すことで、効率的な計算を行います。 - 各ラウンドタイプに対応する定数(
0x5A827999
,0x6ED9EBA1
,0x8F1BBCDC
,0xCA62C1D6
)がconst
レジスタにロードされます。 - 各ブロック処理の終了時に、保存しておいたレジスタの値を復元し、現在のSHA-1アキュムレータ値に加算します(
ADD
)。 - すべてのデータブロックが処理されるまでループを続けます。
- 最終的なSHA-1アキュムレータ値を
dig
構造体に保存し、関数を終了します(RET
)。
src/pkg/crypto/sha1/sha1block_decl.go
--- a/src/pkg/crypto/sha1/sha1block_decl.go
+++ b/src/pkg/crypto/sha1/sha1block_decl.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.\n
-// +build amd64 386
+// +build amd64 386 arm
package sha1
このファイルは、amd64
および386
アーキテクチャ向けにSHA-1ブロック処理のGo宣言(アセンブリ実装のGo側のインターフェース)を提供するものです。変更点は、ビルドタグにarm
が追加されたことです。これにより、amd64
、386
、**およびarm
**アーキテクチャの場合にこのファイルがビルドに含まれるようになります。これは、sha1block_arm.s
で定義されたアセンブリ関数block
がGoコードから呼び出せるようにするための宣言です。
コアとなるコードの解説
このコミットのコアとなるコードは、src/pkg/crypto/sha1/sha1block_arm.s
に記述されたARMアセンブリ実装です。
このアセンブリコードは、SHA-1アルゴリズムの計算をARMプロセッサ上で最大限に効率化するために設計されています。
-
レジスタの最適利用: SHA-1の計算は、5つの32ビットレジスタ(A, B, C, D, E)の状態を繰り返し更新するプロセスです。このアセンブリコードでは、これらのレジスタをARMの物理レジスタに直接マッピングし、中間結果を保持するために一時レジスタを賢く利用しています。特に、SHA-1のラウンドごとにレジスタがローテーションする特性を、マクロの引数をローテーションさせることで、余分な
MOV
命令を発生させずに実現しています。これは、命令数を減らし、パイプラインの効率を高める上で非常に重要です。 -
マクロによる抽象化と最適化:
LOAD
マクロは、入力バイト列から32ビットワードを効率的に読み込み、必要に応じてバイトオーダーを調整し、SHA-1のメッセージスケジュールバッファw
に格納します。同時に、その値を現在のハッシュ状態に加算します。MOVBU
(バイトを符号なしで移動)、ORR
(ビット単位OR)、MOVW.P
(ワードを移動し、ポインタを更新)などの命令を組み合わせて、メモリからのデータロードと変換を最適化しています。SHUFFLE
マクロは、SHA-1のメッセージスケジュール関数(w[i] = (w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]) << 1 | ...
)を実装しています。これは、以前のw
値から新しいw
値を計算するための複雑なビット演算を含みます。EOR
(排他的OR)とMOVW
(ワード移動とローテート)命令を駆使して、この計算を効率的に行います。FUNC1
〜FUNC4
マクロは、SHA-1の各ラウンドで使用される非線形関数を実装しています。これらはAND
,ORR
,MVN
(ビット反転)などの基本的な論理演算を組み合わせています。MIX
マクロは、各ラウンドの最後にハッシュ状態を更新する部分です。レジスタのローテーション(a
を5ビット、b
を30ビット)と、ラウンド関数、現在のハッシュ状態、ラウンド定数を加算する処理を含みます。ADD
とMOVW
(ローテート付き)命令が使用されます。
-
部分的なループアンローリング: SHA-1の80ラウンドは、4つの異なるタイプのラウンドに分けられます。このアセンブリコードでは、各ラウンドタイプに対して部分的にループをアンロールしています。例えば、
loop1
ではROUND1
マクロが5回連続で呼び出され、その後ループカウンタを減らして分岐します。これにより、ループの開始・終了時のオーバーヘッド(カウンタの初期化、比較、分岐)が削減され、CPUの命令パイプラインがより深く、効率的に利用されるようになります。完全にアンロールするとコードサイズが大きくなりすぎるため、この「部分的なアンローリング」は、パフォーマンスとコードサイズのバランスを取るための賢明な選択です。 -
メモリとスタックの管理:
w_buf
として80ワード(320バイト)の一時バッファをスタック上に確保し、SHA-1のメッセージスケジュールに必要なデータを格納しています。また、関数呼び出し規約に従って、SHA-1のアキュムレータレジスタをスタックに保存・復元することで、関数呼び出し間の状態を正しく維持しています。
これらの最適化の組み合わせにより、ARMプロセッサ上でのSHA-1ハッシュ計算のスループットが大幅に向上し、特に大きなデータブロックの処理において顕著な効果を発揮しています。
関連リンク
- Go言語の
crypto/sha1
パッケージのドキュメント: https://pkg.go.dev/crypto/sha1 - Go言語のアセンブリについて: https://go.dev/doc/asm
- SHA-1アルゴリズムの概要 (Wikipedia): https://ja.wikipedia.org/wiki/SHA-1
- ARMアーキテクチャの概要 (Wikipedia): https://ja.wikipedia.org/wiki/ARM%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3
参考にした情報源リンク
- Go言語の
crypto/sha1
パッケージのソースコード (GitHub): https://github.com/golang/go/tree/master/src/crypto/sha1 - Go言語のアセンブリに関する公式ドキュメント: https://go.dev/doc/asm
- ARMの最適化ガイド(一般的な情報源として): https://developer.arm.com/documentation/den0024/a/
- SHA-1アルゴリズムの詳細な説明(RFC 3174など)
- Web検索結果 (Google Search):
go.dev
medium.com
min.io
github.com
arm.com
stackexchange.com
ntu.edu.tw
golang.org
rosettacode.org
(これらのURLは、Web検索ツールが提供した情報源のドメインです。具体的な記事への直接リンクは、検索結果の変動性やプライバシーの観点から、上記「GitHub上でのコミットページへのリンク」のように固定されたもの以外は記載していません。)