Which channel type uses the least amount of memory in Go?

4.4k views Asked by At

I find myself frequently using channels to get things to stop. In these cases the channel is being used solely as a means of signaling, and none of the data is actually used.

For example:

package main

import (
    "fmt"
    "time"
)

func routine(stopChan chan bool) {
    fmt.Println("goroutine: I've started!")
    <-stopChan
    fmt.Println("goroutine: Cya'round pal")
}

func main() {
    fmt.Println("main: Sample program run started")

    stopChan := make(chan bool)

    go routine(stopChan)

    fmt.Println("main: Fired up the goroutine")

    stopChan <- true

    time.Sleep(1 * time.Second)

    fmt.Println("main: Sample program run finished")
}

// Sample output:
//
//  main: Sample program run started
//  main: Fired up the goroutine
//  goroutine: I've started!
//  goroutine: Cya'round pal
//  main: Sample program run finished

Run/view it if you please on the golang playground.


My question is:

Which channel type has the lightest memory footprint in Go?

e.g. Is a bool chan going to require any less overhead than an empty struct{} chan?

chan bool

chan byte

chan interface{}

chan struct{}

...

something else?

1

There are 1 answers

0
tomasz On BEST ANSWER

Looking at the latest implementation of the channel, it's not a trivial structure:

type hchan struct {
    qcount   uint           // total data in the queue
    dataqsiz uint           // size of the circular queue
    buf      unsafe.Pointer // points to an array of dataqsiz elements
    elemsize uint16
    closed   uint32
    elemtype *_type // element type
    sendx    uint   // send index
    recvx    uint   // receive index
    recvq    waitq  // list of recv waiters
    sendq    waitq  // list of send waiters
    lock     mutex
}

Elements of waiter queues are also quite heavy:

type sudog struct {
    g           *g
    selectdone  *uint32
    next        *sudog
    prev        *sudog
    elem        unsafe.Pointer // data element
    releasetime int64
    nrelease    int32  // -1 for acquire
    waitlink    *sudog // g.waiting list
}

You see, many bytes. Even if any element would be created for an empty channel, this would be negligible.

However, I expect all empty channels to take the same amount of space, regardless of underlying type, so if you intend to only close the channel, there'll be no difference (an actual element seems to be hold by a pointer). A quick test backs it up:

package main

import (
    "fmt"
    "time"
)

func main() {
    // channel type
    type xchan chan [64000]byte
    a := make([]xchan, 10000000) // 10 million
    for n := range a {
        a[n] = make(xchan)
    }
    fmt.Println("done")
    time.Sleep(time.Minute)
}

I see no difference between chan struct{} and chan [64000]byte, both leads to ~1GB of usage on my 64-bit machine, which makes me believe that overhead of creating a single channel in somewhere around 100 bytes.

In conclusion, it doesn't really matter. Personally I would use struct{} as it's the only really empty type (of size 0 indeed), clearly indicating there is no intention of any payload being sent.