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

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

このコミットは、Go言語の標準ライブラリ sync/atomic パッケージ内の doc.go ファイルに対する変更です。具体的には、AddUint32 および AddUint64 関数を使用して符号なし整数から定数を減算する方法について、ドキュメントに説明を追加しています。これは、2の補数演算に不慣れな開発者向けに、符号付き正の定数の否定を実装する方法を明確にすることを目的としています。

コミット

commit c4579635cf6aafdb5db231058dd0aedee14fe0ee
Author: Rob Pike <r@golang.org>
Date:   Thu Oct 3 10:40:42 2013 -0700

    sync/atomic: explain how to subtract an unsigned constant
    Explain for those unfamiliar with twos-complement arithmetic how to
    implement negation of signed positive constant.
    Fixes #6408.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/14267044

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

https://github.com/golang/go/commit/c4579635cf6aafdb5db231058dd0aedee14fe0ee

元コミット内容

sync/atomic: explain how to subtract an unsigned constant
Explain for those unfamiliar with twos-complement arithmetic how to
implement negation of signed positive constant.
Fixes #6408.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/14267044

変更の背景

このコミットの背景には、Go言語の sync/atomic パッケージが提供するアトミック操作の利用に関する、開発者の理解を深めるという目的があります。特に、符号なし整数型 (uint32, uint64) に対してアトミックな減算操作を行いたい場合、直接的な減算関数は提供されていません。代わりに、AddUint32AddUint64 のような加算関数を使用する必要があります。

しかし、符号なし整数に対する減算をアトミックな加算として表現するには、2の補数表現の知識が必要です。多くのプログラマ、特に低レベルのビット演算や数値表現に慣れていない開発者にとって、この概念は直感的ではない場合があります。

コミットメッセージにある Fixes #6408 は、この変更がGoのIssue 6408を解決することを示しています。Issue 6408は、「sync/atomic のドキュメントに AddUint32AddUint64 を使って減算する方法の例を追加する」という内容でした。これは、開発者が符号なし整数に対するアトミックな減算をどのように実装すればよいかについて、混乱や疑問を抱いていたことを示唆しています。

このコミットは、このような混乱を解消し、sync/atomic パッケージのドキュメントを改善することで、開発者がより正確かつ安全にアトミック操作を利用できるようにすることを目的としています。特に、パフォーマンスが重要な並行処理のコンテキストでは、アトミック操作の正しい理解と使用が不可欠です。

前提知識の解説

1. Go言語の sync/atomic パッケージ

sync/atomic パッケージは、Go言語において低レベルのアトミックなメモリ操作を提供する標準ライブラリです。アトミック操作とは、複数のゴルーチン(Goの軽量スレッド)が同時に共有データにアクセスしようとしたときに、その操作が中断されずに単一の不可分な操作として実行されることを保証するものです。これにより、競合状態(race condition)を防ぎ、データの整合性を保つことができます。

このパッケージは、ミューテックス(sync.Mutex)のようなロック機構よりも粒度の細かい同期プリミティブを提供し、特定の条件下ではより高いパフォーマンスを発揮することがあります。提供される主な関数には、以下のようなものがあります。

  • AddInt32, AddUint32, AddInt64, AddUint64: 指定されたアドレスの整数値にデルタ値をアトミックに加算します。
  • LoadInt32, LoadUint32 など: 指定されたアドレスの値をアトミックに読み込みます。
  • StoreInt32, StoreUint32 など: 指定されたアドレスに値をアトミックに書き込みます。
  • CompareAndSwapInt32, CompareAndSwapUint32 など: 指定されたアドレスの値が期待値と一致する場合にのみ、新しい値にアトミックに交換します(CAS操作)。

2. 2の補数 (Two's Complement)

2の補数表現は、コンピュータにおいて負の整数を表現するための最も一般的な方法です。また、減算を加算として実行するためにも使用されます。

基本的な考え方: Nビットの2進数において、ある正の数 X の2の補数は、2^N - X として定義されます。これにより、X の2の補数を加算することは、X を減算することと同じ効果を持ちます。

計算方法:

  1. ビット反転 (1の補数): 負にしたい正の数のすべてのビットを反転させます(0を1に、1を0に)。
  2. 1を加算: ビット反転した結果に1を加算します。

例 (8ビットの場合): 正の数 5 (00000101) を負の数 -5 として表現する場合:

  1. ビット反転: 11111010
  2. 1を加算: 11111010 + 1 = 11111011 (これが -5 の2の補数表現)

減算への応用: A - B を計算したい場合、A + (-B) と考えることができます。ここで -BB の2の補数表現です。

このコミットで重要なのは、符号なし整数型 (uint32, uint64) に対して減算を行う際に、この2の補数の原理を応用することです。符号なし整数は負の値を直接表現できませんが、2の補数を利用することで、加算操作を通じて実質的な減算を実現できます。

技術的詳細

このコミットは、src/pkg/sync/atomic/doc.go ファイルに、AddUint32 および AddUint64 関数を用いた符号なし整数からの減算方法に関する説明を追加しています。

追加された説明は以下の通りです。

  • AddUint32 の説明の下に追加:
    // To subtract a signed positive constant value c from x, do AddUint32(&x, ^uint32(c-1)).
    // In particular, to decrement x, do AddUint32(&x, ^uint32(0)).
    
  • AddUint64 の説明の下に追加:
    // To subtract a signed positive constant value c from x, do AddUint64(&x, ^uint64(c-1)).
    // In particular, to decrement x, do AddUint64(&x, ^uint64(0)).
    

これらのコメントは、符号なし整数 x から符号付き正の定数 c を減算したい場合に、AddUint32(&x, ^uint32(c-1)) または AddUint64(&x, ^uint64(c-1)) を使用することを推奨しています。

^uint32(c-1) のメカニズム

この表現は、2の補数演算を応用したものです。 ^ 演算子はGo言語におけるビットごとのNOT(ビット反転)です。

  1. c-1: まず、減算したい定数 c から1を引きます。
  2. uint32(c-1): 結果を uint32 または uint64 型にキャストします。
  3. ^(...): その値のすべてのビットを反転させます。これは、c-1 の1の補数を計算することに相当します。

なぜ c-1 なのか? 2の補数表現では、負の数 -X~X + 1 (ビット反転して1を加える) と表現されます。 したがって、x - cx + (-c) と考えた場合、-c~c + 1 となります。 これを AddUint32 に渡す引数として考えると、AddUint32(&x, ~c + 1) となります。

しかし、Goのビット反転演算子 ^ は、符号なし整数に対しては (2^N - 1) - X と等価です。つまり、^X~X と同じです。 したがって、~c + 1^c + 1 と書けます。

ここで、^uint32(c-1) を展開してみましょう。 ^uint32(c-1) は、uint32 の最大値 (2^32 - 1) から (c-1) を引いた値に等しいです。 つまり、(2^32 - 1) - (c-1) = 2^32 - 1 - c + 1 = 2^32 - c

符号なし整数演算では、x + (2^32 - c) はオーバーフローを伴って x - c と同じ結果になります。 例えば、x = 10, c = 3 (uint32) の場合: AddUint32(&x, ^uint32(3-1)) AddUint32(&x, ^uint32(2)) uint32(2)00...010 ^uint32(2)11...101 (これは uint32 の最大値 - 2) 10 + (uint32の最大値 - 2) は、オーバーフローして 8 になります。

より直感的に理解するために、^uint32(0) のケースを見てみましょう。 ^uint32(0) は、すべてのビットが1である uint32 の最大値 (2^32 - 1) を表します。 AddUint32(&x, ^uint32(0)) は、x(2^32 - 1) を加算します。 符号なし整数演算では、x + (2^32 - 1)x - 1 と同じ効果を持ちます。これは、x から1を減算する(デクリメントする)操作に相当します。

このテクニックは、アトミックな減算操作が直接提供されていない場合に、アトミックな加算操作を利用して減算を実現するための標準的な方法です。特に、カウンタのデクリメントなど、頻繁に発生する操作で役立ちます。

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

変更は src/pkg/sync/atomic/doc.go ファイルのみです。

--- a/src/pkg/sync/atomic/doc.go
+++ b/src/pkg/sync/atomic/doc.go
@@ -94,12 +94,16 @@ func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapp
 func AddInt32(addr *int32, delta int32) (new int32)
 
 // AddUint32 atomically adds delta to *addr and returns the new value.
+// To subtract a signed positive constant value c from x, do AddUint32(&x, ^uint32(c-1)).
+// In particular, to decrement x, do AddUint32(&x, ^uint32(0)).
 func AddUint32(addr *uint32, delta uint32) (new uint32)
 
 // AddInt64 atomically adds delta to *addr and returns the new value.
 func AddInt64(addr *int64, delta int64) (new int64)
 
 // AddUint64 atomically adds delta to *addr and returns the new value.
+// To subtract a signed positive constant value c from x, do AddUint64(&x, ^uint64(c-1)).
+// In particular, to decrement x, do AddUint64(&x, ^uint64(0)).
 func AddUint64(addr *uint64, delta uint64) (new uint64)
 
 // AddUintptr atomically adds delta to *addr and returns the new value.

コアとなるコードの解説

このコミットは、Go言語の sync/atomic パッケージのドキュメンテーションファイル doc.go に、AddUint32 および AddUint64 関数の使用例として、符号なし整数からの減算方法に関するコメントを追加しています。

追加されたコメントは、以下の2つのパターンを示しています。

  1. 任意の正の定数 c を減算する場合:

    • AddUint32(&x, ^uint32(c-1))
    • AddUint64(&x, ^uint64(c-1)) これは、x から c を減算するアトミックな操作を実現するための一般的な形式です。前述の「技術的詳細」セクションで説明したように、これは2の補数演算の原理を利用して、減算を加算として表現しています。
  2. 1を減算する(デクリメントする)場合:

    • AddUint32(&x, ^uint32(0))
    • AddUint64(&x, ^uint64(0)) これは、特定のケースとして、c=1 の場合に ^uint32(1-1) すなわち ^uint32(0) となることを示しています。^uint32(0) は、すべてのビットが1である uint32 の最大値を表し、符号なし整数演算においてこの値を加算することは、実質的に1を減算することと同じ効果を持ちます。これは、カウンタのデクリメントなど、非常に頻繁に使用される操作です。

これらのコメントは、関数の動作を変更するものではなく、あくまでドキュメントの改善です。しかし、これにより、sync/atomic パッケージを扱う開発者が、符号なし整数に対するアトミックな減算をどのように安全かつ効率的に実装できるかについて、明確なガイダンスを得られるようになります。これは、特に並行プログラミングにおいて、正確なアトミック操作の理解と適用が不可欠であるため、非常に価値のあるドキュメントの追加と言えます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • GitHubのGoリポジトリのIssueおよびコミット履歴
  • 2の補数に関する一般的なコンピュータサイエンスの資料