[HN Gopher] Go Optimization Guide
       ___________________________________________________________________
        
       Go Optimization Guide
        
       Author : jedeusus
       Score  : 442 points
       Date   : 2025-03-31 20:29 UTC (1 days ago)
        
 (HTM) web link (goperf.dev)
 (TXT) w3m dump (goperf.dev)
        
       | parhamn wrote:
       | Noticed the object pooling doc, had me wondering: are there any
       | plans to make packages like `sync` generic?
        
         | arccy wrote:
         | eventually: https://github.com/golang/go/issues/71076
        
       | roundup wrote:
       | Additionally...
       | 
       | - https://go101.org/optimizations/101.html
       | 
       | - https://github.com/uber-go/guide
       | 
       | I wish this content existed as a model context protocol (MCP)
       | tool to connect to my IDE along w/ local LLM.
       | 
       | After 6 months or switching between different language projects,
       | it's challenging to remember all the important things.
        
         | jigneshdarji91 wrote:
         | Additionally... - https://www.uber.com/en-AU/blog/how-we-
         | saved-70k-cores-acros...
         | 
         | This has saved Uber a lot of money on compute (I'm one of the
         | devs). If your compute fleet is large and has memory to spare
         | (stateless), performing dynamic GOGC tuning to tradeoff higher
         | memory utilization for fewer GC events will save quite a lot of
         | compute.
        
         | TechDebtDevin wrote:
         | Embedding those docs in your MCP server takes about 5 seconds
         | with mcp-go's AddResource method
         | 
         | https://github.com/mark3labs/mcp-go/blob/main/examples/every...
        
       | nopurpose wrote:
       | Every perf guide recommends to minimize allocations to reduce GC
       | times, but if you look at pprof of a Go app, GC mark phase is
       | what takes time, not GC sweep. GC mark always starts with known
       | live roots (goroutine stacks, globals, etc) and traverse
       | references from there colouring every pointer. To minimize GC
       | time it is best to avoid _long living_ allocations. Short lived
       | allocations, those which GC mark phase will never reach, has
       | almost neglible effect on GC times.
       | 
       | Allocations of any kind have an effect on triggering GC earlier,
       | but in real apps it is almost hopeless to avoid GC, except for
       | very carefully written programs with no dependenciesm, and if GC
       | happens, then reducing GC mark times gives bigger bang for the
       | buck.
        
         | MarkMarine wrote:
         | Are you including in this analysis the amount of time/resources
         | it takes to allocate? GC isn't the only thing you want to
         | minimize for when you're making a high performance system.
        
           | nopurpose wrote:
           | From that perspective it boils down to "do less", which is
           | what any perf guide already includes, allocations is just no
           | different from anything else what app do.
           | 
           | My comment is more about "reduce allocations to reduce GC
           | pressure" advice seen everywhere. It doesn't tell the whole
           | story. Short lived allocation doesn't introduce any GC
           | pressure: you'll be hard pressed to see GC sweep phase on
           | pprof without zooming. People take this advice, spend time
           | and energy hunting down allocations, just to see that total
           | GC time remained the same after all that effort, because they
           | were focusing on wrong type of allocations.
        
             | MarkMarine wrote:
             | Yeah I understand what you're saying, but my point is
             | you're doing the opposite side of the same coin. Not doing
             | full perf analysis and saying this one method works (yours
             | is to reduce GC mark time, ignoring allocation, others are
             | trying to reduce allocation time, ignoring GC time, or all
             | these other methods listed in this doc.)
        
         | Capricorn2481 wrote:
         | Aren't allocations themselves pretty expensive regardless of
         | GC?
        
           | nu11ptr wrote:
           | Go allocations aren't that bad. A few years ago I benchmarked
           | them at about 4x as expensive as a bump allocation. That is
           | slow enough to make an arena beneficial in high allocation
           | situations, but fast enough to not make it worth it most of
           | the time.
        
             | aktau wrote:
             | Comparing with a fairly optimized malloc at $COMPANY, the
             | Go allocator is (both in terms of relative cycles and
             | fraction of cycles of all Go programs) significantly more
             | expensive than the C/C++ counterpart (3-4x IIRC). For one,
             | it has to do more work, like setting up GC metadata, and
             | zeroing.
             | 
             | There have recently been some optimizations to
             | `runtime.mallocgc`, which may have decrease that 3-4x
             | estimate a bit.
        
               | nu11ptr wrote:
               | How can that be true? If it is 3-4x more expensive than
               | malloc, then per my measurements your malloc is a bump
               | allocator, and that simply isn't true for any real world
               | malloc implementation (typically a modified free list
               | allocator afaik). `mallocgc` may not be fast, but I
               | simply did not find it as slow as you are saying. My
               | guess is it is about as fast as most decent malloc
               | functions, but I have not measured, and it would be
               | interesting to see a comparison (tough to do as you'd
               | need to call malloc via CGo or write one in C and one in
               | Go and trust the looping is roughly the same cost).
        
           | epcoa wrote:
           | No. If you have a moving multi generational GC, allocation is
           | literally just an increment for short lived objects.
        
             | burch45 wrote:
             | This is about go not Java. Go makes different tradeoffs and
             | does not have moving multigenerational GC.
        
             | pebal wrote:
             | If you have a moving, generational GC, then all the
             | benefits of fast allocation are lost due to data moving and
             | costly memory barriers.
        
               | gf000 wrote:
               | Not at all. Most objects die young and thus are never
               | moved. Also, the time before it is moved is very long
               | compared to CPU operations so it is only statistically
               | relevant (very good throughput, rare, longer tail on
               | latency graphs).
               | 
               | Also, write-only barriers don't have that big of an
               | overhead.
        
               | pebal wrote:
               | It doesn't matter if objects die young -- the other
               | objects on the heap are still moved around periodically,
               | which reduces performance. When you're using a moving GC,
               | you also have additional read barriers that non-moving
               | GCs don't require.
        
               | pgwhalen wrote:
               | It's in uncharitable to say the benefits are lost - I'd
               | reframe it as creating tradeoffs.
        
         | raggi wrote:
         | You might wanna look at a system profiler too, pprof doesn't
         | show everything.
        
         | zmj wrote:
         | Pretty similar story in .NET. Make sure your inner loops are
         | allocation-free, then ensure allocations are short-lived, then
         | clean up the long tail of large allocations.
        
           | neonsunset wrote:
           | .NET is far more tolerant to high allocation traffic since
           | its GC is generational and overall more sophisticated (even
           | if at the cost of tail latency, although that is workload-
           | dependent).
           | 
           | Doing huge allocations which go to LOH is quite punishing,
           | but even substantial inter-generational traffic won't kill
           | it.
        
         | zbobet2012 wrote:
         | E.h., kind of. If you are allocating in a hot loop it's going
         | to suck regardless. Object pools are really key if you want
         | high perf because the general purpose allocator is way less
         | efficient in comparison.
        
         | liquidgecka wrote:
         | Its worth calling out that abstractions can kill you in
         | unexpected ways with go.
         | 
         | Anytime you use an interface it forces a heap allocation, even
         | if the object is only used read only and within the same scope.
         | That includes calls to things like fmt.Printf() so doing a for
         | loop that prints the value of i forces the integer backing i to
         | be heap allocated, along with every other value that you
         | printed. So if you helpfully make every api in your library use
         | an interface you are forcing the callers to use heap
         | allocations for every single operation.
        
           | slashdev wrote:
           | I thought surely an integer could be inlined into the
           | interface, I thought Go used to do that. But I tried it on
           | the playground, and it heap allocates it:
           | 
           | https://go.dev/play/p/zHfnQfJ9OGc
        
             | masklinn wrote:
             | Go did use to do that, it was removed years ago, in 1.4:
             | https://go.dev/doc/go1.4#runtime
        
               | kbolino wrote:
               | Basically, anything that isn't a thin pointer (*T, chan,
               | map) gets boxed nowadays. The end result is that both
               | words of an interface value are always pointers [1],
               | which is very friendly to the garbage collector (setting
               | aside the extra allocations when escape analysis fails).
               | I've seen some tricks in the standard library to avoid
               | boxing, e.g. how strings and times are handled by
               | log/slog [2].
               | 
               | [1]: https://github.com/teh-cmc/go-
               | internals/blob/master/chapter2...
               | 
               | [2]: https://cs.opensource.google/go/go/+/refs/tags/go1.2
               | 4.1:src/...
        
               | ncruces wrote:
               | slog.Value looks incredibly useful.
               | 
               | Just imagine a day where database/sql doesn't generate a
               | tonne of garbage because it moves to use something like
               | that?
        
               | ominous_prime wrote:
               | go1.15 re-added small integer packing into interfaces:
               | https://go.dev/doc/go1.15#runtime
        
               | masklinn wrote:
               | It didn't, actually. Instead go 1.15 has a static array
               | of the first 256 positive integers, and when it needs to
               | box one for an interface it gets a pointer into that
               | array instead: https://go-
               | review.googlesource.com/c/go/+/216401/4/src/runti...
               | 
               | This array is also used for single-byte strings (which
               | previously had its own array): https://go-
               | review.googlesource.com/c/go/+/221979/3/src/runti...
        
               | ominous_prime wrote:
               | It didn't, do what? I would consider the first 256
               | integers to be "small integers" ;)
               | 
               | > Converting a small integer value into an interface
               | value no longer causes allocation
               | 
               | I forgot that it can also be used for single byte
               | strings, That's not an optimization I ever encountered
               | being useful, but it's there!
        
               | masklinn wrote:
               | > It didn't, do what?
               | 
               | Reintroduce "packing into interfaces".
               | 
               | It did a completely different thing. Small integers
               | remain not inlined.
        
         | nurettin wrote:
         | Is it worth making short lived allocations just to please the
         | GC? You might just end up with too many allocations which will
         | slow things down even more.
        
           | aktau wrote:
           | It is not. Please see my answer
           | (https://news.ycombinator.com/item?id=43545500).
        
         | ncruces wrote:
         | The point is not to avoid GC entirely, but to _reduce_
         | allocation pressure.
         | 
         | If you can avoid allocs in a hot loop, it _definitely_ pays to
         | do so. If you can 't for some reason, and can use sync.Pool
         | there, measure it.
         | 
         | Cutting allocs in half may not matter much, but if you can cut
         | them by 99% because you were allocating in every iteration of a
         | 1 million loop, and now aren't, it will make a difference, even
         | if all those allocs die instantly.
         | 
         | I've gotten better than two fold performance increases on real
         | code with both techniques.
        
         | bboreham wrote:
         | Agree that mark phase is the expensive bit. Disagree that it's
         | not worth reducing short-lived allocations. I spend a lot of
         | time analyzing Go program performance, and reducing bytes
         | allocated per second is always beneficial.
        
           | felixge wrote:
           | +1. In particular []byte slice allocations are often a
           | significant driver of GC pace while also being relatively
           | easy to optimize (e.g. via sync.Pool reuse).
        
         | aktau wrote:
         | Side note: see https://tip.golang.org/doc/gc-guide for more on
         | how the Go GC works and what triggers it.
         | 
         | GC frequency is _directly driven_ by allocation rate (in terms
         | of bytes) and live heap size. Some examples:                 -
         | If you halve the allocation rate, you halve the GC frequency.
         | - If you double the live heap size, you halve the GC frequency
         | (barring changes away from the default `GOGC=100`).
         | 
         | > ...but if you look at pprof of a Go app, GC mark phase is
         | what takes time, not GC sweep.
         | 
         | It is true that sweeping is a lot cheaper than marking, which
         | makes your next statement:
         | 
         | > Short lived allocations, those which GC mark phase will never
         | reach, has almost neglible effect on GC times.
         | 
         | ...technically correct. Usually, this is the best kind of
         | correct, but it omits two important considerations:
         | - If you generate a ton of short-lived allocations instead of
         | keeping them around, the GC will trigger more frequently.
         | - If you reduce the live heap size (by not keeping anything
         | around), the GC will trigger more frequently.
         | 
         | So now you have cheaper GC cycles, but many more of them. On
         | top of that, you have vastly increased allocation costs.
         | 
         | It is not a priori clear to me this is a win. In my experience,
         | it isn't.
        
           | nopurpose wrote:
           | I enjoyed your detailed response, it adds value to this
           | discussion, but I feel you missed the point of my comment.
           | 
           | I am against blanket statements "reduce allocations to reduce
           | GC pressure", which lead people wrong way: they compare
           | libraries based on "allocs/op" from go bench, they trust
           | rediculous (who allocates 8KB per iteration in tight loop??)
           | microbenchmarks of sync.Pool like in the article above,
           | hoping to resolve their GC problem. Spend considerabe amount
           | of effort just to find that they barely moved a needle on GC
           | times.
           | 
           | If we generalize then my "avoid long-lived allocations" or
           | yours "reduce allocation rate in terms of bytes" are much
           | more useful in practice, than what this and many other
           | articles preach.
        
           | deepsun wrote:
           | Interesting, thank you. But I think those points are not
           | correlated that much. For example if I create unnecessary
           | wrappers in a loop, I might double the allocation rate, but I
           | will not halve the live heap size, because I did not have
           | those wrappers outside the loop before.
           | 
           | Basically, I'm trying to come up with an real world example
           | of a style change (like create wrappers for every error, or
           | use naked integers instead of time.Time) to estimate its
           | impact. And my feeling is that any such example would affect
           | one of your points way more than the other, so we can still
           | argument that e.g. "creating short-lived iterators is totally
           | fine".
        
         | deepsun wrote:
         | Interesting, and I think that is not specific to Go, other
         | mark-and-sweep GCs (Java, C#) should behave the same.
         | 
         | Which means that creating short lived objects (like iterators
         | for loops, or some wrappers) is ok.
        
           | int_19h wrote:
           | It should be noted that in C#, at least, the standard pattern
           | is to use value types for enumerators, precisely so as to
           | avoid heap allocations. This is the case for all (non-
           | obsolete) collections in the .NET stdlib - e.g.
           | List<T>.Enumerator is a struct.
        
         | kgeist wrote:
         | The runtime also forces GC every 2 minutes. So yeah, a lot of
         | long living allocations can stress the GC, even if you don't
         | allocate often. That's why Discord moved from Go to Rust for
         | their Read States server.
        
       | ljm wrote:
       | You're not really writing 'Go' anymore when you're optimising it,
       | it's defeating the point of the language as a simple but powerful
       | interface over networked services.
        
         | jrockway wrote:
         | Why? You have control over the parts where control yields
         | noticeable savings, and the rest just kind of works with
         | reasonable defaults.
         | 
         | Taken to the extreme, Go is still nice even with constraints.
         | For example, tinygo is pretty nice for microcontroller
         | projects. You can say upfront that you don't want GC, and just
         | allocate everything at the start of the program (kind of like
         | how DJB writes C programs) and writing the rest of the program
         | is still a pleasant experience.
        
           | ashf023 wrote:
           | 100%. I work in Go and use optimizations like the ones in the
           | article, but only in a small percentage of the code. Go has a
           | nice balance where it's not pessimized by default, and you
           | can just write 99% of code without thinking about these
           | optimizations. But having this control in performance
           | critical parts is huge. Some of this stuff is 10x, not +5%.
           | Also, Go has very good built-in support for CPU and memory
           | profiling which pairs perfectly with this.
        
         | emmelaich wrote:
         | I think you have a point that there's generic advice for
         | optimising: don't.
         | 
         | i.e. Make it simple, then measure, then make it fast if
         | necessary.
         | 
         | Perhaps all this is understood for readers of the article.
        
         | mariusor wrote:
         | I think at least some of the patterns shared in the document,
         | using zero-copy, ordering struct properties are all very
         | idiomatic. Writing code in this manner is writing good Go code.
        
         | Cthulhu_ wrote:
         | What do you mean? If you don't want that level of control over
         | e.g. memory allocation, registries, cache lines etc, there's
         | higher level languages than Go you can pick from, e.g. Java /
         | C# / JS.
        
       | jensneuse wrote:
       | You can often fool yourself by using sync.Pool. pprof looks great
       | because no allocs in benchmarks but memory usage goes through the
       | roof. It's important to measure real world benefits, if any, and
       | not just synthetic benchmarks.
        
         | makeworld wrote:
         | Why would Pool increase memory usage?
        
           | xyproto wrote:
           | I guess if you allocate more than you need upfront that it
           | could increase memory usage.
        
             | throwaway127482 wrote:
             | I don't get it. The pool uses weak pointers under the hood
             | right? If you allocate too much up front, the stuff you
             | don't need will get garbage collected. It's no worse than
             | doing the same without a pool, right?
        
               | cplli wrote:
               | What the top commenter probably failed to mention, and
               | jensneuse tried to explain is that sync.Pool makes an
               | assumption that the size cost of pooled items are
               | similar. If you are pooling buffers (eg: []byte) or any
               | other type with backing memory which during use can/will
               | grow beyond their initial capacity, can lead to a
               | scenario where backing arrays which have grown to MB
               | capacities are returned by the pool to be used for a few
               | KB, and the KB buffers are returned to high memory jobs
               | which in turn grow the backing arrays to MB and return to
               | the pool.
               | 
               | If that's the case, it's usually better to have non-
               | global pools, pool ranges, drop things after a certain
               | capacity, etc.:
               | 
               | https://github.com/golang/go/issues/23199 https://github.
               | com/golang/go/blob/7e394a2/src/net/http/h2_bu...
        
           | jensneuse wrote:
           | Let's say you have constantly 1k requests per second and for
           | each request, you need one buffer, each 1 MiB. That means you
           | have 1 GiB in the pool. Without a pool, there's a high
           | likelihood that you're using less. Why? Because in reality,
           | most requests need a 1 MiB buffer but SOME require a 5 MiB
           | buffer. As such, your pool grows over time as you don't have
           | control over the distribution of the size of the pool items.
           | 
           | So, if you have predictable object sizes, the pool will stay
           | flat. If the workloads are random, you have a new problem
           | because, like in this scenario, your pool grows 5x more.
           | 
           | You can solve this problem. E.g. you can only give back items
           | into the pool that are small enough. Alternatively, you could
           | have a small pool and a big pool, but now you're playing cat
           | and mouse.
           | 
           | In such a scenario, it could also work to simply allocate and
           | use GC to clean up. Then you don't have to worry about memory
           | and the lifetime of objects, which makes your code much
           | simpler to read and reason about.
        
             | jerf wrote:
             | Long before sync.Pool was a thing, I wrote a pool for
             | []bytes: https://github.com/thejerf/gomempool I haven't
             | taken it down because it isn't obsoleted by sync.Pool
             | because the pool is aware of the size of the []bytes.
             | Though it may be somewhat obsoleted by the fact the GC has
             | gotten a lot better since I wrote it, somewhere in the 1.3
             | time frame. But it solve exactly that problem I had;
             | relatively infrequent messages from the computer's point of
             | view (e.g., a system that is probably getting messages
             | every 50ms or so), but that had to be pulled into buffers
             | completely to process, and had highly irregular sizes. The
             | GC was doing a ton of work when I was allocating them all
             | the time but it was easy to reuse buffers in my situation.
        
             | theThree wrote:
             | >That means you have 1 GiB in the pool.
             | 
             | This only happen when every request last 1 second.
        
         | nopurpose wrote:
         | also no one GCs sync.Pool. After a spike in utilization, live
         | with increased memory usage until program restart.
        
           | ncruces wrote:
           | That's just not true. Pool contents are GCed after two cycles
           | if unused.
        
             | nopurpose wrote:
             | What do you mean? Pool content can't be GCed , because
             | there are references to it: pool itself.
             | 
             | What people do is what this article suggested,
             | pool.Get/pool.Put, which makes it only grow in size even if
             | load profile changes. App literally accumulated now
             | unwanted garbage in pool and no app I have seen made and
             | attempt to GC it.
        
               | ashf023 wrote:
               | sync.Pool uses weak references for this purpose. The pool
               | does delay GC, and if your pooled objects have pointers,
               | those are real and can be a problem. If your app never
               | decreases the pool size, you've probably reached a stable
               | equilibrium with usage, or your usage fits a pattern that
               | GC has trouble with. If Go truly cannot GC your pooled
               | objects, you probably have a memory leak. E.g. if you
               | have Nodes in a graph with pointers to each other in the
               | pool, and some root pointer to anything in the pool,
               | that's a memory leak
        
               | ahmedtd wrote:
               | From the sync.Pool documentation:
               | 
               | > If the Pool holds the only reference when this happens,
               | the item might be deallocated.
               | 
               | Conceptually, the pool is holding a weak pointer to the
               | items inside it. The GC is free to clean them up if it
               | wants to, when it gets triggered.
        
       | kevmo314 wrote:
       | Zero-copy is totally underrated. Like the site alludes to, Go's
       | interfaces make it reasonably accessible to write zero-copy code
       | but it still needs some careful crafting. The payoff is great
       | though, I've often been surprised by how much time is spent
       | allocating and shuffling memory around.
        
         | jasonthorsness wrote:
         | I once built a proxy that translated protocol A to protocol B
         | in Go. In many cases, protocol A and B were just wrappers
         | around long UTF-8 or raw bytes content. For large messages,
         | reading the content into a slice then writing that same slice
         | into the outgoing socket (preceded and followed by slices
         | containing the translated bits from A to B) made a significant
         | improvement in performance vs. copying everything over into a
         | new buffer.
         | 
         | Go's network interfaces and slices makes this kind of thing
         | particularly simple - I had to do the same thing in Java and it
         | was a lot more awkward.
        
       | jrockway wrote:
       | GOMEMLIMIT has saved me a number of times. In containerized
       | production, it's nice, because sometimes jobs are ephemeral and
       | don't even do enough allocations to hit the memory limit, so you
       | don't spend any time in GC. But it's saved me the most times in
       | CI where golangci-lint or govulncheck can't complete without
       | running out of memory on a kind-of-large CI machine. Set
       | GOMEMLIMIT and it eventually completes. (I switched to nogo,
       | though, so at least golangci-lint isn't a problem anymore.)
        
       | nikolayasdf123 wrote:
       | nice article. good to see statements backed up by Benchmarks
       | right there
        
       | nikolayasdf123 wrote:
       | nicely organised. I feel like this could grow into community
       | driven current state-of-the-art of optimisation tips for Go. just
       | need to allow people edit/comment their input easily (preferably
       | in-place). I see there is github repo, but my bet people would
       | not actively add their input/suggestions/research there, it is
       | hidden too far from the content/website itself
        
         | whalesalad wrote:
         | For sure. Feels like the broader dev community could use a
         | generic wiki platform like this, where every language or
         | toolkit can have its own section. Not just for
         | performance/optimization, but also for idiomatic ways to use a
         | language in practice.
        
       | EdwardDiego wrote:
       | Huh, this surprises me about Golang, didn't realise it was so
       | similar to C with struct alignment. https://goperf.dev/01-common-
       | patterns/fields-alignment/#why-...
        
         | Cthulhu_ wrote:
         | Yup, it's a fairly low-level language intended as a replacement
         | to C/C++ but for modern day systems (networked, concurrent,
         | etc). You don't have manual memory management per se but you
         | still need to decide on heap vs stack and consider the
         | hardware.
        
           | jerf wrote:
           | "you still need to decide on heap vs stack"
           | 
           | No, you can't decide on heap vs stack. Go's compiler decides
           | that. You can get feedback about the decision if you pass the
           | right debug flags, and then based on that you may be able to
           | tickle the optimizer into changing its mind based on code
           | changes you make, but it'll always be an optimization
           | decision subject to change without notice in any future
           | versions of Go, just like any other language where you
           | program to the optimizer.
           | 
           | If you _need_ that level of control, Go is generally not the
           | right language. However, I would encourage developers to be
           | sure they _need_ that level of control before taking it, and
           | that 's not special pleading for Go but special pleading for
           | the entire class of "languages that are pretty fast but don't
           | offer quite that level of control". There's still a lot of
           | programmers running around with very 200x ideas of
           | performance, even programmers who weren't programmers at the
           | time, who must have picked it up by osmosis.
           | 
           | (My favorite example to show 200x perf ideas is paginated
           | APIs where the "pages" are generally chosen from the set {25,
           | 50, 100} for "performance reasons". In 2025, those are
           | terribly, terribly small numbers. Presenting that many
           | results to _humans_ makes sense, but my default size for
           | paginating _API_ calls nowadays is closer to 1000, and that
           | 's the _bottom_ end, for relatively expensive things. If I
           | have no reason to think it 's expensive, tack another order
           | of magnitude on to my minimum.)
        
       | _345 wrote:
       | Anyone know of a resource like this but for Python 3?
        
         | asicsp wrote:
         | This might help: https://pythonspeed.com/datascience/
        
       | neillyons wrote:
       | Curious to know what people are building where you need to
       | optimise like this? eg Struct Field Alignment
       | https://goperf.dev/01-common-patterns/fields-alignment/#avoi...
        
         | dundarious wrote:
         | False sharing is an absolutely classic Concurrency 101 lesson,
         | nothing remarkable about it.
        
         | kubb wrote:
         | Something that shouldn't be written in a GC language.
        
           | piokoch wrote:
           | I don't think GC has anything to do here, doing manual memory
           | allocation we might hit the same problem.
        
           | Cthulhu_ wrote:
           | GC is not relevant in this case, it's about whether you can
           | make structs fit in cache lines and CPU registers. Mechanical
           | sympathy is the googleable phrase. GC is a few layers further
           | away.
        
       | stouset wrote:
       | Checking out the first example--object pools--I was initially
       | blown away that this is not only possible but it produces no
       | warnings of any kind:                   pool := sync.Pool{
       | New: func() any { return 42 }         }              a :=
       | pool.Get()              pool.Put("hello")
       | pool.Put(struct{}{})              b := pool.Get()         c :=
       | pool.Get()         d := pool.Get()              fmt.Println(a, b,
       | c, d)
       | 
       | Of course, the answer is that this API existed before generics so
       | it just takes and returns `any` (nee `interface{}`). It just
       | feels as though golang might be strongly typed in principle, but
       | in practice there are APIs left and rigth that escape out of the
       | type system and lose all of the actual benefits of having it in
       | the first place.
       | 
       | Is a type system all that helpful if you have to keep turning it
       | off any time you want to do something even slightly interesting?
       | 
       | Also I can't help but notice that there's no API to reset values
       | to some initialized default. Shouldn't there be some sort of
       | (perhaps optional) `Clear` callback that resets values back to a
       | sane default, rather than forcing every caller to remember to do
       | so themselves?
        
         | tgv wrote:
         | You never programmed in Go, I assume? Then you have to
         | understand that the type of `pool.Get()` is `any`, the wildcard
         | type in Go. It _is_ a type, and if you want the underlying
         | value, you have to get it out by asserting the correct type.
         | This cannot be solved with generics. There 's no way in Java,
         | Rust or C++ to express this either, unless it is a pool for a
         | single type, in which case Go generics indeed could handle that
         | as well. But since Go is backwards compatible, this particular
         | construct has to stay.
         | 
         | > Also I can't help but notice that there's no API to reset
         | values to some initialized default.
         | 
         | That's what the New function does, isn't it?
         | 
         | BTW, the code you posted isn't syntactically correct. It needs
         | a comma on the second line.
        
           | zaphodias wrote:
           | I assume they're referring to the fact that a Pool can hold
           | different types instead of being a collection of items of
           | only one homogeneous type.
        
           | gwd wrote:
           | > That's what the New function does, isn't it?
           | 
           | But that's only run when the pool needs to allocate more
           | space. What GP seems to expect is that sync.Pool() would
           | _always_ return a zeroed structure, just as Golang allocation
           | does.
           | 
           | I think Golang's implementation does make sense, as
           | sync.Pool() is clearly an optimization you use when
           | performance is an issue; and in that case you almost
           | certainly want to only initialize parts of the struct that
           | are needed. But I can see why it would be surprising.
           | 
           | > [any] is a type
           | 
           | It's typed the way Python is typed, not the way Rust or C are
           | typed; so loses the "if it compiles there's a good chance
           | it's correct" property that people want from statically typed
           | languages.
           | 
           | I don't use sync.Pool, but it does seem like now that we have
           | generics, having a typed pool would be better.
        
             | 9rx wrote:
             | _> so loses the  "if it compiles there's a good chance it's
             | correct" property that people want from statically typed
             | languages._
             | 
             | If that's what people actually wanted, Coq and friends
             | would be household names, not the obscure oddities that
             | they are. All the languages that people actually use on any
             | kind of regular basis require you to write tests in order
             | to gain that sense of correctness, which also ends up
             | validating type-correctness as a natural byproduct.
             | 
             |  _" A machine can help me refactor my code"_ is the
             | property that most attracts people to the statically typed
             | languages that are normally used. With _" I can write posts
             | about it on the internet"_ being the secondary property of
             | interest.
        
               | gwd wrote:
               | It's a spectrum, with costs and benefits at each level. I
               | lock my front door even though I don't have bars on my
               | windows; I prefer Golang, where doing a basic compile
               | will catch a fair number of errors and testing will catch
               | the rest, to Python or Perl where testing is the only way
               | to catch errors.
        
               | 9rx wrote:
               | _> where doing a basic compile will catch a fair number
               | of errors_
               | 
               | In the case of refactoring this is incredibly useful. It
               | doesn't say much about the correctness of your program,
               | though.
        
             | tgv wrote:
             | > What GP seems to expect is that sync.Pool() would always
             | return a zeroed structure
             | 
             | Might be, but that's a design decision that has nothing to
             | do with type or generics, isn't it? You seem to refer to a
             | function to drain the pool, which is not needed, and
             | frankly, rather unusual.
             | 
             | > It's typed the way Python is typed
             | 
             | Not in the slightest.
             | 
             | > "if it compiles there's a good chance it's correct"
             | 
             | 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.
        
               | gwd wrote:
               | > Not in the slightest.
               | 
               | You know, it's this kind of comment on USENET forums
               | which prompted the creation of StackOverflow. It's not
               | curious and adds nothing to the discussion.
               | 
               | I like Go and use it extensively; and I like having the
               | option to fall back to the `any` type. 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, in the same way that Python isn't able to check
               | certain properties of the program at compile time.
               | 
               | > 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's unwrap() is used when a type can have one of
               | exactly _two_ underlying types (which is why no type is
               | specified). In this case, getting back an `any` type
               | means the underlying type could literally be anything --
               | as demonstrated by the example, where they put an
               | integer, a string, and an empty struct into the pool.
               | That 's almost certainly not what you wanted, but the
               | compiler won't prevent you from doing it.
        
               | tgv wrote:
               | Sorry, but comparing Python's total absence of typing to
               | extracting a value from any is quite weird.
               | 
               | > certain properties of the program can't be checked at
               | compile time
               | 
               | Neither can you check if a number is positive or
               | negative, or if a string is empty or not at compile time,
               | but that doesn't make Go similar to COBOL or Forth. `var
               | v any` declares v to be of the type any, not of any
               | arbitrary type, which is what Python does. Writing `v +
               | 1` gives a compiler error, unlike Python, which may or
               | may not turn it into a runtime error. It is rather
               | different, and especially so when you look at
               | interfacing. Even though you may declare a variable to be
               | an integer in Python, there is no guarantee that it
               | actually is, whereas in Go that is the case, which has
               | significant implications for how you handle e.g. json.
               | 
               | > the compiler won't prevent you from doing it.
               | 
               | It will prevent you from using e.g. an array of strings
               | as if it were an array ints. Python does not. They are
               | quite different.
               | 
               | > You know, it's this kind of comment on USENET forums
               | which prompted the creation of StackOverflow. It's not
               | curious and adds nothing to the discussion.
               | 
               | Ffs.
        
               | sophacles wrote:
               | Python doesn't have "total absense of typing". It doesn't
               | have static typing, so compile time checks are not
               | possible (well historically, there's some psuedo static
               | typing things these days). The fact that you can call `+`
               | on some objects but not others is literally the result of
               | the objects being different types.
               | 
               | A truly typeless language (or maybe more accurately
               | single type for everything language) is ASM, particularly
               | for older CPU designs. You get a word - the bitfield in a
               | register, and can do any operation on it. Is that 64 bits
               | loaded from an array of characters and the programmer
               | intended it to be a string? Cool you can bitwise and it
               | with some other register. Was it a u64, a pointer, a pair
               | of u32s? Same thing - the semantics don't change.
        
               | 9rx wrote:
               | _> 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_
               | 
               | Yes, structural typing removes the ability to check
               | certain properties at compile-time. That doesn't make it
               | typed like Python, though.
        
               | int_19h wrote:
               | "any" is not structural typing.
        
             | ignoramous wrote:
             | > _What GP seems to expect is that sync.Pool() would always
             | return a zeroed structure, just as Golang allocation does._
             | 
             | One could define a new "Pool[T]" type (extending sync.Pool)
             | to get these guarantees:                 type Pool[T any]
             | sync.Pool  // typed def            func (p *Pool[T]) Get()
             | T { // typed Get           pp := (*sync.Pool)(p)
             | return pp.Get().(T)       }            func (p *Pool[T])
             | Put(v T) { // typed Put           pp := (*sync.Pool)(p)
             | pp.Put(v)       }            intpool := Pool[int]{
             | // alias New           New: func() any { var zz int; return
             | zz },       }            boolpool := Pool[bool]{      //
             | alias New           New: func() any { var zz bool; return
             | zz },       }
             | 
             | https://go.dev/play/p/-WG7E-CVXHR
        
               | 9rx wrote:
               | _> One could define a new  "Pool[T]" type (extending
               | sync.Pool) to get these guarantees:_
               | 
               | So long as that one is not you? You completely forgot to
               | address the expectation:                   type Foo
               | struct{ V int }         pool := Pool[*Foo]{ // Your Pool
               | type.             New: func() any { return new(Foo) },
               | }              a := pool.Get()         a.V = 10
               | pool.Put(a)              b := pool.Get()
               | fmt.Println(b.V) // Prints: 10; 0 was expected.
        
               | ignoramous wrote:
               | > _You completely forgot to address the expectation_
               | 
               | > _fmt.Println(b.V) // Prints: 10; 0 was expected._
               | 
               | Sorry, I don't get what _else_ one expects when pooling
               | pointers to a type? In fact, pooling * _[]uint8_ or *
               | _[]byte_ is common place; Pool.Put() or Pool.Get() then
               | must zero its contents.
        
               | 9rx wrote:
               | _> I don 't get what else one expects when pooling
               | pointers to a type?_
               | 
               | As seen in your previous comment, the expectation is that
               | the zero value will always be returned: _" What GP seems
               | to expect is that sync.Pool() would always return a
               | zeroed structure, just as Golang allocation does."_ To
               | which you offered a guarantee.
               | 
               |  _> Pool.Put() or Pool.Get() then must zero its
               | contents._
               | 
               | Right. That is the solution (as was also echoed in the
               | top comment in this thread) if one needs that expectation
               | to hold. But you completely forgot to do it, which
               | questions what your code was for? It behaves exactly the
               | same as sync.Pool itself... And, unfortunately, doesn't
               | even get the generic constraints right, as demonstrated
               | with the int and bool examples.
        
               | ignoramous wrote:
               | > _And, unfortunately, doesn 't even get the generic
               | constraints right, as demonstrated with the int and bool
               | examples._
               | 
               | If those constraints don't hold (like you say) it should
               | manifest as runtime panic, no?
               | 
               | > _What GP seems to expect is that sync.Pool() would
               | always return a zeroed structure_
               | 
               | Ah, well. You gots to be careful when _Pool_ ing
               | addresses.
               | 
               | > _But you completely forgot to do it, which questions
               | what your code was for?_
               | 
               | OK. If anyone expects zero values for pointers, then the
               | _New_ func should return nil (but this is almost always
               | useless), or if one expects values to be zeroed-out, then
               | _Pool.Get /Put_ must zero it out. Thanks for the code
               | review.
        
               | 9rx wrote:
               | _> If those constraints don 't hold (like you say) it
               | should manifest as runtime panic, no?_
               | 
               | No. Your int and bool pools run just fine - I can't
               | imagine you would have posted the code if it panicked -
               | but are not correct.
               | 
               |  _> I did not forget?_
               | 
               | Then your guarantee is bunk: _" One could define a new
               | "Pool[T]" type (extending sync.Pool) to get these
               | guarantees:"_ Why are you making claims you won't stand
               | behind?
        
               | ignoramous wrote:
               | It was a blueprint. Embedding and typedefs are ways to
               | implement these guarantees. And of course, writing a
               | generic pool library is not what I was after.
               | 
               | > _but are not correct._
               | 
               | I don't follow what you're saying. You asserted, "And,
               | unfortunately, doesn't even get the generic constraints
               | right, as demonstrated with the int and bool examples."
               | What does it even mean? I guess, this bikeshed has been
               | so thoroughly built that the discussion points aren't
               | even getting through.
        
               | 9rx wrote:
               | _> What does it even mean?_
               | 
               | Values are copied in Go. Your code will function, but it
               | won't work.
               | 
               | You've left it up to the user of the pool to not screw
               | things up. Which is okay to some degree, but sync.Pool
               | already does that alone, so what is your code for?
        
               | ignoramous wrote:
               | > _Values are copied in Go_
               | 
               | Gotcha. Thanks for clearing it up.
               | 
               | > _so what is your code for?_
               | 
               | If that's not rhetorical, then the code was to
               | demonstrate that _sync.Pool_ could be  "extended" with
               | typedefs/embeds + custom logic. Whether it got _pooling_
               | itself right was not the intended focus (as shown by the
               | fact that it created int  & bool pools).
        
               | 9rx wrote:
               | _> then the code was to demonstrate that sync.Pool could
               | be  "extended" with other types and custom logic._
               | 
               | Wherein lies the aforementioned guarantee? The code
               | guarantees neither the ask (zero values) nor even proper
               | usage if you somehow didn't read what you quoted and
               | thought that some kind of type safety was the guarantee
               | being offered.
               | 
               | Furthermore, who, exactly, do you think would be familiar
               | enough with Go to get all the other things right that you
               | left out but be unaware of that standard, widely used
               | feature?
        
               | ignoramous wrote:
               | > _Wherein lies the aforementioned guarantee?_
               | 
               | I think you should re-read what I wrote. You seem to be
               | upset that I did not solve everyone's problem with
               | _sync.Pool_ with my 10 liner (when I claimed no such
               | thing).                 One could define a new "Pool[T]"
               | type (extending sync.Pool) to get these guarantees
               | 
               | Meant... One could define / extend _sync.Pool_ to get
               | those guarantees [for their custom types] ... Followed by
               | an example for int  & bool types (which are copied
               | around, so pooling is ineffective like you say, but my
               | intention was to show how _sync.Pool_ could be extended,
               | and nothing much else).
        
               | 9rx wrote:
               | _> I think you should re-read what I wrote._
               | 
               | You "forgot" to copy the colon from the original
               | statement. A curious exclusion given the semantic meaning
               | it carries. Were you hoping I didn't read your original
               | comment and wouldn't notice?
               | 
               |  _> You seem to be upset_
               | 
               | How could one ever possibly become upset on an internet
               | forum? Even if for some bizarre and unlikely reason you
               | were on the path to becoming upset, you'd turn off the
               | computer long before ever becoming upset. There is
               | absolutely no reason to use this tool if it isn't
               | providing joy.
               | 
               |  _> One could define  / extend sync.Pool to get those
               | guarantees [for their custom types] ..._
               | 
               | What audience would be interested in this? Is there
               | anyone who understands all the intricacies of sync.Pool
               | but doesn't know how to define types or how to write
               | functions?
        
               | ignoramous wrote:
               | > _You "forgot" to copy the colon from the original
               | statement._
               | 
               | You got me!
        
             | stouset wrote:
             | > But that's only run when the pool needs to allocate more
             | space. What GP seems to expect is that sync.Pool() would
             | always return a zeroed structure, just as Golang allocation
             | does.
             | 
             | Not quite that. Imagine I have a pool of buffers with a
             | length and capacity, say when writing code to handle
             | receiving data from the network.
             | 
             | When I put one of those buffers back, I would like the next
             | user of that buffer to get it back emptied. The capacity
             | should stay the same, but the length should be zero.
             | 
             | I think it's reasonable to have a callback to do this. One,
             | it doesn't force every consumer of the pool to have to
             | remember themselves; it's now a guarantee of the system
             | itself. Two, it's not _much_ work but it does prevent me
             | from re-emptying freshly-allocated items (in this case
             | reinitialzing is fast, but in some cases it may not be).
             | 
             | This also should be an optional callback since there are
             | many cases where you don't want any form of object reset.
        
           | eptcyka wrote:
           | Is there a time in your career where an object pool
           | absolutely had to contain an unbounded set of types? Any time
           | when you would try know at compile time the total set of
           | types a pool should contain?
        
           | gf000 wrote:
           | How is it different than pre-generic Java?
           | 
           | Map/List<T> etc are erased to basically an array of Objects
           | (or a more specific supertype) at compile-time, but you can
           | still use the non-generic version (with a warning) if you
           | want and put any object into a map/list, and get it out as
           | any other type, you having to cast it as the correct type.
        
           | sapiogram wrote:
           | > You never programmed in Go, I assume?
           | 
           | You might want to step off that extremely high horse for a
           | second, buddy. It's extremely reasonable to expect a type-
           | safe pool that only holds a single type, since that's the
           | most common use case.
        
           | pyrale wrote:
           | > There's no way in Java, Rust or C++ to express this either
           | 
           | You make it look like it's a good thing to be able to express
           | it.
           | 
           | There's no way in Java, Rust or C++ to express this, praised
           | be the language designers.
           | 
           | As for expressing a pool value that may be multiple things
           | without a horrible any type and an horrible cast, you could
           | make an union type in Rust, or an interface in Java
           | implemented by multiple concrete objects. Both ways would
           | force the consumer to explicitly check the value without
           | requiring unchecked duck typing.
        
             | sophacles wrote:
             | Rust has an Any type. It's rarely useful, but there are
             | occasionally situations where a heterogeneous collection is
             | the right thing to do. Casting the any type back to actual
             | type is fairly nice though, as the operation returns an
             | Option<T> and you're forced to deal with the case where
             | your cast is wrong.
        
             | tgv wrote:
             | > You make it look like it's a good thing to be able to
             | express it.
             | 
             | No, just that this pre-generics Go, and backwards
             | compatibility is taken seriously.
        
             | int_19h wrote:
             | > There's no way in Java, Rust or C++ to express this,
             | praised be the language designers.
             | 
             | That's not even the case. In Java, you'd just use Object,
             | which is for all practical purposes equivalent to
             | `interface{}` aka `any` in Go. And then you downcast.
             | Indeed, code exactly like this was necessary in Java to
             | work with collections before generics were added to the
             | language.
             | 
             | In C++, there's no common supertype, but there std::any,
             | which can contain a value of any type and be downcast if
             | you know what the actual type is.
        
         | zaphodias wrote:
         | 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.
         | 
         | Go is pretty strict about breaking changes, so they probably
         | won't change the current implementations; maybe we'll see a v2
         | version, or maybe not. The more code you have, the more code
         | you have to maintain, and given Go's backward-compatibility
         | promises, that's a lot of work.
        
           | Someone wrote:
           | > 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.
           | 
           | That's not a strong argument. You can easily (but sometimes
           | tediously) wrap any API with one that (further) restricts
           | what types you can use with it. Generics make it possible to
           | avoid doing that work, and code you don't write won't have
           | errors.
        
             | zaphodias wrote:
             | Don't get me wrong, I agree! Especially performance-wise,
             | I'd love to have the best primitives that let me build
             | whatever I want and not some very generic primitives that
             | perform a bit worse and I have to tune myself so I don't
             | shoot myself in the foot.
        
           | aktau wrote:
           | Upstream thinks a type-safer `sync.Pool` is a good idea too.
           | It's being discussed in https://go.dev/issue/71076.
        
           | strangelove026 wrote:
           | Sync.map is meant to have poor performance I believe
           | 
           | https://github.com/golang/go/issues/21031
        
             | PhilippGille wrote:
             | It depends on the use case.
             | 
             | From the Godoc:
             | 
             | > The Map type is optimized for two common use cases: (1)
             | when the entry for a given key is only ever written once
             | but read many times, as in caches that only grow, or (2)
             | when multiple goroutines read, write, and overwrite entries
             | for disjoint sets of keys. In these two cases, use of a Map
             | may significantly reduce lock contention compared to a Go
             | map paired with a separate Mutex or RWMutex.
             | 
             | Source: https://pkg.go.dev/sync#Map
             | 
             | And regarding slow writes, those were recently improved in
             | Go 1.24:
             | 
             | > The implementation of sync.Map has been changed,
             | improving performance, particularly for map modifications.
             | For instance, modifications of disjoint sets of keys are
             | much less likely to contend on larger maps, and there is no
             | longer any ramp-up time required to achieve low-contention
             | loads from the map.
             | 
             | Source: https://go.dev/doc/go1.24#minor_library_changes
             | ("sync" section)
        
         | jfwwwfasdfs wrote:
         | A lot of languages have top types
        
         | ncruces wrote:
         | This is still strong typing, even it it's not static typing.
         | 
         | It's static vs. dynamic and strong vs. weak.
         | 
         | https://stackoverflow.com/a/11889763
        
           | 9rx wrote:
           | It is strong, static, and structural. But structural typing
           | is effectively compile-time duck typing, so it is
           | understandable that some might confuse it with dynamic
           | typing.
        
         | jlouis wrote:
         | It is fairly common your type system ends up with escape
         | hatches allowing you to violate the type rules in practice. See
         | e.g., OCaml and the function "magic" in the Obj module.
         | 
         | It serves as a way around a limitation in the type system which
         | you don't want to deal with.
         | 
         | You can still have the rest of the code base be safe, as long
         | as you create a wrapper which is.
         | 
         | The same can be said about having imperative implementations
         | with functional interfaces wrapping said implementation. From
         | the outside, you have a view of a system which is functionally
         | sound. Internally, it might break the rules and use imperative
         | code (usually for the case of efficiency).
        
           | stouset wrote:
           | Obviously every type system in practice has escape hatches.
           | But I've never seen another staticly-typed language where you
           | need to break out of the type system so regularly.
           | 
           | Go's type system has your back when you're writing easy
           | stuff.
           | 
           | But it throws up its hands and leaves you to fend for
           | yourself when you need to do nearly anything interesting or
           | complex, which is precisely when I _want_ the type system to
           | have my back.
           | 
           | I should not have to worry (or worse, not worry and be caught
           | off guard) that my pool of database connections suddenly
           | starts handing back strings.
        
             | int_19h wrote:
             | > But I've never seen another staticly-typed language where
             | you need to break out of the type system so regularly.
             | 
             | It's about the same as Java and C# prior to their adoption
             | of generics, and largely for the same reasons.
        
       | kunley wrote:
       | "Although the struct Data contains a [1024]int array, which is 4
       | KB (assuming int is 4 bytes on the architecture used)"
       | 
       | Huh,what?
       | 
       | I mean, who uses 32b architecture by default?
        
         | bombela wrote:
         | Most C/C++ compilers have 32b int on 64b arch. Maybe the
         | confusion comes from that.
         | 
         | Also it would be 4KiB not 4KB.
        
       | donatj wrote:
       | Unpopular opinion maybe, but sync.Pool is so sharp, dangerous and
       | leaky that I'd avoid using it unless it's your absolute last
       | option. And even then, maybe consider a second server first.
        
         | infogulch wrote:
         | A new sync/v2 NewPool() is being discussed that eliminates the
         | sharp edges by making it generic:
         | https://github.com/golang/go/issues/71076
         | 
         | I haven't personally found it to be problematic; just keep it
         | private, give it a default new func, and be cautious about only
         | putting things in it that you got out.
        
         | nasretdinov wrote:
         | I think in general people understand that sync.Pool introduces
         | essentially an equivalent of unitialised memory (since objects
         | aren't required to be cleaned up before returning them to the
         | pool), and mostly use it for something like []byte, slicing it
         | like buf[0:0] to avoid accidentally reading someone else's
         | memory.
         | 
         | But the instrument itself is really sharp and is indeed kind of
         | last resort
        
       | dennis-tra wrote:
       | Can someone explain to me why the compiler can't do struct-field-
       | alignment? This feels like something that can easily be
       | automated.
        
         | CamouflagedKiwi wrote:
         | Because the order of fields can be significant. It's very
         | relevant for syscalls, and is observable via the reflect
         | package; it'd be strange if the field order was arbitrarily
         | changed (and might change further between releases).
         | 
         | I assume the thinking was that this is pretty easy to optimise
         | if you care, and if it's on by default there'd then have to be
         | some opt-out which there isn't a good mechanism for.
        
           | kbolino wrote:
           | In particular, struct field alignment matches C (even without
           | cgo) and so any change to the default would break a lot of
           | code.
        
             | 9rx wrote:
             | _> struct field alignment matches C (even without cgo)_
             | 
             | The spec defines alignment for numeric types, but that's
             | about it. There is nothing in the spec about struct layout.
             | That is implementation dependent. If you are relying on a
             | particular implementation, you are decidedly in unsafe
             | territory.
             | 
             |  _> so any change to the default would break a lot of
             | code._
             | 
             | The compiler can disable optimization on cgo calls
             | automatically and most other places where it matters are
             | via the standard library, so it might not be as much as you
             | think. And if you still have a case where it matters, that
             | is what this is for: https://pkg.go.dev/structs#HostLayout
        
               | kbolino wrote:
               | That's good to know. I'm not making use of this
               | assumption, but the purego package (came from ebitengine)
               | does. It looks like they're aware of HostLayout [1] but
               | I'm not sure how many other people have gotten the memo
               | (HostLayout didn't exist before Go 1.23).
               | 
               | [1]: https://github.com/ebitengine/purego/issues/259
        
               | arp242 wrote:
               | > There is nothing in the spec about struct layout
               | 
               | De-facto a lot of programs rely on it, so whatever the
               | spec says is irrelevant.
               | 
               | Not just for cgo by the way, but also things like
               | binary.Read()/Write().
        
               | 9rx wrote:
               | _> but also things like binary.Read() /Write()._
               | 
               | binary.Read/Write already uses reflect as far as I can
               | tell, so it wouldn't matter in that case. There is an
               | optimization for numeric values in there, which may be
               | what you are thinking of? But they are specified in the
               | spec and that's not what we're talking about anyway (and
               | if an optimization really had to go, for whatever reason,
               | it wouldn't be the end of the world).
               | 
               | Did you mean something else?
        
             | int_19h wrote:
             | This is very unfortunate, since most structs are never
             | going to be passed to C, yet end up paying the tax anyway.
             | They really should have made it opt-in.
        
           | 9rx wrote:
           | _> and if it 's on by default there'd then have to be some
           | opt-out which there isn't a good mechanism for._
           | 
           | Good is subjective, but the mechanism is something already
           | implemented: https://pkg.go.dev/structs#HostLayout
        
         | 9rx wrote:
         | Like the answer to all "Why doesn't Go have X?" questions: Lack
         | of manpower. There has been some work done to support it, but
         | is far from complete. Open source doesn't mean open willingness
         | to contribute, unfortunately. Especially when you're not the
         | cool kid on the block.
        
       ___________________________________________________________________
       (page generated 2025-04-01 23:01 UTC)