LoginSignup
5
2

More than 1 year has passed since last update.

go で「*」をたくさんつけるとコンパイルに30時間以上かかることがある

Last updated at Posted at 2021-08-10

これは何?

先日

という記事を書いたんだけど、そういえば go もたくさん * をつけられそうだなと思って試した。

* の付け方

こんな感じ。

go
package main

import (
  "log"
  "unsafe"
)

func main() {
  var p ********************************uintptr
  x := (uintptr)((unsafe.Pointer)(&p))
  *(*uintptr)((unsafe.Pointer)(&p)) = x
  log.Println(********************************p)
}

x の初期化と *****(略)*****p の間に GC が入ると死ぬような気がするけど気にしない。

実行するとこんな出力が得られる。

2021/08/10 18:33:55 824634449616

限界に挑戦

ruby でこんなコードを書いた。

ruby
# CC="gcc-7"
# CC="gcc-11"
CC="clang"

def test_aster(n)
  aster = "*"*n
  src=<<~"SRC"
    package main

    import (
      "log"
      "unsafe"
    )

    func main() {
      var p #{aster}uintptr
      x := (uintptr)((unsafe.Pointer)(&p))
      *(*uintptr)((unsafe.Pointer)(&p)) = x
      log.Println(#{aster}p)
    }
    SRC

  Dir.chdir("go") do
    File.open( "main.go", "w" ){ |f| f.puts(src) }
    puts %x(time go run main.go)
    puts("n=#{n}  err = #{$?}")
  end
  $?==0
end

n=1
loop do
  break unless test_aster(n)
  n*=2
end

a = [*(n/2)..n]
p(a.bsearch do |n|
  !test_aster(n)
end)

go version go1.16.5 darwin/amd64 で試したらこんな結果になった。

2021/08/08 18:28:39 824634449760
        0.48 real         0.21 user         0.20 sys

n=1  err = pid 53457 exit 0
︙
略
︙
2021/08/08 19:02:32 824634834896
     1773.83 real      1761.87 user         9.20 sys

n=8192  err = pid 53747 exit 0
2021/08/08 23:15:01 824634900432
    15149.74 real     15074.27 user        60.22 sys

n=16384  err = pid 54106 exit 0

2021/08/10 14:35:05 824635031504
   139271.45 real    139042.02 user       438.17 sys

n=32768  err = pid 56707 exit 0
# command-line-arguments
./main.go:12:14: internal compiler error: name too long: **********(略)**********uintptr

Please file a bug report including a short program that triggers the error.
https://golang.org/issue/new
     1864.84 real      1874.95 user        21.12 sys

n=65536  err = pid 79661 exit 2

ここで ctrl+C を押して止めた。

n=32768 の場合、139042秒 = 38.6時間を要した。これをあと数回やらないと結果が出ないと思われるので諦めた。

n=65536 だと失敗するので、32768 と 65536 の間のどこかに境界があるのだろう。

失敗する場合のメッセージは上記の通り

# command-line-arguments
./main.go:12:14: internal compiler error: name too long: **********(略)**********uintptr

Please file a bug report including a short program that triggers the error.
https://golang.org/issue/new

という感じ。本当は (略) の部分に六万個を超える * がある。
internal compiler error なので、実装者の予期せぬ事態が起こっている模様。

チャレンジした身としては、internal compiler error が起こることより、* が倍になるとコンパイル時間が 10倍ぐらいになるのがつらかった。

補足: ************* の意味

見出し詐欺だけど、 ************* の意味だと説明しにくいので、 *** ぐらいにしておく。

コードの上では

go
var p ***uintptr
x := (uintptr)((unsafe.Pointer)(&p))
*(*uintptr)((unsafe.Pointer)(&p)) = x
log.Println(***p)

ということになる。

普通、

  1. p の値は xxxx。p***uintptr なので、 xxxx を **uintptr の値が書いてあるアドレスとみなす。
  2. アドレス xxxx には yyyy と書いてある。yyyy は **uintptr 型なので、 yyyy を *uintptr の値が書いてあるアドレスとみなす。
  3. アドレス yyyy には zzzz と書いてある。zzzz は *uintptr 型なので、 zzzz を uintptr の値が書いてあるアドレスとみなす。
  4. というわけで、 ***p の値はアドレス zzzz に書いてある値になる。

という具合に間接参照演算子(*)は機能する。

今回は、 unsafe.Pointer と邪悪なキャストを使って、 p 自身のアドレスを p に書き込んでいる。

例えば &puintptr にキャストした値が 824634449760 の場合を考える。
この場合の ***p

  1. p の値は 824634449760p***uintptr なので、824634449760**uintptr の値が書いてあるアドレスとみなす。
  2. アドレス 824634449760 には 824634449760 と書いてある。この文脈では 824634449760**uintptr 型なので、 824634449760*uintptr の値が書いてあるアドレスとみなす。
  3. アドレス 824634449760 には 824634449760 と書いてある。この文脈では 824634449760*uintptr 型なので、 824634449760uintptr の値が書いてあるアドレスとみなす。
  4. というわけで、 ***p の値はアドレス 824634449760 に書いてある値 824634449760 になる。

という具合に解釈される。

これは * が三個しかない例だけど、見て分かる通り、何個になってもうまくいくはず。

とはいえ、p のアドレスを取っている箇所とその先にある log.Println の間で GC が発生すると、 p の値と p のアドレスが合わなくなって死ぬかもしれないと思うけどよくわからなない。死なないかな。どうだろう。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2