Go コンパイラーは、ゴルーチン内の変数をインクリメントするコードを最適化しますか?

DDD
リリース: 2024-10-29 05:55:30
オリジナル
843 人が閲覧しました

 Does the Go compiler optimize away code that increments a variable in a goroutine?

Go コンパイラーはコードを最適化していますか?

このコード内:

package main

import "time"

func main() {
    i := 1
    go func() {
        for {
            i++
        }
    }()
    <-time.After(1 * time.Second)
    println(i)
}
ログイン後にコピー

出力は常に 1しかし、for ループが何回も繰り返されるのに 1 秒で十分であることには驚きます。その理由は、Go コンパイラがコードを最適化しているためです。

Go メモリ モデルは、1 つのゴルーチン内の変数の読み取りが、同じ変数への書き込みによって生成された値を観察することを保証できる条件を指定します。別のゴルーチン。インクリメント i (i = i 1) による i への代入の後には同期イベントが続かないため、他のゴルーチンによって監視されることは保証されません。実際、積極的なコンパイラは i ステートメント全体を削除する可能性があります。

たとえば、このコードでは:

package main

import "time"

func main() {
    i := 1
    go func() {
        for {
            i++
        }
    }()
    <-time.After(1 * time.Millisecond)
    println(i)
}
ログイン後にコピー

出力は 1 です。ゴルーチンは次のように削減されます:

"".main.func1 STEXT nosplit size=2 args=0x8 locals=0x0
    0x0000 00000 (elide.go:7)   TEXT    "".main.func1(SB), NOSPLIT, <pre class="brush:php;toolbar:false">for {
    i++
}
ログイン後にコピー
-8 0x0000 00000 (elide.go:7) FUNCDATA
package main

import "time"

func main() {
    i := 1
    go func() {
        for {
            i++
            println("+1")
        }
    }()
    <-time.After(1 * time.Millisecond)
    println(i)
}
ログイン後にコピー
, gclocals·2a5305abe05176240e61b8620e19a815(SB) 0x0000 00000 (elide.go:7) FUNCDATA , gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0000 00000 (elide.go:9) JMP 0

コンパイラにとって、for ループはレジスタを永久にインクリメントすることで実装でき、基本的には何も操作を行わない for ループです。

+1
+1
<< SNIP >>
+1
+1
432
ログイン後にコピー

print ステートメントを挿入した後、

"".main.func1 STEXT size=81 args=0x8 locals=0x18
    0x0000 00000 (elide.go:7)   TEXT    "".main.func1(SB), -8
    0x0000 00000 (elide.go:7)   MOVQ    (TLS), CX
    0x0009 00009 (elide.go:7)   CMPQ    SP, 16(CX)
    0x000d 00013 (elide.go:7)   JLS 74
    0x000f 00015 (elide.go:7)   SUBQ    , SP
    0x0013 00019 (elide.go:7)   MOVQ    BP, 16(SP)
    0x0018 00024 (elide.go:7)   LEAQ    16(SP), BP
    0x001d 00029 (elide.go:7)   FUNCDATA    <pre class="brush:php;toolbar:false">==================
WARNING: DATA RACE

Read at 0x00c420094000 by 
main goroutine:
  main.main()
      /home/peter/gopath/src/lucky.go:14 +0xac

Previous write at 0x00c420094000 by 
goroutine 5:
  main.main.func1()
      /home/peter/gopath/src/lucky.go:9 +0x4e

Goroutine 5 (running) created at:
  main.main()
      /home/peter/gopath/src/lucky.go:7 +0x7a
==================
ログイン後にコピー
, gclocals·a36216b97439c93dafebe03e7f0808b5(SB) 0x001d 00029 (elide.go:7) FUNCDATA , gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x001d 00029 (elide.go:8) MOVQ "".&i+32(SP), AX 0x0022 00034 (elide.go:9) INCQ (AX) 0x0025 00037 (elide.go:10) PCDATA
package main

import (
    "sync"
    "time"
)

func main() {
    mx := new(sync.Mutex)
    i := 1
    go func() {
        for {
            mx.Lock()
            i++
            mx.Unlock()
        }
    }()
    <-time.After(1 * time.Second)
    mx.Lock()
    println(i)
    mx.Unlock()
}
ログイン後にコピー
,
41807838
ログイン後にコピー
0x0025 00037 (elide.go:10) CALL runtime.printlock(SB) 0x002a 00042 (elide.go:10) LEAQ go.string."+1\n"(SB), AX 0x0031 00049 (elide.go:10) MOVQ AX, (SP) 0x0035 00053 (elide.go:10) MOVQ , 8(SP) 0x003e 00062 (elide.go:10) PCDATA , 0x003e 00062 (elide.go:10) CALL runtime.printstring(SB) 0x0043 00067 (elide.go:10) PCDATA , 0x0043 00067 (elide.go:10) CALL runtime.printunlock(SB) 0x0048 00072 (elide.go:9) JMP 29 0x004a 00074 (elide.go:9) NOP 0x004a 00074 (elide.go:7) PCDATA , $-1 0x004a 00074 (elide.go:7) CALL runtime.morestack_noctxt(SB) 0x004f 00079 (elide.go:7) JMP 0

出力は次のとおりです:

ゴルーチンは次のように展開されます:

ゴルーチンの複雑さの増加により、コンパイラはレジスタを次の値専用にすることを考慮しなくなりました。私。 i のメモリ内値がインクリメントされ、データ競合を伴う更新がメインの goroutine に表示されるようになります。

期待される結果を得るには、同期を追加します。

出力:

以上がGo コンパイラーは、ゴルーチン内の変数をインクリメントするコードを最適化しますか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート