在golang中,当我使用缓冲(异步)通道时,为什么我的程序运行得较慢



我对golang还很陌生,所以我确信这个问题很大程度上是由于我在概念上的一些不足。

在golang中,我们可以有两种类型的通道:非缓冲通道和缓冲通道(分别为同步通道和异步通道(。

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

通过非缓冲通道通信的两个goroutine必须相互等待。也就是说,接收goroutine阻塞,直到发送方发送,发送方阻塞,直到接收方接收。

在缓冲的情况下,只有当信道为空时,接收器才会阻塞。只有当通道已满时,发送方才会阻止。

通过使用缓冲通道,我希望减少goroutines阻塞的时间,从而提高代码的速度。相反,一切都变慢了。与我的预期相反(即,"缓冲更高效"(,无缓冲的通道最终大大加快了速度。

这是所有的代码您需要先使用go get code.google.com/p/go-html-transform/html/transform

package main
import (
    "fmt"
    "math/rand"
    "os"
    "sync"
    "time"
    sel "code.google.com/p/go-html-transform/css/selector"
    h5 "code.google.com/p/go-html-transform/h5"
    gnhtml "code.google.com/p/go.net/html"
)
// Find a specific HTML element and return its textual element children.
func main() {
    test := `
<html>
  <head>
    <title>This is the test document!</title>
    <style>
      header: color=blue;
    </style>
  </head>
  <body>
    <div id="h" class="header">This is some text</div>
  </body>
</html>`
    // Get a parse tree for this HTML
    h5tree, err := h5.NewFromString(test)
    if err != nil {
        die(err)
    }
    n := h5tree.Top()
    // Create a Chain object from a CSS selector statement
    chn, err := sel.Selector("#h")
    if err != nil {
        die(err)
    }
    // 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): %vn", unbufAvg)
    fmt.Printf("Buffered average time (ns): %vn", bufAvg)
}
// Kill the program and report the error
func die(err error) {
    fmt.Printf("Terminating: %vn", err.Error())
    os.Exit(1)
}
// 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.
    wg.Add(1)
    nTree := h5.NewTree(n)
    go func () {
        nTree.Walk(func (c *gnhtml.Node) {
            if c.Type == gnhtml.TextNode {
                texts <- c.Data
            }
        })
        close(texts)
        wg.Done()
    }()
    // As text data comes in over the texts channel, build up finalString
    wg.Add(1)
    finalString := ""
    go func () {
        for t := range texts {
            finalString += t
        }
        wg.Done()
    }()
    // Return finalString once both of the goroutines have finished.
    wg.Wait()
    return finalString
}

运行程序10次,结果如下:

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

实际上,我可能刚刚想明白了。我认为额外的时间是由于创建了一个100元素的缓冲通道。

我再次运行该程序,这次使用的缓冲区大小为10(仍然超过我的小测试程序所需的大小(,而不是100(原始缓冲区大小(

结果如下:

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

这些数字彼此更接近。

在测试中外部化缓冲区的创建:

package main
import (
    "fmt"
    "math/rand"
    "os"
    "sync"
    "time"
    sel "code.google.com/p/go-html-transform/css/selector"
    h5 "code.google.com/p/go-html-transform/h5"
    gnhtml "code.google.com/p/go.net/html"
)
// Find a specific HTML element and return its textual element children.
func main() {
    test := `
<html>
  <head>
    <title>This is the test document!</title>
    <style>
      header: color=blue;
    </style>
  </head>
  <body>
    <div id="h" class="header">This is some text</div>
  </body>
</html>`
    // Get a parse tree for this HTML
    h5tree, err := h5.NewFromString(test)
    if err != nil {
        die(err)
    }
    n := h5tree.Top()
    // Create a Chain object from a CSS selector statement
    chn, err := sel.Selector("#h")
    if err != nil {
        die(err)
    }
    // 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
            texts := make(chan string)
            startTime := time.Now()
            getAllText(h, 0, texts)
            unbufSum += time.Since(startTime).Nanoseconds()     
        } else {
            // Use buffering
            bufCount += 1
            texts := make(chan string, bufSize)
            startTime := time.Now()
            getAllText(h, bufSize, texts)
            bufSum += time.Since(startTime).Nanoseconds()
        }
    }
    unbufAvg := unbufSum / int64(unbufCount)
    bufAvg := bufSum / int64(bufCount)
    fmt.Printf("Unbuffered average time (ns): %vn", unbufAvg)
    fmt.Printf("Buffered average time (ns): %vn", bufAvg)
}
// Kill the program and report the error
func die(err error) {
    fmt.Printf("Terminating: %vn", err.Error())
    os.Exit(1)
}
// 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, texts chan string) string {
    wg := sync.WaitGroup{}

    // Go walk through all n's child nodes, sending only textual data
    //   over the texts channel.
    wg.Add(1)
    nTree := h5.NewTree(n)
    go func () {
        nTree.Walk(func (c *gnhtml.Node) {
            if c.Type == gnhtml.TextNode {
                texts <- c.Data
            }
        })
        close(texts)
        wg.Done()
    }()
    // As text data comes in over the texts channel, build up finalString
    wg.Add(1)
    finalString := ""
    go func () {
        for t := range texts {
            finalString += t
        }
        wg.Done()
    }()
    // Return finalString once both of the goroutines have finished.
    wg.Wait()
    return finalString
}

有了这个(在启动定时器之前移动通道创建(,我得到了这样的结果:

Buffered average time (ns): 2649
Buffered average time (ns): 2655
Buffered average time (ns): 2657
Buffered average time (ns): 2695
Buffered average time (ns): 2695
Buffered average time (ns): 2699
Buffered average time (ns): 2719
Buffered average time (ns): 2724
Buffered average time (ns): 2793
Buffered average time (ns): 3101
Unbuffered average time (ns): 2997
Unbuffered average time (ns): 3005
Unbuffered average time (ns): 3027
Unbuffered average time (ns): 3031
Unbuffered average time (ns): 3048
Unbuffered average time (ns): 3062
Unbuffered average time (ns): 3086
Unbuffered average time (ns): 3092
Unbuffered average time (ns): 3095
Unbuffered average time (ns): 3191

为了改进,解决方案可能是尽可能保持渠道畅通。