メインコンテンツへスキップ

Goの最適化で性能爆上げ!GC対策、ゼロコピー、オブジェクトプールまで徹底解説

·4 分
2025/03 Go 最適化 パフォーマンス GC プログラミング

Goの最適化で性能爆上げ!GC対策、ゼロコピー、オブジェクトプールまで徹底解説

引用元:https://news.ycombinator.com/item?id=43539585

nopurpose 2025-03-31T22:34:12

GCの時間を減らすためにallocationを減らすのが良いってよく言われるけど、Goのアプリのpprofを見ると、GC sweepじゃなくてGC mark phaseが時間かかってるんだよね。GC markはいつもlive root(goroutineのstackとかglobalsとか)から始まって、そこから参照を辿ってpointerを色付けする。だからGCの時間を減らすには、long living allocationを避けるのが一番良いんだ。短いallocation、GC mark phaseが届かないやつは、GCの時間にほとんど影響ないんだよ。Allocation自体はGCを早くtriggerする効果があるけど、現実のアプリではGCを避けるのはほぼ無理ゲー。依存関係がないプログラムとか、すごく丁寧に書かれたプログラム以外はね。だからGCが起きたら、GC markの時間を減らすのが一番効果的だと思うよ。

liquidgecka 2025-04-01T03:38:20

abstractionはGoでは予期せぬ形で足を引っ張ることがあるから注意が必要だよ。Interfaceを使うと、たとえオブジェクトが読み取り専用で同じscope内だけで使われるとしても、heap allocationが強制されるんだ。fmt.Printf()みたいな関数もそう。だから、for loopでiの値をprintすると、iのintegerがheapにallocationされちゃうんだ。ライブラリのAPIを全部interfaceにすると、呼び出し元は毎回heap allocationしないといけなくなるよ。

slashdev 2025-04-01T04:12:37

integerはinterfaceにinlineできると思ったんだけどなー。Goは昔はそうしてた気がする。でもplaygroundで試したら、heap allocationされちゃった:https://go.dev/play/p/zHfnQfJ9OGc

masklinn 2025-04-01T04:16:04

Goは昔はそうしてたんだよ。でも1.4で削除されたんだ:https://go.dev/doc/go1.4#runtime

kbolino 2025-04-01T13:36:40

基本的には、thin pointer(*T, chan, map)以外はboxingされるんだよね。結局、interfaceの値の2つのwordは常にpointerになるんだ。これはgarbage collectorにとっては都合が良い(escape analysisが失敗した時のallocationは別として)。標準ライブラリにはboxingを避けるためのtrickがいくつかあるみたいだよ。例えば、log/slogでstringとかtimeがどう扱われてるかとか[2]。[1]:https://github.com/teh-cmc/go-internals/blob/master/chapter2… [2]:https://cs.opensource.google/go/go/+/refs/tags/go1.24.1:src/…

ncruces 2025-04-01T21:36:42

slog.Valueはすごく便利そうだね。database/sqlがslog.Valueみたいなのを使うようになったら、garbageを大量に生成しなくなる日が来るかも?

ominous_prime 2025-04-01T13:59:53

go1.15でsmall integerのpackingがinterfaceにre-addedされたんだよ:https://go.dev/doc/go1.15#runtime

masklinn 2025-04-01T14:35:00

いや、違うんだ。go 1.15には最初の256個のpositive integerのstatic arrayがあって、interfaceのためにboxingする必要があるときは、そのarrayへのpointerを取得するんだ:https://go-review.googlesource.com/c/go/+/216401/4/src/runti… このarrayはsingle-byte stringにも使われるんだ(以前は独自のarrayがあった):https://go-review.googlesource.com/c/go/+/221979/3/src/runti…

ominous_prime 2025-04-01T17:06:00

>It didn’t, do what?
それの何が違うの?最初の256個のintegerは“small integer”だと思うけど😉
>Converting a small integer value into an interface value no longer causes allocation
single byte stringにも使えるのは知らなかった。それが役に立ったことはないけどね!

masklinn 2025-04-01T18:11:50

>It didn’t, do what?
>Reintroduce “packing into interfaces”.
完全に違うことをしたんだよ。small integerはinlineされないまま。

MarkMarine 2025-03-31T22:40:48

アロケートにかかる時間とかリソースも考慮してる?高パフォーマンスシステム作るなら、GCだけじゃなくてアロケーションも最小限にしたいよね。

nopurpose 2025-04-01T07:14:53

結局は“do less”ってことじゃん?それってどんなパフォーマンスガイドにも書いてあるし。アロケーションも他のアプリの処理と変わらないよ。
僕が言いたいのは、よくある「アロケーション減らしてGCの負担を減らす」ってアドバイスが全てじゃないってこと。短いアロケーションはGCの負担にならないし。みんな時間かけてアロケーション減らしても、結局GCの時間は変わらないってことあるある。

MarkMarine 2025-04-01T15:20:19

言いたいことはわかるけど、それってコインの裏表だよね。完全なパフォーマンス分析せずに、ある方法だけが有効だって言うのは違うと思う。(君はGCのmark時間を減らすこと、他の人はアロケーション時間を減らすこと、この記事に書いてある他の方法も同様)

aktau 2025-04-01T11:31:22

ちなみに、GoのGCについてはhttps://tip.golang.org/doc/gc-guideを見てね。
GCの頻度は、アロケーションレート(バイト単位)とライブヒープサイズに直接影響されるよ。例えば:
 -アロケーションレートを半分にすると、GCの頻度も半分になる。
 -ライブヒープサイズを2倍にすると、GCの頻度は半分になる(GOGC=100の場合)。

>…でもGoアプリのpprofを見ると、時間がかかるのはGCのmarkフェーズで、GCのsweepじゃないよね。
>sweepがmarkよりずっと安いのは確かで、君の次の発言も:
>短いアロケーションはGCのmarkフェーズに到達しないから、GC時間にほとんど影響がない。
…技術的には正しい。でも、2つの重要な点を省略してる:
 -短いアロケーションを大量に生成すると、GCがより頻繁にトリガーされる。
 -ライブヒープサイズを小さくすると(何も保持しないようにすると)、GCがより頻繁にトリガーされる。

だから、GCサイクルは安くなるけど、回数は増える。それに、アロケーションコストも大幅に増加する。これが本当に得策なのかはわからない。僕の経験ではそうじゃない。

deepsun 2025-04-01T20:42:23

興味深いね、ありがとう。でも、それらの点はそんなに相関性がないと思うな。例えば、ループ内で不要なラッパーを作成すると、アロケーションレートは2倍になるかもしれないけど、ライブヒープサイズは半分にならないよね?だって、以前はループの外にラッパーがなかったんだから。
要するに、スタイル変更(例えば、すべてのエラーに対してラッパーを作成したり、time.Timeの代わりに生の整数を使用したりする)が与える影響を見積もりたいんだ。そして、どんな例でも、君の指摘の一方に他方よりもはるかに影響を与えると思うから、「短いイテレータを作るのは全く問題ない」って主張できると思う。

nopurpose 2025-04-01T15:04:29

詳細な回答ありがとう。議論の価値を高めてくれて感謝するけど、僕のコメントの意図が伝わってない気がする。
僕は「アロケーションを減らしてGCの負担を減らす」って blanket statement に反対なんだ。それが人々を間違った方向に導いてる。Go benchの“allocs/op”でライブラリを比較したり、sync.Poolの馬鹿げた(誰がタイトループで8KBもアロケートするんだ?)マイクロベンチマークを信じたりして、GCの問題を解決しようとする。そして、多くの努力を費やした結果、GC時間がほとんど変わらなかったことに気づくんだ。
一般化するなら、僕の「長生きするアロケーションを避ける」とか、君の「バイト単位でのアロケーションレートを減らす」って方が、この記事や他の多くの記事が説いていることよりも、実際にははるかに役に立つよ。

zmj 2025-04-01T02:04:15

.NETでも同じような話だね。まずインナーループがallocation-freeであることを確認して、次にallocationが短命であることを確認して、最後に大きなallocationのlong tailをクリーンアップする。

neonsunset 2025-04-01T03:55:15

.NETは、GCがgenerationalで全体的にもっと洗練されているから、高いアロケーショントラフィックにもっと寛容だよ(tail latencyというコストはあるけど、それはワークロード次第)。
LOHに行くような巨大なアロケーションはかなり厳しいけど、実質的なinter-generationalトラフィックでも死ぬことはない。

kgeist 2025-04-01T21:51:06

ランタイムは2分ごとにGCを強制するからね。だから、頻繁にアロケートしなくても、長生きするアロケーションはGCに負担をかける可能性がある。だからDiscordはRead StatesサーバーをGoからRustに移行したんだ。

zbobet2012 2025-04-01T02:18:09

まぁ、そうかもね。hot loopでアロケートしてたら、どっちにしろ最悪だよ。Object poolは、汎用アロケータよりも効率がずっと良いから、高パフォーマンスを目指すならマジで重要。

もっとコメントを表示(1)
ncruces 2025-04-01T10:06:33

GCを完全に避けるって話じゃなくて、allocationの圧力を減らすのが大事なんだよね。アツいループでallocを避けられるなら、絶対やる価値あり。もし無理でもsync.Poolが使えるなら試してみて。allocを半分に減らしても大したことないかもしれないけど、100万回のループで毎回allocationしてたのを無くせたら、たとえすぐに死ぬallocでも効果あるよ。実際、このテクニックでパフォーマンスが2倍以上になったコードもある。

bboreham 2025-04-01T10:30:00

mark phaseが重いのは同意。でも、短命なallocationを減らす価値がないってのは違うと思うな。Goプログラムのパフォーマンス分析をよくするんだけど、1秒あたりのbytes allocatedを減らすのは常に有効だよ。

felixge 2025-04-01T14:19:23

+1。特に[]byte sliceのallocationはGCのペースを左右することが多いし、sync.Poolとかで簡単に最適化できることが多いよね。

raggi 2025-04-01T00:38:59

pprofだけじゃ全部見れないから、システムプロファイラも見てみるといいかも。

Capricorn2481 2025-03-31T23:23:18

allocation自体、GCに関係なく結構コストかかるんじゃない?

nu11ptr 2025-03-31T23:48:58

Goのallocationはそんなに悪くないよ。数年前にベンチマークしたとき、bump allocationの4倍くらいのコストだった。だから、allocationが多い状況ではarenaが有効だけど、ほとんどの場合はそこまでやる価値ないかな。

aktau 2025-04-01T10:29:08

$COMPANYのかなり最適化されたmallocと比較すると、Goのallocatorは(相対的なサイクル数でも、Goプログラム全体のサイクル数でも)C/C++のallocatorよりかなりコストが高い(3~4倍だったと思う)。GCのmetadataの設定とか、zeroingとか、やることが多いからね。最近runtime.mallocgcの最適化があったから、3~4倍っていうのは少し減ってるかも。

nu11ptr 2025-04-01T12:48:49

それ本当?mallocより3~4倍も遅いってことは、そっちのmallocはbump allocatorってことになっちゃうけど、そんな実装はありえない(普通はmodified free list allocatorだよね)。mallocgcが速くないのはわかるけど、そんなに遅いとは思わなかったな。普通のmalloc関数と同じくらい速いと思うけど、測ったことはないし、比較するのは難しいよね(CGo経由でmallocを呼ぶか、CとGoで書いてループのコストが同じになるようにしないといけないし)。

aktau 2025-04-02T08:38:24

訂正と明確化:
相対的な意味で3〜4倍高価であるという意味でした。
C + +プログラムの場合、アロケーター(割り当て+解放)はサイクルのおおよそ5%を消費します。
Goプログラムの場合、アロケーター(runtime.mallocgc)は〜20%のサイクルを消費していました(これは私が参照したデータです)。確認したところ、最近では最適化のおかげで15%近くになっています。

バイトレベルでのパフォーマンスの差はテストしていません(Goのオブジェクト構造によっても異なります)。

epcoa 2025-04-01T01:24:13

違うよ。moving multi generational GCなら、allocationは短命なオブジェクトに対してはincrementするだけだよ。

pebal 2025-04-01T03:54:47

movingでgenerationalなGCだと、高速allocationのメリットはデータの移動とコストのかかるメモリバリアで全部なくなっちゃうんだって。

gf000 2025-04-01T09:25:46

そんなことないって。ほとんどのオブジェクトはすぐ死ぬから移動しないし。移動するまでの時間もCPUの処理に比べたらめっちゃ長いから、統計的にしか影響ないよ(スループットは高いけど、たまにレイテンシが長くなる)。write-only barrierもそんなにオーバーヘッド大きくないし。

pebal 2025-04-01T15:17:46

オブジェクトがすぐ死ぬかどうかは関係なくて、ヒープにある他のオブジェクトが定期的に移動するせいでパフォーマンスが落ちるんだよ。moving GCを使うと、movingじゃないGCにはないread barrierも必要になるし。

gf000 2025-04-02T06:45:39

OSがスレッドをcontext switchするのに比べたら、その周期ってそんなに気になる?CPUのタイムラインで見たら、全然珍しいことじゃないよね。それに、ハイパフォーマンスなGC runtimeは全部movingでgenerationalな方式を選んでるんだから。

pebal 2025-04-02T07:45:31

OSがcontext switchするからって、その時間が無視できるわけじゃないよ。その間、コードは本来の処理をしてないんだから。Generational GCはmoving GCでデータの移動によるパフォーマンス低下を減らすために使われてるんだ。movingじゃないGCは侵略性が低いから、generational GCが必要なくて、完全にconcurrentにできる。

gf000 2025-04-02T08:57:21

Generational GCはmoving collectorをさらに改善して、メモリの使用効率を上げてmarkフェーズを短くするものだと思うな。完全にconcurrentなGCってある?ZGCが一番concurrentらしいけど、read barrierとかポインタのトリックでstop-the-world時間をヒープサイズに依存させないようにしてるんだって。

pebal 2025-04-02T09:24:40

Javaにはまだ完全にconcurrentなGCはないし、管理するgarbageの量とかオブジェクトを移動させるってことを考えると、完全にconcurrentなGCができる可能性は低いと思う。でも、movingじゃないGCなら完全にconcurrentにできるよ。C++のSGCLプロジェクトがそう。GoのGCが将来的に完全にconcurrentになる可能性が一番高いと思う。

gf000 2025-04-02T11:20:27

SGCLってあんたのプロジェクト?もしそうなら、managed pointerとかread flagにatomic writeしてる?Redditでいくつかコメント読んだけど、flagはメモリページ単位っぽいね。でも、同期のオーバーヘッドが他の方法より大きいかもしれないし、JavaみたいにGC研究の最先端と比較できないから、本当にメリットがあるかどうかはわかんないね。TLA+で設計をモデル化してみた?

deepsun 2025-04-01T20:33:56

へー、それってGoだけじゃなくて、他のmark-and-sweep GC(JavaとかC#)でも同じなんじゃない?ってことは、短い間しか生きないオブジェクト(loopのiteratorとかwrapperとか)を作るのは全然ありなんだね。

nurettin 2025-04-01T05:00:04

GCのために短い間しか生きないallocationをする価値ってある?allocationが増えすぎて、逆に遅くなるかもしれないじゃん。

もっとコメントを表示(2)
stouset 2025-04-01T06:46:35

最初の例のobject poolsだけど、こんなのありなんだって驚いたよ。しかも警告も出ないし。
sync.Poolってgenerics以前からあるからanyを受け取って返すんだよね。Goって強い型付けが原則だけど、型システムから抜け出すAPIが多くて、メリットがなくなってる気がする。
ちょっと面白いことをしようとすると型システムを切らないといけないなら、意味なくない?
それと、初期値に戻すAPIがないのも気になる。Clearみたいなコールバックがあっても良くない?

ncruces 2025-04-01T09:35:15

これはstatic typingじゃないけど、strong typingだよ。static vs dynamicとstrong vs weakの話。
https://stackoverflow.com/a/11889763

9rx 2025-04-01T12:59:49

strongでstaticでstructuralだね。でもstructural typingはコンパイル時のduck typingみたいなものだから、dynamic typingと混同する人がいるのも理解できる。

masklinn 2025-04-02T07:21:35

Ggpが言ってるのはstructural typingじゃなくて、sync.Poolの型消去のことだよ(anyの値を受け取って返す)。だから、変なgarbageが入る可能性がある。

9rx 2025-04-02T09:12:40

>Ggp is not talking about structural typing、
いや、返信先はtypingがstaticかどうかをquestionしてたんだよ。structural typingによってstaticなんだ。コンパイラがdacksがcompatibleかどうかを強制する。でもempty interfaceにはconstraintsがないから、全てのtypeはcompatibleになる。他のcommentが何を言ってたかはirrelevant。
>but about sync.Pool type erasing
typeはeraseされてないよ…?
p := sync.Pool{New: func() interface{} { return 1 }}
fmt.Printf(”%T”, p.Get()) // Prints: int
>So you can put (and will retrieve) random garbage from it.
それがdynamic typeだってこと?違うでしょ。

zaphodias 2025-04-01T08:02:59

たしかにgenericsは便利かもね。でもsync.Poolとかsync.Mapみたいなprimitiveはuse caseに合わせてwrapするのが簡単だよ。
Goはbreaking changesに厳しいから、今の実装は変わらないと思う。v2が出るかもしれないけど、どうだろうね。codeが多いほどmaintainが大変だし。

aktau 2025-04-01T10:24:46

upstreamもtype-saferなsync.Poolはgood ideaだと思ってるみたい。
https://go.dev/issue/71076
でdiscussionされてるよ。

Someone 2025-04-01T10:10:13

>While I think you’re right (generics might be useful there), it’s fairly easy to wrap the sync primitives such as sync.Pool and sync.Map into your specific use case。”
それってstrong argumentじゃないよね。どんなAPIでも、typeをrestrictするwrapは簡単にできる。Genericsならそのworkをしなくて済むし、書かないcodeにはerrorsがない。

zaphodias 2025-04-01T11:10:32

誤解しないでほしいんだけど、I agreeだよ!performance的にも、want whatever I wantをbuildできるprimitiveが欲しい。generic primitiveはperformanceがちょっと悪くて、自分をshoot in the footしないようにtuneしなきゃいけない。

strangelove026 2025-04-01T10:26:46

Sync.mapってperformanceがbadなはず。
https://github.com/golang/go/issues/21031

PhilippGille 2025-04-01T11:51:12

ケースバイケースだね。
Godocによると、Map型は2つのユースケースに最適化されてるんだ。(1)キーに対する書き込みが1回だけで読み込みが多い場合、例えばキャッシュとか。(2)複数のgoroutineがバラバラのキーに対して読み書きする場合。この2つならMutexよりlockの競合が減るかも。
https://pkg.go.dev/sync#Map
あと、書き込みが遅い問題はGo 1.24で改善されたみたい。
https://go.dev/doc/go1.24#minor_library_changes

jlouis 2025-04-01T13:39:56

型システムには、型ルールを破れる抜け道があるのが普通だよね。例えば、OCamlのObjモジュールにある”magic”関数とか。
これは、型システムの制限を回避するための手段なんだ。
wrapperを作れば、コードの安全性を保てるしね。
関数型インターフェースで命令型の実装を包むのも同じ考え方だ。表面的には関数型だけど、内部では命令型コードを使ってる場合もある(効率のため)。

stouset 2025-04-01T15:45:48

抜け道がない型システムなんてないけど、Goみたいに頻繁に型システムを破る必要がある言語は見たことないな。
簡単なことならGoの型システムは役立つけど、複雑なことをしようとすると、すぐ投げ出されるんだよ。そんな時にこそ型システムに頼りたいのに。
データベース接続のプールが突然文字列を返すなんて心配したくない。

int_19h 2025-04-01T22:39:00

ジェネリクス導入前のJavaやC#と似たようなもんだよ。理由はほぼ同じ。

jfwwwfasdfs 2025-04-01T08:13:45

多くの言語にトップ型って概念があるよね。

tgv 2025-04-01T07:32:58

Goでプログラミングしたことないでしょ?pool.Get()の型はanyで、Goのワイルドカード型なんだよ。値を取り出すには、正しい型をアサートする必要がある。これはジェネリクスでは解決できない。JavaやRust、C++でも無理だよ。Goが後方互換性を持ってるから、この構造は残るんだ。
>初期化されたデフォルト値にリセットするAPIがないって?
New関数がそうじゃないの?
あと、コードにコンマがないよ。

gwd 2025-04-01T07:56:45

>New関数がそうじゃないの?
New関数はプールがスペースを確保する必要がある時にしか呼ばれないじゃん。sync.Pool()が常にゼロ構造体を返すと思ってるみたいだけど、Golangのallocationと同じようにね。
sync.Pool()はパフォーマンスが重要な場合に使う最適化だから、必要な部分だけ初期化するのは理にかなってる。でも、そう思うのも無理はないかも。
>[any]は型
Pythonみたいな型付けで、RustやCとは違う。コンパイルできれば正しいってわけじゃないんだ。
sync.Poolは使わないけど、ジェネリクスがあるなら型付きのプールの方が良いと思う。

9rx 2025-04-01T08:05:17

>コンパイルできれば正しいってわけじゃないんだ。
みんながそう思ってるなら、Coqとかがもっと有名になってるはずだよ。普通に使われてる言語はテストが必要で、その過程で型も検証される。”機械がコードをリファクタリングしてくれる”ってのが静的型付き言語の魅力で、”ネットで記事を書ける”ってのが二番目の魅力。

gwd 2025-04-01T08:30:13

レベルによってコストとメリットがあるよね。窓に鉄格子はないけど、玄関には鍵をかける。Golangはコンパイルで多くのエラーを検出できるし、テストで残りを検出できる。PythonやPerlみたいにテストだけが頼りって状態よりマシ。

9rx 2025-04-01T08:35:01

>コンパイルで多くのエラーを検出できる
リファクタリングにはすごく便利だよね。でも、プログラムの正確さについては、あまり意味がない。

もっとコメントを表示(3)
ignoramous 2025-04-01T09:06:48

GPが期待してるのは、Golangのallocationみたいにsync.Pool()が常にゼロ構造体を返すことだと思うんだよね。Pool[T]型を新しく定義して、この保証を得ることもできるよ。intpoolとかboolpoolの例もあるよ。
https://go.dev/play/p/-WG7E-CVXHR

9rx 2025-04-01T13:37:27

>One could define a new “Pool[T]” type (extending sync.Pool) to get these guarantees:”
それって、自分がそうじゃない限り?Foo構造体の例で、期待を完全に忘れちゃってるよ。pool.Get()で10を設定してpool.Put(a)したあと、もう一度pool.Get()すると10が表示されちゃう。0が期待されてるのに。

ignoramous 2025-04-01T14:58:30

>You completely forgot to address the expectation
>fmt.Println(b.V) // Prints: 10; 0 was expected.
ポインタをpoolingするときに何を期待してるのか、正直よくわかんないんだよね。[]uint8とか[]byteをpoolingするのはよくあることだし、Pool.Put()とかPool.Get()で中身をゼロにする必要があるのかな?

9rx 2025-04-01T15:32:47

>I don’t get what else one expects when pooling pointers to a type?
前のコメントにもあるように、期待されてるのは常にゼロ値が返ってくることだよ。“GPが期待してるのはsync.Pool()が常にゼロ構造体を返すこと”なんだから。それを保証するって言ったんだよね?Pool.Put()とかPool.Get()で中身をゼロにしないと。それなのに、そうしてないじゃん。sync.Poolと全く同じ動きしかしないし…ジェネリクスの制約も間違ってるし。

ignoramous 2025-04-01T16:12:08

>And、unfortunately、doesn’t even get the generic constraints right、as demonstrated with the int and bool examples.
もし制約が正しくないなら、runtime panicになるんじゃないの?
>What GP seems to expect is that sync.Pool() would always return a zeroed structure
アドレスをPoolするときは気をつけないとね。
>But you completely forgot to do it、which questions what your code was for?
ポインタにゼロ値を期待するなら、New funcはnilを返すか、Pool.Get/Putでゼロにする必要があるね。レビューありがと。

9rx 2025-04-01T16:18:27

>If those constraints don’t hold (like you say) it should manifest as runtime panic、no?
intとかboolのpoolは問題なく動くよ。panicしたらコードを投稿しないよね。でも、正しくないんだよ。
>I did not forget?
じゃあ、保証はどうなってるの?“Pool[T]型を新しく定義して、この保証を得ることもできる”って言ったよね?なんで守れない約束をするの?

ignoramous 2025-04-01T16:24:13

それは設計図だよ。Embeddingとかtypedefは、その保証を実現する方法。ジェネリックなpoolライブラリを書くのが目的じゃないんだ。
>but are not correct.
意味がわからない。制約が正しくないって言ったよね?どういう意味?

9rx 2025-04-01T16:40:05

>What does it even mean?
Goでは値がコピーされるんだよ。コードは動くけど、期待通りには動かない。poolのユーザーがミスしないように任せっきりだよね。それはある程度は良いけど、sync.Poolだけでも同じことができるから、あなたのコードは何のためにあるの?

ignoramous 2025-04-01T16:45:55

>Values are copied in Go
なるほど、ありがとう。
>so what is your code for?
それが修辞的な質問じゃないなら、sync.Poolをtypedefとかembedで拡張できることを示すためだよ。pooling自体が正しいかどうかは焦点じゃないんだ。

9rx 2025-04-01T16:49:18

>then the code was to demonstrate that sync.Pool could be “extended” with other types and custom logic.
その保証はどこにあるの?コードはゼロ値も保証しないし、型安全ですらない。そもそも、Goに詳しい人が、そんな標準的な機能を unawareなわけないじゃん。

ignoramous 2025-04-01T17:01:25

その保証はどこにあるのさ?あんたの書いたことを読み直した方がいいんじゃない?俺がsync.Poolでみんなの問題を解決しなかったって怒ってるみたいだけど、そんなこと言ってないし。Pool[T]型を新しく定義(sync.Poolを拡張)すれば、その保証が得られるかもね。まあ、sync.Poolを拡張してカスタムタイプ向けに保証を得るってこと。intとかboolの例もあるけど、コピーされるからpoolingは効果ないって言ってる通り。でも、sync.Poolの拡張方法を示したかっただけで、他意はないんだ。

9rx 2025-04-01T17:25:16

>あんたの書いたことを読み直した方がいいんじゃない?
全角の>元の文からコロンを”忘れ”てコピーしてるじゃん。意味を考えると、わざと省いたのかな?俺が元のコメントを読んでないと思ってた?
全角の>怒ってるみたい
全角の>ネットで怒るなんてありえないでしょ。もし怒りそうになっても、その前にパソコン消すって。楽しくないなら使う意味ないし。
全角の>One could define / extend sync.Pool to get those guarantees [for their custom types] …
全角の>誰が興味あるの?sync.Poolの複雑さを理解してて、型定義や関数も書けない人なんていないでしょ。

tgv 2025-04-01T08:40:58

GPが期待してるのは、sync.Pool()が常にゼロ構造体を返すってことかもね。でもそれって型とかジェネリクスとは関係ない設計判断だよね?プールのドレイン関数が必要ないって言ってるみたいだけど、それって珍しいよね。
>It’s typed the way Python is typed
全然違う。
>”if it compiles there’s a good chance it’s correct”
Rustのunwrap()みたいかも。間違った結果に適用するとpanicするやつ。

gwd 2025-04-01T08:53:21

>Not in the slightest.
それってUSENETでよくある、議論を呼ばないコメントだよね。Goは好きでよく使うし、any型にフォールバックできるのも良いと思う。でもany型を使うってことは、コンパイル時にチェックできない性質があるってこと。Pythonも同じ。
>If you want to compare it to something, it’s more like Rust’s unwrap(), which will panic if you apply it to the wrong result.
Rustのunwrap()は、型が2つしかない時に使うんだよね(だから型指定がない)。any型は、型が何でもありってこと。例にあるように、int、string、空のstructをプールに入れられる。多分それは求めてることじゃないけど、コンパイラは止めないよ。

9rx 2025-04-01T17:05:14

>But it’s simply a fact that using the any type means that certain properties of the program can’t be checked at compile time
構造的型付けは、コンパイル時にチェックできる性質を減らすよね。でも、だからってPythonみたいになるわけじゃない。

9rx 2025-04-02T02:52:51

anyは特別な型じゃないよ。interface{}のエイリアスだよ。空集合はすべての型で満たされるけど、必要に応じて絞り込める。

tgv 2025-04-01T10:35:20

Pythonの型がないのと、anyから値を取り出すのを比べるのはおかしいでしょ。
>certain properties of the program can’t be checked at compile time
数字がプラスかマイナスか、stringが空かどうかはコンパイル時にチェックできないけど、だからってGoがCOBOLやForthみたいになるわけじゃない。var v anyは、vがany型だって宣言してるんであって、任意の型ってわけじゃない。Pythonとは違う。v + 1はコンパイルエラーになるけど、Pythonはランタイムエラーになるかも。特にインターフェースを考えると、全然違う。Pythonでintegerを宣言しても保証はないけど、Goでは保証がある。例えばjsonを扱う時に重要になってくる。
>the compiler won’t prevent you from doing it.
stringの配列をintの配列として使うことはできないよ。Pythonはできる。全然違う。

sophacles 2025-04-01T20:01:35

Pythonには"total absense of typing"はないよ。静的型付けがないから、コンパイル時のチェックができないだけ(最近は擬似的な静的型付けもあるけど)。+を呼べるオブジェクトと呼べないオブジェクトがあるのは、型が違うからだよ。
本当に型のない言語(またはすべてが単一の型)はASM。レジスタのビットフィールドにどんな操作もできる。文字配列からロードされた64ビットがstringだって?OK、他のレジスタとbitwise andできる。u64?ポインタ?u32のペア?同じこと。意味は変わらない。

pyrale 2025-04-01T13:39:44

>There’s no way in Java, Rust or C++ to express this either
それを表現できるのが良いことみたいに言うなよ。Java、Rust、C++でそれが表現できないのは、言語設計者のおかげだ。
any型とかキャストを使わずに、複数の型を持つpoolの値を表現したいなら、Rustのunion型とか、Javaのinterfaceを実装した複数の具象オブジェクトを使えばいい。どちらも、チェックされないduck typingなしに、明示的に値を確認する必要がある。

kevmo314 2025-03-31T23:35:42

ゼロコピーってマジで過小評価されてるよねー。このサイトでも言ってるように、Go のインターフェースのおかげでゼロコピーのコードが比較的書きやすいんだけど、それでも工夫は必要だよね。でも、それに見合うだけの価値はあると思う。メモリの割り当てとか、データのやり取りにめっちゃ時間使ってることに気づいてびっくりすることがよくあるんだよねー。

記事一覧へ

海外テックの反応まとめ
著者
海外テックの反応まとめ
暇つぶしがてらに読むだけで海外のテックニュースに詳しくなれるまとめサイトです。