Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 18166] ファイルの概要

このコミットは、Go言語の標準ライブラリである crypto/sha1crypto/sha256crypto/sha512 パッケージにおけるパフォーマンス改善を目的としています。具体的には、ハッシュ計算時に部分的な入力ブロックをスクラッチ領域にコピーする際の処理を、手動で記述されたループからGo言語の組み込み関数 copy に置き換えることで、特に小さなデータブロックを頻繁にハッシュ化する際の効率を向上させています。

コミット

commit 29fe067ba7257bcfa7b1a15304592997b0d3c294
Author: Joel Sing <jsing@google.com>
Date:   Mon Jan 6 01:34:56 2014 +1100

    crypto/sha1, crypto/sha256, crypto/sha512: use copy for partial block
    
    Use copy rather than a hand rolled loop when moving a partial input
    block to the scratch area. This results in a reasonable performance
    gain when partial blocks are written.
    
    Benchmarks on Intel(R) Xeon(R) CPU X5650  @ 2.67GHz with Go amd64:
    
           benchmark               old MB/s     new MB/s  speedup
    SHA1   BenchmarkHash8Bytes        18.37        22.80    1.24x
    SHA256 BenchmarkHash8Bytes        11.86        13.78    1.16x
    SHA512 BenchmarkHash8Bytes         4.51         5.24    1.16x
    
           benchmark              old ns/op    new ns/op    delta
    SHA1   BenchmarkHash8Bytes          435          350  -19.54%
    SHA256 BenchmarkHash8Bytes          674          580  -13.95%
    SHA512 BenchmarkHash8Bytes         1772         1526  -13.88%
    
    R=agl, dave, bradfitz
    CC=golang-codereviews
    https://golang.org/cl/35840044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/29fe067ba7257bcfa7b1a15304592997b0d3c294

元コミット内容

このコミットの目的は、crypto/sha1crypto/sha256crypto/sha512 パッケージにおいて、部分的な入力ブロックをスクラッチ領域に移動する際に、手書きのループではなく copy 関数を使用することです。これにより、部分的なブロックが書き込まれる際のパフォーマンスが向上します。

ベンチマーク結果(Intel(R) Xeon(R) CPU X5650 @ 2.67GHz, Go amd64)は以下の通りです。

ベンチマーク旧 MB/s新 MB/s速度向上旧 ns/op新 ns/op差分
SHA1 BenchmarkHash8Bytes18.3722.801.24x435350-19.54%
SHA256 BenchmarkHash8Bytes11.8613.781.16x674580-13.95%
SHA512 BenchmarkHash8Bytes4.515.241.16x17721526-13.88%

変更の背景

Go言語の暗号化ハッシュ関数(SHA1, SHA256, SHA512)の実装において、入力データがハッシュアルゴリズムのブロックサイズに満たない場合(「部分ブロック」と呼ばれる)、そのデータを一時的なバッファ(スクラッチ領域)に格納し、次の入力データと結合して完全なブロックを形成してから処理を行う必要があります。

以前の実装では、この部分ブロックのコピーに手動で記述された for ループが使用されていました。しかし、Go言語にはスライス間のデータコピーを効率的に行うための組み込み関数 copy が存在します。copy 関数は、多くの場合、手動で記述されたループよりも最適化されており、特にアセンブリレベルでの最適化や、CPUのSIMD命令(Single Instruction, Multiple Data)を活用することで、高速なデータ転送を実現できます。

このコミットの背景には、Go言語の標準ライブラリのパフォーマンスを継続的に改善するという目標があります。特に、暗号化ハッシュ関数は多くのアプリケーションで利用される基本的な機能であり、その性能向上はシステム全体のパフォーマンスに寄与します。ベンチマーク結果が示すように、小さなデータ(8バイト)のハッシュ計算において顕著な改善が見られたことから、この変更が特にストリーミング処理や、小さなチャンクでデータが供給されるシナリオにおいて有効であることが示唆されます。

前提知識の解説

1. ハッシュ関数 (Cryptographic Hash Function)

ハッシュ関数は、任意の長さの入力データ(メッセージ)を受け取り、固定長の短い出力(ハッシュ値、メッセージダイジェスト、フィンガープリントなどと呼ばれる)を生成する一方向性の関数です。暗号学的ハッシュ関数は、以下の特性を持つことが求められます。

  • 一方向性 (Preimage Resistance): ハッシュ値から元の入力データを効率的に復元することは困難である。
  • 第二原像耐性 (Second Preimage Resistance): ある入力データと同じハッシュ値を持つ別の入力データを効率的に見つけることは困難である。
  • 衝突耐性 (Collision Resistance): 同じハッシュ値を持つ異なる2つの入力データを効率的に見つけることは困難である。

SHA-1、SHA-256、SHA-512は、米国国家安全保障局(NSA)によって設計されたSecure Hash Algorithm (SHA) ファミリーに属する暗号学的ハッシュ関数です。

  • SHA-1: 160ビットのハッシュ値を生成します。現在ではセキュリティ上の脆弱性が指摘されており、新規のアプリケーションでの使用は推奨されません。
  • SHA-256: 256ビットのハッシュ値を生成します。SHA-2ファミリーの一部であり、広く利用されています。
  • SHA-512: 512ビットのハッシュ値を生成します。SHA-2ファミリーの一部であり、SHA-256よりも長いハッシュ値を生成するため、より高いセキュリティレベルが求められる場合に利用されます。

これらのハッシュ関数は、内部的にデータを固定長のブロックに分割し、各ブロックに対して一連の数学的・論理的演算を適用することでハッシュ値を計算します。

2. Go言語のスライス (Slice)

Go言語のスライスは、配列のセグメントを参照するデータ構造です。スライスは、基となる配列へのポインタ、長さ(len)、容量(cap)の3つの要素で構成されます。スライスは動的なサイズ変更が可能であり、Go言語で最も頻繁に使用されるデータ構造の一つです。

3. copy 関数 (Go Built-in Function)

Go言語の組み込み関数 copy は、ソーススライスからデスティネーションスライスへ要素をコピーするために使用されます。そのシグネチャは copy(dst, src []Type) int であり、コピーされた要素の数を返します。copy 関数は、Goランタイムによって高度に最適化されており、多くの場合、手動で記述されたループよりも高速に動作します。これは、コンパイラが特定のアーキテクチャの特性(例: SIMD命令)を利用して、より効率的なコードを生成できるためです。

4. d.x[0:]d.x[:]

Go言語のスライス表記において、d.x[0:]d.x[:] はどちらもスライス d.x の全体を参照する新しいスライスを作成します。機能的には同じですが、d.x[:] の方がより簡潔で慣用的な表現とされています。このコミットでは、この慣用的な表現への変更も行われています。

技術的詳細

このコミットの核心は、SHAハッシュ関数の Write メソッドにおける最適化です。Write メソッドは、入力データ p を受け取り、それをハッシュ計算のために処理します。ハッシュ計算はブロック単位で行われるため、入力データがブロックサイズに満たない場合、その「部分ブロック」は内部バッファ d.x に一時的に格納されます。

変更前のコードでは、この部分ブロックのコピーに以下のような手動ループが使用されていました。

// 変更前 (例: SHA1)
if d.nx > 0 { // d.nx はバッファに既に格納されているデータの量
    n := len(p) // 入力データの長さ
    if n > chunk-d.nx { // 入力データがバッファの残りの容量を超える場合
        n = chunk - d.nx // コピーする量をバッファの残りの容量に制限
    }
    for i := 0; i < n; i++ {
        d.x[d.nx+i] = p[i] // 1バイトずつ手動でコピー
    }
    d.nx += n
    if d.nx == chunk {
        block(d, d.x[0:]) // 完全なブロックが形成されたら処理
        d.nx = 0
    }
    p = p[n:]
}

このループは、入力データ p の先頭から n バイトを、内部バッファ d.xd.nx オフセットからコピーしています。

変更後のコードでは、この手動ループがGo言語の組み込み関数 copy に置き換えられました。

// 変更後 (例: SHA1)
if d.nx > 0 {
    n := copy(d.x[d.nx:], p) // d.x[d.nx:] はd.xのd.nx以降のスライス
    d.nx += n
    if d.nx == chunk {
        block(d, d.x[:]) // d.x[0:] から d.x[:] へ変更
        d.nx = 0
    }
    p = p[n:]
}

copy(d.x[d.nx:], p) は、以下の処理を効率的に行います。

  1. d.x[d.nx:]: これは、内部バッファ d.xd.nx インデックスから始まるスライスを作成します。これが copy 関数のデスティネーション(コピー先)となります。
  2. p: これは、入力データを含むスライスであり、copy 関数のソース(コピー元)となります。
  3. copy 関数は、p の内容を d.x[d.nx:] にコピーします。コピーされるバイト数は、デスティネーションスライスまたはソーススライスのいずれか短い方の長さによって決定されます。これにより、手動ループで行っていた n の計算と範囲チェックが不要になります。
  4. copy 関数は実際にコピーされたバイト数を返します。この戻り値が n に代入され、d.nx の更新に使用されます。

この変更により、コンパイラは copy 関数の呼び出しを、より最適化されたアセンブリ命令(例えば、REP MOVSB のようなブロック転送命令や、SIMD命令)に変換できるようになります。これにより、特に小さなデータブロックが頻繁にコピーされるシナリオ(例: BenchmarkHash8Bytes のようなベンチマーク)において、CPUの効率的な利用が可能となり、パフォーマンスが向上します。

また、block(d, d.x[0:])block(d, d.x[:]) に変更されています。これは機能的な変更ではなく、Go言語におけるスライス全体の参照の慣用的な表現への変更です。どちらも d.x スライスの基底配列全体を参照する新しいスライスを生成しますが、d.x[:] の方がより簡潔で推奨される書き方です。

コアとなるコードの変更箇所

変更は src/pkg/crypto/sha1/sha1.gosrc/pkg/crypto/sha256/sha256.gosrc/pkg/crypto/sha512/sha512.go の3つのファイルに共通して適用されています。

各ファイルの Write メソッド内の d.nx > 0 の条件ブロックが変更されています。

変更前:

// src/pkg/crypto/sha1/sha1.go (同様の変更がsha256.go, sha512.goにも適用)
if d.nx > 0 {
    n := len(p)
    if n > chunk-d.nx {
        n = chunk - d.nx
    }
    for i := 0; i < n; i++ {
        d.x[d.nx+i] = p[i]
    }
    d.nx += n
    if d.nx == chunk {
        block(d, d.x[0:]) // 変更点1
        d.nx = 0
    }
    p = p[n:]
}

変更後:

// src/pkg/crypto/sha1/sha1.go (同様の変更がsha256.go, sha512.goにも適用)
if d.nx > 0 {
    n := copy(d.x[d.nx:], p) // 変更点2: 手動ループをcopy関数に置き換え
    d.nx += n
    if d.nx == chunk {
        block(d, d.x[:]) // 変更点1: d.x[0:] から d.x[:] へ変更
        d.nx = 0
    }
    p = p[n:]
}

コアとなるコードの解説

d はハッシュダイジェストの状態を保持する構造体(digest)のインスタンスです。 d.x は、ハッシュ計算のための内部バッファ(スクラッチ領域)であり、通常はバイトスライスです。 d.nx は、d.x バッファに現在格納されているデータのバイト数を示します。 chunk は、ハッシュアルゴリズムのブロックサイズ(例: SHA1は64バイト、SHA256は64バイト、SHA512は128バイト)です。

Write メソッドが呼び出され、入力データ p が渡されると、まず d.nx > 0 の条件がチェックされます。これは、前回の Write 呼び出しで部分的なデータが d.x に残っているかどうかを確認しています。

変更前: 手動ループでは、まず n を計算して、入力データ p から d.x にコピーできる最大バイト数を決定していました。これは、p の長さと d.x の残りの容量 (chunk - d.nx) の小さい方です。その後、for ループを使って1バイトずつ p[i] から d.x[d.nx+i] へとデータをコピーしていました。このバイトごとのコピーは、オーバーヘッドが大きく、特に大量の小さなコピーが発生する場合に非効率的でした。

変更後: n := copy(d.x[d.nx:], p) の行が、このコピー処理を大幅に簡素化し、効率化しています。

  • d.x[d.nx:] は、d.x スライスの d.nx インデックスから始まる部分スライスを作成します。これは、部分ブロックを格納するための d.x 内の利用可能な領域を指します。
  • copy 関数は、このターゲットスライス (d.x[d.nx:]) とソーススライス (p) を受け取り、可能な限り多くのバイトをコピーします。copy 関数は、コピー先の容量またはコピー元の長さのいずれか小さい方までしかコピーしません。これにより、手動ループで行っていた n の計算と境界チェックが不要になります。
  • copy 関数は実際にコピーされたバイト数を n として返します。この n を使って d.nx を更新し、d.x に格納されているデータの総量を追跡します。

if d.nx == chunk のブロックは、d.x バッファが完全に満たされた場合に、block 関数を呼び出してハッシュ計算の次のステップを実行し、d.nx をリセットするロジックです。ここで block(d, d.x[0:])block(d, d.x[:]) に変更されていますが、これは前述の通り、スライス全体の参照の慣用的な表現への変更であり、機能的な違いはありません。

この変更により、Goランタイムの最適化された copy 関数が利用されるため、特に小さなデータブロックのコピーにおいて、CPUの効率的な利用とパフォーマンスの向上が実現されました。ベンチマーク結果は、この変更が実際にSHAハッシュ関数の性能を向上させたことを明確に示しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード
  • 暗号学的ハッシュ関数に関する一般的な情報源 (例: Wikipedia, NIST publications)
  • Go言語のベンチマークに関する一般的な知識
  • Go言語のスライスに関する一般的な知識
  • Go言語の copy 関数の内部実装と最適化に関する情報 (Goのソースコードや関連するブログ記事など)
  • Go言語のコードレビューシステム (Gerrit) のCL (Change-list) ページ: https://golang.org/cl/35840044
    • このCLページには、コミットメッセージ、変更されたファイル、レビューコメントなどが含まれており、コミットの背景や議論を理解する上で非常に有用です。
    • 特に、ベンチマーク結果がコミットメッセージに直接含まれているため、変更の意図と効果を明確に理解できます。
  • Go言語の crypto パッケージの歴史と進化に関する情報 (Goのリリースノートやメーリングリストのアーカイブなど)
    • このコミットは2014年のものであり、Go言語の初期の最適化努力の一環として位置づけられます。
  • SIMD命令とデータコピーの最適化に関する一般的なコンピュータアーキテクチャの知識。
    • copy 関数が手動ループよりも高速である理由の一つとして、コンパイラがSIMD命令を活用できる点が挙げられます。
# [インデックス 18166] ファイルの概要

このコミットは、Go言語の標準ライブラリである `crypto/sha1`、`crypto/sha256`、`crypto/sha512` パッケージにおけるパフォーマンス改善を目的としています。具体的には、ハッシュ計算時に部分的な入力ブロックをスクラッチ領域にコピーする際の処理を、手動で記述されたループからGo言語の組み込み関数 `copy` に置き換えることで、特に小さなデータブロックを頻繁にハッシュ化する際の効率を向上させています。

## コミット

commit 29fe067ba7257bcfa7b1a15304592997b0d3c294 Author: Joel Sing jsing@google.com Date: Mon Jan 6 01:34:56 2014 +1100

crypto/sha1, crypto/sha256, crypto/sha512: use copy for partial block

Use copy rather than a hand rolled loop when moving a partial input
block to the scratch area. This results in a reasonable performance
gain when partial blocks are written.

Benchmarks on Intel(R) Xeon(R) CPU X5650  @ 2.67GHz with Go amd64:

       benchmark               old MB/s     new MB/s  speedup
SHA1   BenchmarkHash8Bytes        18.37        22.80    1.24x
SHA256 BenchmarkHash8Bytes        11.86        13.78    1.16x
SHA512 BenchmarkHash8Bytes         4.51         5.24    1.16x

       benchmark              old ns/op    new ns/op    delta
SHA1   BenchmarkHash8Bytes          435          350  -19.54%
SHA256 BenchmarkHash8Bytes          674          580  -13.95%
SHA512 BenchmarkHash8Bytes         1772         1526  -13.88%

R=agl, dave, bradfitz
CC=golang-codereviews
https://golang.org/cl/35840044

## GitHub上でのコミットページへのリンク

[https://github.com/golang/go/commit/29fe067ba7257bcfa7b1a15304592997b0d3c294](https://github.com/golang/go/commit/29fe067ba7257bcfa7b1a15304592997b0d3c294)

## 元コミット内容

このコミットの目的は、`crypto/sha1`、`crypto/sha256`、`crypto/sha512` パッケージにおいて、部分的な入力ブロックをスクラッチ領域に移動する際に、手書きのループではなく `copy` 関数を使用することです。これにより、部分的なブロックが書き込まれる際のパフォーマンスが向上します。

ベンチマーク結果(Intel(R) Xeon(R) CPU X5650 @ 2.67GHz, Go amd64)は以下の通りです。

| ベンチマーク           | 旧 MB/s | 新 MB/s | 速度向上 | 旧 ns/op | 新 ns/op | 差分    |
| :------------------- | :------ | :------ | :------- | :------- | :------- | :------ |
| SHA1 BenchmarkHash8Bytes | 18.37   | 22.80   | 1.24x    | 435      | 350      | -19.54% |
| SHA256 BenchmarkHash8Bytes | 11.86   | 13.78   | 1.16x    | 674      | 580      | -13.95% |
| SHA512 BenchmarkHash8Bytes | 4.51    | 5.24    | 1.16x    | 1772     | 1526     | -13.88% |

## 変更の背景

Go言語の暗号化ハッシュ関数(SHA1, SHA256, SHA512)の実装において、入力データがハッシュアルゴリズムのブロックサイズに満たない場合(「部分ブロック」と呼ばれる)、そのデータを一時的なバッファ(スクラッチ領域)に格納し、次の入力データと結合して完全なブロックを形成してから処理を行う必要があります。

以前の実装では、この部分ブロックのコピーに手動で記述された `for` ループが使用されていました。しかし、Go言語にはスライス間のデータコピーを効率的に行うための組み込み関数 `copy` が存在します。`copy` 関数は、多くの場合、手動で記述されたループよりも最適化されており、特にアセンブリレベルでの最適化や、CPUのSIMD命令(Single Instruction, Multiple Data)を活用することで、高速なデータ転送を実現できます。

このコミットの背景には、Go言語の標準ライブラリのパフォーマンスを継続的に改善するという目標があります。特に、暗号化ハッシュ関数は多くのアプリケーションで利用される基本的な機能であり、その性能向上はシステム全体のパフォーマンスに寄与します。ベンチマーク結果が示すように、小さなデータ(8バイト)のハッシュ計算において顕著な改善が見られたことから、この変更が特にストリーミング処理や、小さなチャンクでデータが供給されるシナリオにおいて有効であることが示唆されます。

## 前提知識の解説

### 1. ハッシュ関数 (Cryptographic Hash Function)

ハッシュ関数は、任意の長さの入力データ(メッセージ)を受け取り、固定長の短い出力(ハッシュ値、メッセージダイジェスト、フィンガープリントなどと呼ばれる)を生成する一方向性の関数です。暗号学的ハッシュ関数は、以下の特性を持つことが求められます。

*   **一方向性 (Preimage Resistance)**: ハッシュ値から元の入力データを効率的に復元することは困難である。
*   **第二原像耐性 (Second Preimage Resistance)**: ある入力データと同じハッシュ値を持つ別の入力データを効率的に見つけることは困難である。
*   **衝突耐性 (Collision Resistance)**: 同じハッシュ値を持つ異なる2つの入力データを効率的に見つけることは困難である。

SHA-1、SHA-256、SHA-512は、米国国家安全保障局(NSA)によって設計されたSecure Hash Algorithm (SHA) ファミリーに属する暗号学的ハッシュ関数です。

*   **SHA-1**: 160ビットのハッシュ値を生成します。現在ではセキュリティ上の脆弱性が指摘されており、新規のアプリケーションでの使用は推奨されません。
*   **SHA-256**: 256ビットのハッシュ値を生成します。SHA-2ファミリーの一部であり、広く利用されています。
*   **SHA-512**: 512ビットのハッシュ値を生成します。SHA-2ファミリーの一部であり、SHA-256よりも長いハッシュ値を生成するため、より高いセキュリティレベルが求められる場合に利用されます。

これらのハッシュ関数は、内部的にデータを固定長のブロックに分割し、各ブロックに対して一連の数学的・論理的演算を適用することでハッシュ値を計算します。

### 2. Go言語のスライス (Slice)

Go言語のスライスは、配列のセグメントを参照するデータ構造です。スライスは、基となる配列へのポインタ、長さ(`len`)、容量(`cap`)の3つの要素で構成されます。スライスは動的なサイズ変更が可能であり、Go言語で最も頻繁に使用されるデータ構造の一つです。

### 3. `copy` 関数 (Go Built-in Function)

Go言語の組み込み関数 `copy` は、ソーススライスからデスティネーションスライスへ要素をコピーするために使用されます。そのシグネチャは `copy(dst, src []Type) int` であり、コピーされた要素の数を返します。`copy` 関数は、Goランタイムによって高度に最適化されており、多くの場合、手動で記述されたループよりも高速に動作します。これは、コンパイラが特定のアーキテクチャの特性(例: SIMD命令)を利用して、より効率的なコードを生成できるためです。

### 4. `d.x[0:]` と `d.x[:]`

Go言語のスライス表記において、`d.x[0:]` と `d.x[:]` はどちらもスライス `d.x` の全体を参照する新しいスライスを作成します。機能的には同じですが、`d.x[:]` の方がより簡潔で慣用的な表現とされています。このコミットでは、この慣用的な表現への変更も行われています。

## 技術的詳細

このコミットの核心は、SHAハッシュ関数の `Write` メソッドにおける最適化です。`Write` メソッドは、入力データ `p` を受け取り、それをハッシュ計算のために処理します。ハッシュ計算はブロック単位で行われるため、入力データがブロックサイズに満たない場合、その「部分ブロック」は内部バッファ `d.x` に一時的に格納されます。

変更前のコードでは、この部分ブロックのコピーに以下のような手動ループが使用されていました。

```go
// 変更前 (例: SHA1)
if d.nx > 0 { // d.nx はバッファに既に格納されているデータの量
    n := len(p) // 入力データの長さ
    if n > chunk-d.nx { // 入力データがバッファの残りの容量を超える場合
        n = chunk - d.nx // コピーする量をバッファの残りの容量に制限
    }
    for i := 0; i < n; i++ {
        d.x[d.nx+i] = p[i] // 1バイトずつ手動でコピー
    }
    d.nx += n
    if d.nx == chunk {
        block(d, d.x[0:]) // 完全なブロックが形成されたら処理
        d.nx = 0
    }
    p = p[n:]
}

このループは、入力データ p の先頭から n バイトを、内部バッファ d.xd.nx オフセットからコピーしています。

変更後のコードでは、この手動ループがGo言語の組み込み関数 copy に置き換えられました。

// 変更後 (例: SHA1)
if d.nx > 0 {
    n := copy(d.x[d.nx:], p) // d.x[d.nx:] はd.xのd.nx以降のスライス
    d.nx += n
    if d.nx == chunk {
        block(d, d.x[:]) // d.x[0:] から d.x[:] へ変更
        d.nx = 0
    }
    p = p[n:]
}

copy(d.x[d.nx:], p) は、以下の処理を効率的に行います。

  1. d.x[d.nx:]: これは、内部バッファ d.xd.nx インデックスから始まるスライスを作成します。これが copy 関数のデスティネーション(コピー先)となります。
  2. p: これは、入力データを含むスライスであり、copy 関数のソース(コピー元)となります。
  3. copy 関数は、p の内容を d.x[d.nx:] にコピーします。コピーされるバイト数は、デスティネーションスライスまたはソーススライスのいずれか短い方の長さによって決定されます。これにより、手動ループで行っていた n の計算と範囲チェックが不要になります。
  4. copy 関数は実際にコピーされたバイト数を返します。この戻り値が n に代入され、d.nx の更新に使用されます。

この変更により、コンパイラは copy 関数の呼び出しを、より最適化されたアセンブリ命令(例えば、REP MOVSB のようなブロック転送命令や、SIMD命令)に変換できるようになります。これにより、特に小さなデータブロックが頻繁にコピーされるシナリオ(例: BenchmarkHash8Bytes のようなベンチマーク)において、CPUの効率的な利用が可能となり、パフォーマンスが向上します。

また、block(d, d.x[0:])block(d, d.x[:]) に変更されています。これは機能的な変更ではなく、Go言語におけるスライス全体の参照の慣用的な表現への変更です。どちらも d.x スライスの基底配列全体を参照する新しいスライスを生成しますが、d.x[:] の方がより簡潔で推奨される書き方です。

コアとなるコードの変更箇所

変更は src/pkg/crypto/sha1/sha1.gosrc/pkg/crypto/sha256/sha256.gosrc/pkg/crypto/sha512/sha512.go の3つのファイルに共通して適用されています。

各ファイルの Write メソッド内の d.nx > 0 の条件ブロックが変更されています。

変更前:

// src/pkg/crypto/sha1/sha1.go (同様の変更がsha256.go, sha512.goにも適用)
if d.nx > 0 {
    n := len(p)
    if n > chunk-d.nx {
        n = chunk - d.nx
    }
    for i := 0; i < n; i++ {
        d.x[d.nx+i] = p[i]
    }
    d.nx += n
    if d.nx == chunk {
        block(d, d.x[0:]) // 変更点1
        d.nx = 0
    }
    p = p[n:]
}

変更後:

// src/pkg/crypto/sha1/sha1.go (同様の変更がsha256.go, sha512.goにも適用)
if d.nx > 0 {
    n := copy(d.x[d.nx:], p) // 変更点2: 手動ループをcopy関数に置き換え
    d.nx += n
    if d.nx == chunk {
        block(d, d.x[:]) // 変更点1: d.x[0:] から d.x[:] へ変更
        d.nx = 0
    }
    p = p[n:]
}

コアとなるコードの解説

d はハッシュダイジェストの状態を保持する構造体(digest)のインスタンスです。 d.x は、ハッシュ計算のための内部バッファ(スクラッチ領域)であり、通常はバイトスライスです。 d.nx は、d.x バッファに現在格納されているデータのバイト数を示します。 chunk は、ハッシュアルゴリズムのブロックサイズ(例: SHA1は64バイト、SHA256は64バイト、SHA512は128バイト)です。

Write メソッドが呼び出され、入力データ p が渡されると、まず d.nx > 0 の条件がチェックされます。これは、前回の Write 呼び出しで部分的なデータが d.x に残っているかどうかを確認しています。

変更前: 手動ループでは、まず n を計算して、入力データ p から d.x にコピーできる最大バイト数を決定していました。これは、p の長さと d.x の残りの容量 (chunk - d.nx) の小さい方です。その後、for ループを使って1バイトずつ p[i] から d.x[d.nx+i] へとデータをコピーしていました。このバイトごとのコピーは、オーバーヘッドが大きく、特に大量の小さなコピーが発生する場合に非効率的でした。

変更後: n := copy(d.x[d.nx:], p) の行が、このコピー処理を大幅に簡素化し、効率化しています。

  • d.x[d.nx:] は、d.x スライスの d.nx インデックスから始まる部分スライスを作成します。これは、部分ブロックを格納するための d.x 内の利用可能な領域を指します。
  • copy 関数は、このターゲットスライス (d.x[d.nx:]) とソーススライス (p) を受け取り、可能な限り多くのバイトをコピーします。copy 関数は、コピー先の容量またはコピー元の長さのいずれか小さい方までしかコピーしません。これにより、手動ループで行っていた n の計算と境界チェックが不要になります。
  • copy 関数は実際にコピーされたバイト数を n として返します。この n を使って d.nx を更新し、d.x に格納されているデータの総量を追跡します。

if d.nx == chunk のブロックは、d.x バッファが完全に満たされた場合に、block 関数を呼び出してハッシュ計算の次のステップを実行し、d.nx をリセットするロジックです。ここで block(d, d.x[0:])block(d, d.x[:]) に変更されていますが、これは前述の通り、スライス全体の参照の慣用的な表現への変更であり、機能的な違いはありません。

この変更により、Goランタイムの最適化された copy 関数が利用されるため、特に小さなデータブロックのコピーにおいて、CPUの効率的な利用とパフォーマンスの向上が実現されました。ベンチマーク結果は、この変更が実際にSHAハッシュ関数の性能を向上させたことを明確に示しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード
  • 暗号学的ハッシュ関数に関する一般的な情報源 (例: Wikipedia, NIST publications)
  • Go言語のベンチマークに関する一般的な知識
  • Go言語のスライスに関する一般的な知識
  • Go言語の copy 関数の内部実装と最適化に関する情報 (Goのソースコードや関連するブログ記事など)
  • Go言語のコードレビューシステム (Gerrit) のCL (Change-list) ページ: https://golang.org/cl/35840044
    • このCLページには、コミットメッセージ、変更されたファイル、レビューコメントなどが含まれており、コミットの背景や議論を理解する上で非常に有用です。
    • 特に、ベンチマーク結果がコミットメッセージに直接含まれているため、変更の意図と効果を明確に理解できます。
  • Go言語の crypto パッケージの歴史と進化に関する情報 (Goのリリースノートやメーリングリストのアーカイブなど)
    • このコミットは2014年のものであり、Go言語の初期の最適化努力の一環として位置づけられます。
  • SIMD命令とデータコピーの最適化に関する一般的なコンピュータアーキテクチャの知識。
    • copy 関数が手動ループよりも高速である理由の一つとして、コンパイラがSIMD命令を活用できる点が挙げられます。