如何决定并发动作的数量?
How to decide on the amount of concurrent actions?
我目前正在编写一个编码器并且(显然)想要让它变得更快。
我有一个用于编码的工作系统(所以每个 goroutine 都在做同样的事情)但是我正在努力寻找合适数量的 goroutines 来 运行 代码。我基本上想决定在最大数量的 Goroutines 上保持 CPU 忙碌。
我脑海中闪过以下想法:
- 如果一个文件只有 <1 kB,它对 运行 很多 goroutines 中的代码没有用处
- goroutines 的数量应该受到 cores/threads 可用的影响
- 运行在 4x4 GHz CPU 上运行 16 个 goroutine 不会有问题,但是对于 4x1 GHz CPU 会怎样?
- 难以确定可靠的跨平台
- CPU 应该很忙,但不会忙到让其他程序无法响应(~70-ish %?)
- 由于时钟速度和其他参数,很难事先决定
现在我尝试根据这些因素来决定使用多少 goroutine,但我不太确定如何跨平台并以可靠的方式这样做。
已经尝试:
- 使用线性函数根据文件大小确定
- 根据CPU
需要不同的函数
- 正在解析 CPU-来自 lscpu 的规范
- 不能跨平台
- 需要另一个函数来根据频率确定
哪个还不尽如人意。
您在评论中提到
every goroutine is reading the file that is to be encoded
当然,文件——任何文件——已经以某种方式编码:可能是纯文本,或者是 UTF-8(字节流),可能被组装成"lines" 的单位。或者它可能是一个图像流,例如 mpeg 文件,由一定数量的帧组成。或者它可能是一个由记录组成的数据库。无论其 输入 形式如何,它都包含某种基本单元,您可以将其提供给(重新)编码器。
那个单位,不管它是什么,都是一个合理的分工场所。 (如何明智,取决于它是什么。请参阅下面的分块的想法。)
假设文件由独立的行组成:然后使用scanner.Scan
读取它们,并将每个行传递给一个接受行的通道。分拆 N,对于一些 N,阅读频道的读者,一次一行:
ch := make(chan string)
for i := 0; i < n; i++ {
go readAndEncode(ch)
}
// later, or immediately:
for s := bufio.NewScanner(os.Stdin); s.Scan(); {
ch <- s.Text()
}
close(ch)
如果有 100 行和 4 个读取器,前四个 ch <- s.Text()
操作进行得很快,第五个操作暂停,直到其中一个读取器完成编码并返回读取通道。
如果单行作为一个单元太小,也许您应该一次阅读 "chunk"(例如 1 MB)。如果块末尾有部分行,请备份或阅读更多内容,直到你有整行。然后发送整个数据块。
因为通道 复制 数据,您可能希望发送对块的引用。1 这适用于任何更大的数据单元。 (行往往很短,与首先使用通道的开销相比,复制它们的开销通常不是很大。如果您的行的类型为 string
,那么请参阅脚注。)
如果行或 chunk-of-lines 不是此处的正确工作单元,请找出 是什么 。将 goroutines 想象成每个人都有一份工作要做的人(或忙碌的小地鼠)。他们可以依靠其他人——另一个人或 gopher——来做更小的工作,不管那是什么;有 10 个人或 gophers 在 sub-tasks 上工作可以让主管管理他们。如果你需要做同样的工作 N 次,并且 N 不是无限的,你可以分拆 N 个 goroutines。如果 N 是 可能是无界的,则派生出一个固定的数字(可能基于 #cpus)并通过一个渠道提供给他们工作。
1正如 Burak Serdar 指出的那样,一些副本可以自动删除:例如,字符串实际上是 read-only 个切片。切片类型包含三个部分:指向底层数据的指针(引用)、长度和容量。复制切片会复制这三个部分,但不会复制底层数据。字符串也是如此:字符串 headers 省略了容量,因此通过通道发送字符串只会复制两个 header 字。因此,许多显而易见的 easy-to-code 数据分块方法已经非常有效。
我目前正在编写一个编码器并且(显然)想要让它变得更快。
我有一个用于编码的工作系统(所以每个 goroutine 都在做同样的事情)但是我正在努力寻找合适数量的 goroutines 来 运行 代码。我基本上想决定在最大数量的 Goroutines 上保持 CPU 忙碌。
我脑海中闪过以下想法:
- 如果一个文件只有 <1 kB,它对 运行 很多 goroutines 中的代码没有用处
- goroutines 的数量应该受到 cores/threads 可用的影响
- 运行在 4x4 GHz CPU 上运行 16 个 goroutine 不会有问题,但是对于 4x1 GHz CPU 会怎样?
- 难以确定可靠的跨平台
- CPU 应该很忙,但不会忙到让其他程序无法响应(~70-ish %?)
- 由于时钟速度和其他参数,很难事先决定
现在我尝试根据这些因素来决定使用多少 goroutine,但我不太确定如何跨平台并以可靠的方式这样做。
已经尝试:
- 使用线性函数根据文件大小确定
- 根据CPU 需要不同的函数
- 正在解析 CPU-来自 lscpu 的规范
- 不能跨平台
- 需要另一个函数来根据频率确定
哪个还不尽如人意。
您在评论中提到
every goroutine is reading the file that is to be encoded
当然,文件——任何文件——已经以某种方式编码:可能是纯文本,或者是 UTF-8(字节流),可能被组装成"lines" 的单位。或者它可能是一个图像流,例如 mpeg 文件,由一定数量的帧组成。或者它可能是一个由记录组成的数据库。无论其 输入 形式如何,它都包含某种基本单元,您可以将其提供给(重新)编码器。
那个单位,不管它是什么,都是一个合理的分工场所。 (如何明智,取决于它是什么。请参阅下面的分块的想法。)
假设文件由独立的行组成:然后使用scanner.Scan
读取它们,并将每个行传递给一个接受行的通道。分拆 N,对于一些 N,阅读频道的读者,一次一行:
ch := make(chan string)
for i := 0; i < n; i++ {
go readAndEncode(ch)
}
// later, or immediately:
for s := bufio.NewScanner(os.Stdin); s.Scan(); {
ch <- s.Text()
}
close(ch)
如果有 100 行和 4 个读取器,前四个 ch <- s.Text()
操作进行得很快,第五个操作暂停,直到其中一个读取器完成编码并返回读取通道。
如果单行作为一个单元太小,也许您应该一次阅读 "chunk"(例如 1 MB)。如果块末尾有部分行,请备份或阅读更多内容,直到你有整行。然后发送整个数据块。
因为通道 复制 数据,您可能希望发送对块的引用。1 这适用于任何更大的数据单元。 (行往往很短,与首先使用通道的开销相比,复制它们的开销通常不是很大。如果您的行的类型为 string
,那么请参阅脚注。)
如果行或 chunk-of-lines 不是此处的正确工作单元,请找出 是什么 。将 goroutines 想象成每个人都有一份工作要做的人(或忙碌的小地鼠)。他们可以依靠其他人——另一个人或 gopher——来做更小的工作,不管那是什么;有 10 个人或 gophers 在 sub-tasks 上工作可以让主管管理他们。如果你需要做同样的工作 N 次,并且 N 不是无限的,你可以分拆 N 个 goroutines。如果 N 是 可能是无界的,则派生出一个固定的数字(可能基于 #cpus)并通过一个渠道提供给他们工作。
1正如 Burak Serdar 指出的那样,一些副本可以自动删除:例如,字符串实际上是 read-only 个切片。切片类型包含三个部分:指向底层数据的指针(引用)、长度和容量。复制切片会复制这三个部分,但不会复制底层数据。字符串也是如此:字符串 headers 省略了容量,因此通过通道发送字符串只会复制两个 header 字。因此,许多显而易见的 easy-to-code 数据分块方法已经非常有效。