增强大规模任务和高效人员的通道性能
“纵有疾风来,人生不言弃”,这句话送给正在学习Golang的朋友们,也希望在阅读本文《增强大规模任务和高效人员的通道性能》后,能够真的帮助到大家。我也会在后续的文章中,陆续更新Golang相关的技术文章,有好的建议欢迎大家在评论留言,非常感谢!
我想有效地解决一个概念上类似于查找空间中 p
点的平均成对距离的问题,这是我为了解决这个问题而使用的示例。计算可以很好地并行化,所以我想用 go 来解决它。在顺序程序中,我需要运行两个嵌套循环,外部循环在 i = 0...p-1
上,内部循环在 j = i+1...p-1
上。然后,我会计算点 i
和 j
之间的距离,将它们全部相加,最后除以点对的数量。因此,计算需要覆盖可能的点对组合的“三角形”。
在 go 中,我的第一次尝试是使用相同的逻辑,但通过通道将计算分配给工作函数。我的方法如下:
package main import "math" import "sync" import "fmt" import "math/rand" import "github.com/schollz/progressbar" const nprocs = 32 const npoints = 30000 type pair struct { p1 [3]float64 p2 [3]float64 } func square(f float64) (float64) { return f * f } func progress(total int64, ch <-chan int) { bar := progressbar.default(total) for i := range ch { bar.add(i) } } func worker(idx int, sumbuffer []float64, in <-chan pair, out chan<- int, wg *sync.waitgroup) { count := int64(0) defer wg.done(); for pair := range in { dist := math.sqrt(square(pair.p1[0]-pair.p2[0]) + square(pair.p1[1]-pair.p2[1]) + square(pair.p1[2]-pair.p2[2])) sumbuffer[idx] += dist count++ if count % (2<<15) == 0 { out <- (2<<15) } } } func main() { var sumbuffer [nprocs]float64 var points [npoints][3]float64 var sum float64 for i := 0; i < npoints; i++ { for j := 0; j < 3; j++ { points[i][j] = rand.float64() } } wg := &sync.waitgroup{}; wg.add(nprocs); progressch := make(chan int, 4 * nprocs) pairch := make(chan pair, 4 * nprocs) npairs := int64(npoints - 1) * int64(npoints) / int64(2) go progress(npairs, progressch) for i := 0; i < nprocs; i++ { go worker(i, sumbuffer[:], pairch, progressch, wg) } for i := int64(0); i < npoints; i++ { for j := int64(i+1); j < npoints; j++ { pairch <- pair{points[i], points[j]} } } close(pairch) wg.wait(); for i := 0; i < nprocs; i++ { sum += sumbuffer[i] } sum /= float64(npairs) fmt.println("average distance:", sum) }
但是,该程序的运行速度没有我预期的那么快。在第二次尝试中,我摆脱了分配任务的渠道,而是手动在工作人员之间划分计算。然后,每个worker首先需要计算自己要覆盖“三角形”的哪一部分,相当麻烦。
package main import "math" import "sync" import "fmt" import "math/rand" import "github.com/schollz/progressbar" const nProcs = 32 const nPoints = 30000 func square(f float64) (float64) { return f * f } func progress(total int64, ch <-chan int) { bar := progressbar.Default(total) for i := range ch { bar.Add(i) } } func worker(idx int, points [][3]float64, sumBuffer []float64, start int64, stop int64, out chan<- int, wg *sync.WaitGroup) { count := start length := int64(len(points)) defer wg.Done(); // Calculate start value for loop index i iStart := int64(0) pointCount := int64(0) for k := length - 1; k >= 0; k-- { if pointCount + k > start { break } else { pointCount += k iStart++ } } firstLoop := true for i := int64(iStart); i < length && count < stop; i++ { // Calculate start value for loop index j var jStart int64 if firstLoop { jStart = (i + 1) + (start - pointCount) } else { jStart = i + 1 } for j := int64(jStart); j < length && count < stop; j++ { dist := math.Sqrt(square(points[i][0]-points[j][0]) + square(points[i][1]-points[j][1]) + square(points[i][2]-points[j][2])) sumBuffer[idx] += dist count++ if count % (2<<15) == 0 { out <- (2<<15) } } firstLoop = false } } func main() { var sumBuffer [nProcs]float64 var points [nPoints][3]float64 var sum float64 for i := 0; i < nPoints; i++ { for j := 0; j < 3; j++ { points[i][j] = rand.Float64() } } wg := &sync.WaitGroup{}; wg.Add(nProcs); progressCh := make(chan int, 4 * nProcs) nPairs := int64(nPoints - 1) * int64(nPoints) / int64(2) go progress(nPairs, progressCh) step := int64(math.Ceil(float64(nPairs) / float64(nProcs))) for i := 0; i < nProcs; i++ { go worker(i, points[:], sumBuffer[:], int64(i) * step, int64(i+1) * step, progressCh, wg) } wg.Wait(); for i := 0; i < nProcs; i++ { sum += sumBuffer[i] } sum /= float64(nPairs) fmt.Println("Average distance:", sum) }
现在第二个程序快了大约 100 倍!然而,我什至没有利用该版本中 go 的优势,而且我可以用 c++ 编写相同的程序。如何改进第一个程序,使得使用通道带来的开销不那么严重?或者这只是渠道效率的限制,而对于我的用例来说,渠道根本不是正确的选择?
除此之外,我对 go 还很陌生。我确信我的程序对于 go 来说不太理想。欢迎任何关于如何改进我的风格的评论。
解决方案
通道版本可能会更慢,因为相对于 goroutine 之间的调度和通信的开销来说,工作(计算距离)非常小。
最好将点对分成更大的块,就像您在第二个示例中所做的那样。
这里有一个简单的方法来了解发生了什么:
var sum float64 var count int64 for i, p := range points { for _, p2 := range points[i+1:] { sum += p.distanceto(p2) count++ } } fmt.printf("%d points, %d pairs, avg %f\n", len(points), count, sum/float64(count))
当我尝试这个时,我的笔记本电脑变热,然后输出:
100000 points, 4999950000 pairs, avg 0.660843 duration 22.623835855s
每次距离计算大约需要 5 纳秒,速度非常快,而且分工太小。
只有一个 cpu 核心正在执行这项工作:
由于我的笔记本电脑有 8 个内核,因此我尝试创建一个示例,其中 8 个工作人员处理相同的点对块。
8 workers, 100000 points, 4999950000 pairs, avg 0.660843 duration 7.481476421s
速度并没有提高 8 倍,但它是一种改进,并且可以让这些闲置核心投入工作:
示例:
package main import ( "fmt" "math" "math/rand" "runtime" "time" ) const nPoints = 100000 var points [nPoints]*Point type Point struct { x, y, z float64 } func (p *Point) distanceTo(p2 *Point) float64 { a := p.x - p2.x b := p.y - p2.y c := p.z - p2.z return math.Sqrt(a*a + b*b + c*c) } func main() { initPoints() start := time.Now() run() fmt.Printf("duration %v\n", time.Since(start)) } func initPoints() { for i := 0; i < nPoints; i++ { points[i] = &Point{rand.Float64(), rand.Float64(), rand.Float64()} } } func run() { requests := make(chan Request) results := make(chan Result) nWorkers := runtime.NumCPU() //change, eg: 1 or 2 for n := 0; n < nWorkers; n++ { go worker(requests, results) } go func() { nPairs := nPoints * (nPoints - 1) / 2 batchSize := nPairs / nWorkers req := Request{iStart: 0, jStart: 1, count: 0} for i := 0; i < nPoints; i++ { for j := i + 1; j < nPoints; j++ { req.count++ if req.count == batchSize { requests <- req req = Request{iStart: i, jStart: j, count: 0} } } } if req.count > 0 { requests <- req } close(requests) }() var sum, avg float64 var count int64 for n := 0; n < nWorkers; n++ { r := <-results fmt.Printf("worker %d: %v\n", n+1, r) sum += r.sum count += r.count } close(results) if count > 0 { avg = sum / float64(count) } fmt.Printf("%d workers, %d points, %d pairs, avg %f\n", nWorkers, len(points), count, avg) } func worker(requests chan Request, results chan Result) { r := Result{} for req := range requests { req.work(&r) } results <- r } func (req *Request) work(result *Result) { count := 0 jStart := req.jStart for i := req.iStart; i < nPoints; i++ { p := points[i] if i > req.iStart { jStart = i + 1 } for j := jStart; j < nPoints; j++ { p2 := points[j] result.sum += p.distanceTo(p2) result.count++ count++ if count == req.count { return } } } } type Request struct { iStart int jStart int count int } type Result struct { sum float64 count int64 } func (r Result) String() string { avg := 0.0 if r.count > 0 { avg = r.sum / float64(r.count) } return fmt.Sprintf("%d comparisons, avg distance %f", r.count, avg) }
pprof profiling blog post 很好地演示了查看程序将时间花在哪里的技术。
以上就是《增强大规模任务和高效人员的通道性能》的详细内容,更多关于的资料请关注golang学习网公众号!

- 上一篇
- 选择适用的集成开发环境(IDE)对高效开发Go语言项目至关重要

- 下一篇
- 深入解析Java环境变量的配置步骤
-
- Golang · Go问答 | 1年前 |
- 在读取缓冲通道中的内容之前退出
- 139浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 戈兰岛的全球 GOPRIVATE 设置
- 204浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何将结构作为参数传递给 xml-rpc
- 325浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何用golang获得小数点以下两位长度?
- 477浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何通过 client-go 和 golang 检索 Kubernetes 指标
- 486浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 将多个“参数”映射到单个可变参数的习惯用法
- 439浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 将 HTTP 响应正文写入文件后出现 EOF 错误
- 357浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 结构中映射的匿名列表的“复合文字中缺少类型”
- 352浏览 收藏
-
- Golang · Go问答 | 1年前 |
- NATS Jetstream 的性能
- 101浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何将复杂的字符串输入转换为mapstring?
- 440浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 相当于GoLang中Java将Object作为方法参数传递
- 212浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何确保所有 goroutine 在没有 time.Sleep 的情况下终止?
- 143浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- 协启动
- SEO摘要协启动(XieQiDong Chatbot)是由深圳协启动传媒有限公司运营的AI智能服务平台,提供多模型支持的对话服务、文档处理和图像生成工具,旨在提升用户内容创作与信息处理效率。平台支持订阅制付费,适合个人及企业用户,满足日常聊天、文案生成、学习辅助等需求。
- 9次使用
-
- Brev AI
- 探索Brev AI,一个无需注册即可免费使用的AI音乐创作平台,提供多功能工具如音乐生成、去人声、歌词创作等,适用于内容创作、商业配乐和个人创作,满足您的音乐需求。
- 9次使用
-
- AI音乐实验室
- AI音乐实验室(https://www.aimusiclab.cn/)是一款专注于AI音乐创作的平台,提供从作曲到分轨的全流程工具,降低音乐创作门槛。免费与付费结合,适用于音乐爱好者、独立音乐人及内容创作者,助力提升创作效率。
- 9次使用
-
- PixPro
- SEO摘要PixPro是一款专注于网页端AI图像处理的平台,提供高效、多功能的图像处理解决方案。通过AI擦除、扩图、抠图、裁切和压缩等功能,PixPro帮助开发者和企业实现“上传即处理”的智能化升级,适用于电商、社交媒体等高频图像处理场景。了解更多PixPro的核心功能和应用案例,提升您的图像处理效率。
- 9次使用
-
- EasyMusic
- EasyMusic.ai是一款面向全场景音乐创作需求的AI音乐生成平台,提供“零门槛创作 专业级输出”的服务。无论你是内容创作者、音乐人、游戏开发者还是教育工作者,都能通过EasyMusic.ai快速生成高品质音乐,满足短视频、游戏、广告、教育等多元需求。平台支持一键生成与深度定制,积累了超10万创作者,生成超100万首音乐作品,用户满意度达99%。
- 12次使用
-
- GoLand调式动态执行代码
- 2023-01-13 502浏览
-
- 用Nginx反向代理部署go写的网站。
- 2023-01-17 502浏览
-
- Golang取得代码运行时间的问题
- 2023-02-24 501浏览
-
- 请问 go 代码如何实现在代码改动后不需要Ctrl+c,然后重新 go run *.go 文件?
- 2023-01-08 501浏览
-
- 如何从同一个 io.Reader 读取多次
- 2023-04-11 501浏览