In golang, why does my program run slower when I use a buffered (asynchronous) channel?

Aaron Johnson :

I'm fairly new to golang, so I'm sure that this question is largely due to some conceptual shortcomings on my part.

In golang, we can have two types of channels: unbuffered and buffered (synchronous and asynchronous, respectively).

unbufferedChan := make(chan string)
bufferedChan := make(chan string, 100)

Two goroutines communicating via an unbuffered channel must wait on each other. That is, the receiving goroutine blocks until the sender sends, and the sender blocks until the receiver receives.

In the buffered case, the receiver blocks only if the channel is empty. The sender blocks only if the channel is full.

By using the buffered channel, I expected to decrease the amount of time the goroutines were blocking, and therefore increase the speed of the code. Instead, it all went slower. Contrary to my expectations (i.e., "Buffered is more efficient") the unbuffered channel ended up being substantially faster.

Here's all the code. You'll need to go get first.

package main

import (
    sel ""
    h5 ""
    gnhtml ""

// Find a specific HTML element and return its textual element children.
func main() {
    test := `
    <title>This is the test document!</title>
      header: color=blue;
    <div id="h" class="header">This is some text</div>

    // Get a parse tree for this HTML
    h5tree, err := h5.NewFromString(test)
    if err != nil {
    n := h5tree.Top()

    // Create a Chain object from a CSS selector statement
    chn, err := sel.Selector("#h")
    if err != nil {

    // Find the item.  Should be a div node with the text "This is some text"
    h := chn.Find(n)[0]

    // run our little experiment this many times total
    var iter int = 100000

    // When buffering, how large shall the buffer be?
    var bufSize uint = 100

    // Keep a running total of the number of times we've tried buffered
    //   and unbuffered channels.
    var bufCount int = 0
    var unbufCount int =0

    // Keep a running total of the number of nanoseconds that have gone by.
    var bufSum int64 = 0
    var unbufSum int64 = 0

    // Call the function {iter} times, randomly choosing whether to use a
    //   buffered or unbuffered channel.
    for i := 0; i < iter; i++ {
        if rand.Float32() < 0.5 {
            // No buffering
            unbufCount += 1
            startTime := time.Now()
            getAllText(h, 0)
            unbufSum += time.Since(startTime).Nanoseconds()     
        } else {
            // Use buffering
            bufCount += 1
            startTime := time.Now()
            getAllText(h, bufSize)
            bufSum += time.Since(startTime).Nanoseconds()
    unbufAvg := unbufSum / int64(unbufCount)
    bufAvg := bufSum / int64(bufCount)

    fmt.Printf("Unbuffered average time (ns): %v\n", unbufAvg)
    fmt.Printf("Buffered average time (ns): %v\n", bufAvg)

// Kill the program and report the error
func die(err error) {
    fmt.Printf("Terminating: %v\n", err.Error())

// Walk through all of a nodes children and construct a string consisting
//   of c.Data where c.Type == TextNode
func getAllText(n *gnhtml.Node, bufSize uint) string {
    var texts chan string
    if bufSize == 0 {
        // unbuffered, synchronous
        texts = make(chan string)
    } else {
        // buffered, asynchronous
        texts = make(chan string, bufSize)

    wg := sync.WaitGroup{}

    // Go walk through all n's child nodes, sending only textual data
    //   over the texts channel.
    nTree := h5.NewTree(n)
    go func () {
        nTree.Walk(func (c *gnhtml.Node) {
            if c.Type == gnhtml.TextNode {
                texts <- c.Data


    // As text data comes in over the texts channel, build up finalString
    finalString := ""
    go func () {
        for t := range texts {
            finalString += t


    // Return finalString once both of the goroutines have finished.
    return finalString

Running the program 10 times, here are the results:

Buffered average time (ns): 32088
Buffered average time (ns): 32183
Buffered average time (ns): 35091
Buffered average time (ns): 35798
Buffered average time (ns): 36966
Buffered average time (ns): 38707
Buffered average time (ns): 39464
Buffered average time (ns): 40021
Buffered average time (ns): 41063
Buffered average time (ns): 46248
Unbuffered average time (ns): 18265
Unbuffered average time (ns): 18804
Unbuffered average time (ns): 20268
Unbuffered average time (ns): 20401
Unbuffered average time (ns): 21652
Unbuffered average time (ns): 22630
Unbuffered average time (ns): 22907
Unbuffered average time (ns): 23326
Unbuffered average time (ns): 24133
Unbuffered average time (ns): 27546
Aaron Johnson :

Actually, I may have just figured it out. I think the extra time is due to the creation of a 100-element buffered channel.

I ran the program again, this time using a buffer size of 10 (still more than my little test program would need) instead of 100 (the original buffer size.)

Here are the results:

Buffered average time (ns): 21930
Buffered average time (ns): 22721
Buffered average time (ns): 23011
Buffered average time (ns): 23707
Buffered average time (ns): 27701
Buffered average time (ns): 28325
Buffered average time (ns): 28851
Buffered average time (ns): 29641
Buffered average time (ns): 30417
Buffered average time (ns): 32600
Unbuffered average time (ns): 21077
Unbuffered average time (ns): 21490
Unbuffered average time (ns): 22332
Unbuffered average time (ns): 22584
Unbuffered average time (ns): 26438
Unbuffered average time (ns): 26824
Unbuffered average time (ns): 27322
Unbuffered average time (ns): 27926
Unbuffered average time (ns): 27985
Unbuffered average time (ns): 30322

These numbers are much closer to each other.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at


Login to comment


TOP Ranking