Some questions about the memory allocation strategy of channel

188 views
Skip to first unread message

Paul Zhang

unread,
Mar 18, 2021, 12:55:36 PM3/18/21
to golang-nuts
I was reading the source code of makechan(), and these questions confused me. I would appreciate it if someone could help me.

Q1. What does Elements do not contain pointers. mean? Does that means that the type of channel is not a pointer type (like chan int and chan *int)?

Q2. Why does the allocation strategy of memory allocation differ? It seems like the buf and the hchan should be allocated into one piece of continuous memory, if the "elements contains pointers", so what's the point? The logically continuous memory might not physically continuous.

Ian Lance Taylor

unread,
Mar 18, 2021, 2:50:37 PM3/18/21
to Paul Zhang, golang-nuts
On Thu, Mar 18, 2021 at 9:55 AM Paul Zhang <tianx...@gmail.com> wrote:
>
> I was reading the source code of makechan(), and these questions confused me. I would appreciate it if someone could help me.
>
> Q1. What does Elements do not contain pointers. mean? Does that means that the type of channel is not a pointer type (like chan int and chan *int)?

It means that the element type of the channel is not a pointer and
also does not contain any pointers. For example "struct { a, b int }"
does not contain any pointers, but "struct { a int; b []byte }" does
contain pointers.


> Q2. Why does the allocation strategy of memory allocation differ? It seems like the buf and the hchan should be allocated into one piece of continuous memory, if the "elements contains pointers", so what's the point? The logically continuous memory might not physically continuous.

The garbage collector has to always know exactly where pointers are
stored in memory. We could in principle use contiguous allocation for
the case where the element type contains pointers, but we would have
to build a description that tells the garbage collector exactly where
those pointers are. Those descriptions are built by the compiler (on
tip, the code is in cmd/compile/internal/reflectdata/reflect.go), so
the compiler would have to build a new descriptor for every channel
type with an element type that contains pointers. And the descriptor
would have to vary based on the channel size, so it would be based not
just on the channel type but also on the argument passed to "make".
Of course the argument passed to "make" can be a variable, so that
adds another complication.

So it's probably possible to use a contiguous buffer here, but it's
not simple, and it's not the common case, and it's not clear that it
would be worth it.

Ian

Paul Zhang

unread,
Mar 18, 2021, 11:37:00 PM3/18/21
to Ian Lance Taylor, golang-nuts
For this piece of code:
case elem.ptrdata == 0:
// Elements do not contain pointers.
// Allocate hchan and buf in one call.
c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
c.buf = add(unsafe.Pointer(c), hchanSize)
default:
// Elements contain pointers.
c = new(hchan)
c.buf = mallocgc(mem, elem, true)
}
Can I just understand that for the default case, when the compiler executes the mallocgc() for c.buf, it would use reflect to build the descriptor for the type of element? And if it allocates the both spaces in one function call, it wouldn't build the descriptor for the gc, and gc would find the descriptor later with more time, thus lead to worse performance? Thanks a lot!

Ian Lance Taylor <ia...@golang.org> 于2021年3月19日周五 上午2:50写道:

Jan Mercl

unread,
Mar 19, 2021, 4:47:09 AM3/19/21
to Paul Zhang, Ian Lance Taylor, golang-nuts
On Fri, Mar 19, 2021 at 4:36 AM Paul Zhang <tianx...@gmail.com> wrote:

Please do not post colorized code to the ML. Particularly with
inverted colors. Please use plain black on white text, thank you.

Tamás Gulácsi

unread,
Mar 19, 2021, 12:39:05 PM3/19/21
to golang-nuts
AFAIK when there's no pointer in elem (the channel's element's type), we can cheat/optimize (call as you wish),
and allocate the channel's backing memory AND all it's elements' memory at once, AS A BYTE ARRAY (nil is the second argument of mallocgc).
Then we can play unsafe tricks an treat it as proper channel and its buffer.

When there's pointer somewhere in the element's type, then - as the garbage collector must know each and every pointer's placement -
we don't play tricks, and give mallocgc the proper type information, and allocate the element buffer separate from the channel backing.

You'd have to repeat mallocgc's functionality to provide the gc with proper information, just to save one allocation.
That'd be too much work for less gain.

Ian Lance Taylor

unread,
Mar 19, 2021, 1:20:12 PM3/19/21
to Paul Zhang, golang-nuts
On Thu, Mar 18, 2021 at 8:36 PM Paul Zhang <tianx...@gmail.com> wrote:
>
> Can I just understand that for the default case, when the compiler executes the mallocgc() for c.buf, it would use reflect to build the descriptor for the type of element? And if it allocates the both spaces in one function call, it wouldn't build the descriptor for the gc, and gc would find the descriptor later with more time, thus lead to worse performance? Thanks a lot!

As Jan said, please post code as ordinary text or as a link to the Go
playground or the Go sources. The colorized text on black is very
difficult to read. Thanks.

That said, I'm sorry, I don't really understand what you are asking.

When the code you showed calls mallocgc, it passes the type descriptor
for the channel element type. This is called "elem" in the code.
This type descriptor was created by the compiler.

If the runtime code allocated both the channel data structure and the
buffer in a single memory allocation, it would need to have a type
descriptor that combined the channel data structure with the element
type. Not only that, this new type descriptor would change based on
the argument passed to make. In the existing code, that is not
necessary; when mallocgc is passed a type descriptor to allocate a
size that is larger than the type, it understands that it is
allocating an array. That wouldn't work for an allocation that shares
the channel data structure with the channel buffer.

I don't know what you mean when you say "gc would find the descriptor
later with more time." The compiler would have to create the
descriptor at compile time, so that the runtime code could use that
type descriptor to allocate the memory. The gc can't find the
descriptor later.

Ian

Paul Zhang

unread,
Mar 27, 2021, 12:25:04 PM3/27/21
to golang-nuts
I guess I have misunderstood something about the process of the allocation and gc :) Maybe I need to learn from the gc theory first. BTW thanks for your reply.
Reply all
Reply to author
Forward
0 new messages