Golang:如何通过缓冲通道判断生产者或消费者哪个速度较慢?

huangapple go评论78阅读模式
英文:

Golang: How to tell whether producer or consumer is slower when communicating via buffered channels?

问题

我有一个使用Golang编写的应用程序,在其中设置了一个流水线,每个组件都执行一些工作,然后通过缓冲通道将其结果传递给另一个组件,然后该组件对其输入执行一些工作,然后通过另一个缓冲通道将其结果传递给另一个组件,依此类推。例如:

C1 -> C2 -> C3 -> ...

其中C1、C2、C3是流水线中的组件,每个 "->" 表示一个缓冲通道。

在Golang中,缓冲通道非常好用,因为它强制快速的生产者减速以匹配下游的消费者(或者快速的消费者减速以匹配上游的生产者)。就像一个装配线一样,我的流水线的速度与其中最慢的组件一样快。

问题是,我想找出流水线中哪个组件是最慢的,以便我可以专注于改进该组件,从而使整个流水线更快。

Golang强制快速的生产者或快速的消费者减速的方式是,在生产者尝试发送到一个已满的缓冲通道时阻塞生产者,或者在消费者尝试从一个空的通道中消费时阻塞消费者。就像这样:

outputChan <- result  // 当发送到满的通道时,生产者会在这里阻塞

input := <- inputChan // 当从空的通道中消费时,消费者会在这里阻塞

这使得很难确定是生产者还是消费者阻塞得最多,从而确定流水线中最慢的组件。因为我无法知道它们阻塞了多长时间。阻塞时间最长的组件是最快的组件,而阻塞时间最短(或根本不阻塞)的组件是最慢的组件。

我可以在读取或写入通道之前添加如下代码来判断是否会阻塞:

// 对于生产者
if len(outputChan) == cap(outputChan) {
    producerBlockingCount++
}
outputChan <- result

// 对于消费者
if len(inputChan) == 0 {
    consumerBlockingCount++
}
input := <-inputChan

然而,这只会告诉我阻塞的次数,而不是总的阻塞时间。更不用说TOCTOU问题了,即检查只针对一个时间点,状态可能会立即在检查之后更改,从而使检查不正确/误导。

任何去过赌场的人都知道,赢或输的次数并不重要,真正重要的是你赢或输的总金额。我可以输掉10手每手10美元(总共损失100美元),然后赢得一手150美元,我仍然是赢家。

同样,生产者或消费者被阻塞的次数并不重要。真正重要的是生产者或消费者被阻塞的总时间,这决定了它们是否是最慢的组件。

但是我想不出任何方法来确定在读取/写入缓冲通道时阻塞的总时间。或者我的搜索能力不够好。有人有什么好主意吗?

英文:

I have an app in Golang where I have a pipeline setup where each component performs some work, then pass along its results to another component via a buffered channel, then that component performs some work on its input then pass along its results to yet another component via another buffered channel, and so on. For example:

C1 -> C2 -> C3 -> ...

where C1, C2, C3 are components in the pipeline and each "->" is a buffered channel.

In Golang buffered channels are great because it forces a fast producer to slow down to match its downstream consumer (or a fast consumer to slow down to match its upstream producer). So like an assembly line, my pipeline is moving along as fast as the slowest component in that pipeline.

The problem is I want to figure out which component in my pipeline is the slowest one so I can focus on improving that component in order to make the whole pipeline faster.

The way that Golang forces a fast producer or a fast consumer to slow down is by blocking the producer when it tries to send to a buffered channel that is full, or when a consumer tries to consume from a channel that is empty. Like this:

outputChan &lt;- result  // producer would block here when sending to full channel

input := &lt;- inputChan // consumer would block here when consuming from empty channel

This makes it hard to tell which one, the producer or consumer, is blocking the most, and thus the slowest component in pipeline. As I cannot tell how long it is blocking for. The one that is blocking the most amount of time is the fastest component and the one that is blocking the least (or not blocking at all) is the slowest component.

I can add code like this just before the read or write to channel to tell whether it would block:

// for producer
if len(outputChan) == cap(outputChan) {
    producerBlockingCount++
}
outputChan &lt;- result

// for consumer
if len(inputChan) == 0 {
    consumerBlockingCount++
}
input := &lt;-inputChan

However, that would only tell me the number of times it would block, not the total amount of time it is blocked. Not to mention the TOCTOU issue where the check is for a single point in time where state could change immediately right after the check rendering the check incorrect/misleading.

Anybody that has ever been to a casino knows that it's not the number of times that you win or lose that matters, it's the total amount of money that you win or lose that's really matter. I can lose 10 hands with $10 each (for a total of $100 loss) and then wins one single hand of $150, I would still comes out ahead.

Likewise, it's not the number of times that a producer or consumer is blocked that's meaningful. It's the total amount of time that a producer or consumer is blocked that's the determining factor whether it's the slowest component or not.

But I cannot think of anyway to determine the total amount that something is blocked at the reading to / writing from a buffered channel. Or my google-fu isn't good enough. Anyone has any bright idea?

答案1

得分: 3

有几种解决方案可以考虑。

1. 秒表

最简单和最明显的方法是在每次读写之前和之后记录时间。将其记录下来,求和,并报告总的I/O延迟。类似地,报告经过的处理时间。

2. 基准测试

进行合成基准测试,其中每个阶段都对一百万个相同的输入进行操作,产生一百万个相同的输出。

或者进行“系统测试”,在该测试中,您可以窃听通过生产环境流动的消息,将其写入日志文件,并将相关的日志消息重新播放到各个流水线阶段,测量经过的时间。由于重新播放,不会有I/O限制。

3. 发布/订阅

重新设计以使用更高开销的通信基础设施,例如Kafka / 0mq / RabbitMQ。更改参与第一阶段处理、第二阶段等的节点数量。思路是压倒当前正在研究的阶段,没有空闲周期,以测量其在饱和状态下的每秒事务吞吐量。

或者,只需将每个阶段分布到自己的节点上,并测量{用户,系统,空闲}时间,以进行正常系统行为的测量。

英文:

There are several solutions that spring to mind.

1. stopwatch

The least invasive and most obvious is to just note the time,
before and after,
each read or write.
Log it, sum it, report on total I/O delay.
Similarly report on elapsed processing time.

2. benchmark

Do a synthetic bench,
where you have each stage operate on a million
identical inputs, producing a million identical outputs.

Or do a "system test" where you wiretap the
messages that flowed through production,
write them to log files,
and replay relevant log messages to each
of your various pipeline stages,
measuring elapsed times.
Due to the replay, there will be no I/O throttling.

3. pub/sub

Re-architect to use a higher overhead
comms infrastructure, such as Kafka / 0mq / RabbitMQ.
Change the number of nodes participating
in stage-1 processing, stage-2, etc.
The idea is to overwhelm the stage currently
under study, no idle cycles, to measure
its transactions / second throughput
when saturated.

Alternatively, just distribute each stage
to its own node, and measure {user, sys, idle} times,
during normal system behavior.

huangapple
  • 本文由 发表于 2022年12月24日 07:55:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/74904836.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定