您现在的位置是:主页 > 云市场 >

大数据难吗-Go表演故事

2020-10-21 10:02云市场 人已围观

简介理论/量度/gojasonmoiron@jmoiron是一名Datadog软件工程师,在这个博客最初发布的地方运行着自己的博客。我们很高兴杰森给了我们机会与数据狗分享他的帖子社区。那个在过去的几个月里...

Go表演故事

理论/量度/gojasonmoiron@jmoiron是一名Datadog软件工程师,在这个博客最初发布的地方运行着自己的博客。我们很高兴杰森给了我们机会与数据狗分享他的帖子社区。那个在过去的几个月里,物联网关键技术,我有幸在Datadog从事一项新的摄入处理工作。这是我们用Go编写的第一个生产服务,我想确定一些重要的消费者、处理和调度习惯用法的性能,这些习惯用法将构成未来项目的基础。我写了很多基准测试,花了很多时间检查配置文件的输出,学习关于Go的新东西,重新学习编程的旧东西。虽然使用直觉可能是一种有缺陷的方法来获得良好的表现,但了解为什么你会得到某些行为通常证明是有价值的。我想分享我的一些事情学习。使用整数映射键(如果可能)新服务的设计目的是管理索引,这些索引跟踪客户最近使用度量、主机和标记的时间。这些索引在前端用于概览页和自动完成。通过减轻主进气处理器的负担,我们可以将其释放出来执行其他任务,并添加更多索引来加速网站。这个状态处理器将保留我们最近看到的所有度量的历史记录。如果从队列中出来的数据点不在历史记录中,它会被快速刷新到索引中,以确保新的主机和指标尽快出现在站点上。如果它在历史记录中,那么它很可能已经在索引中,并且可以放入缓存中以减少刷新的频率。这种方法将保持新数据点的低延迟,同时大幅减少重复的数量写。我们开始使用map[string]结构{}来实现这些历史记录和缓存。虽然我们的度量名称通常是分层的,patricia tries/radix trees似乎非常适合,但是我找不到也没有构建一个可以与Go的map实现竞争的,甚至是对于数以千万计的元素的集合。在遍历树时比较大量的子字符串会降低它与哈希的查找性能,而内存方面,8字节指针意味着您需要相当大的匹配子字符串来节省映射上的空间。为了保持内存使用率,让条目过期也是一件棘手的事情有界。偶数使用map,我们仍然没有看到我认为使用Go可以实现的吞吐量类型。地图操作在我们的档案中非常突出。我们能从他们身上得到更多的表现吗?我们现有的所有索引都是基于在后端有相关联的整数id的字符串数据,所以我对带有整数键的映射和带字符串的映射的插入/哈希性能进行了基准测试键:BenchmarkTypedSetStrings 10000001393纳秒/次基准类型试验次数10000000 275 ns/op这看起来很有希望。由于来自队列的数据点已经规范化为它们的id,所以我们可以将整数用作映射键,而不必做额外的工作。使用map[int]*度量而不是map[string]struct{}可以给我们一个已知的整数键,同时保持对索引所需字符串的访问。实际上,它要快得多:总吞吐量双倍AES-NI处理器extns确实提高了字符串哈希性能实际上,我们想添加新的索引来跟踪最近看到的"应用程序"。在某些概念中,这些名称通常是基于特定的结构近似吞吐量"或"应用程序延迟". 我们为应用程序关联了后端ID,因此我们为它们恢复了字符串键控的映射,总体吞吐量像石头一样下降。可以预见的是,应用程序历史中的字符串映射分配(我们已经知道它的速度很慢)是罪魁祸首:事实上,runtime·strhash†"runtime·memhash路径控制了输出,比所有其他整数哈希和所有通道通信占用的时间都多。这是一个例证,如果需要证明,人们应该更喜欢结构而不是映射,只要有一个简单的命名值集合必须的。仍然,云服务器免,这里的斯特拉什表演似乎很糟糕。在重插入的情况下,散列如何比所有其他映射开销占用更多的时间?这些不是大钥匙。当我问到如何在go nuts中提高字符串哈希性能时,有人告诉我,自从go 1.1以来,runtime·memhash有一个使用AES-NI处理器扩展的快速路径,一个快速的grep AES/proc/cpuinfo显示我所在的aws c1.xlarge设备缺少这些功能。在找到了与它们在同一类中的另一台机器后,买云服务器,吞吐量增加了50-65%,并且strhash的显著性在配置文件。注意上面设置的stringvsint配置文件是在没有AES-NI支持的机器上完成的。不用说,这些扩展将使这些结果更加接近一起。德-神秘化通道我们从中读取的队列发送的消息包含许多单独的度量;在Go术语中,您可以想象像message[]Metric类型的消息,其中的长度是相当可变的。我很早就决定用单一的度量标准化我们的信道通信单元,因为它们在网络上的大小都是一样的。这允许更可预测的内存使用和简单、无状态的处理代码。当程序开始整合时,我在生产消防水龙带上进行了试运行,但性能并不令人满意。分析显示,在atomic ASM wrapper runtime·xchg(如下所示)和runtime·futex中花费了大量时间。运行时在不同的地方使用这些原子:内存分配器、GC、调度器、锁、信号量等等。在我们的配置文件中,它们大多是runtime·chansend和selectgo的后代,它们是Go的通道实现的一部分。性能问题是由于缓冲通道中存在大量锁定和解锁执行。而通道提供了强大的并发语义,它们的并行化实现并不神奇。在异步通道上发送、接收和选择的大多数路径目前都涉及到锁定;尽管它们的语义与goroutine结合在一起改变了游戏规则,但作为一种数据结构,它们与许多其他同步队列/环形缓冲区的实现完全一样。目前正在努力改善信道性能,但这并不能完全实现无锁实施。今天,在该通道上发送或接收调用runtime·lock,在确定它不是nil之后不久。尽管Dmitry正在进行的通道性能检修工作看起来很有前途,但对于未来Go性能的改进,更令人兴奋的是他提出的原子内部函数(atomic intrinsics),它可以在整个运行时减少所有这些原子锁原语的开销。此时,它看起来可能会错过1.3,但很有可能会在1.4中重新检查。我决定逐个发送指标意味着我们发送、接收和选择的频率比必要的要多,每个消息锁定和解锁多次。虽然它在我们的度量处理代码中以循环的形式增加了一些额外的复杂性,但是对传递消息的重新标准化反而减少了这些锁发送和读取的数量,以至于它们实际上会丢弃我们的后续概要文件。吞吐量提高了近6倍。Cgo和borders在加入这个项目之前,我预期的慢的原因之一是Go对zlib的实现。我在过去做过一些测试,结果表明它比Python慢得多,这些文件大小覆盖了我们的消息的典型大小。zlibc实现以其优化良好而著称,当我发现Intel最近为它提供了许多补丁时,我很想看看它将如何衡量起来。很幸运,返利微信,YouTube的vitess项目已经实现了一个非常好的Go包装器cgzip,在我的测试中,它的性能比Go的gzip要好很多。尽管如此,Python的gzip仍然比它表现得更好,这让我很困惑。我深入研究了Python的zlibmodule.c和czip的代码读卡器.go,并注意到cgzip从Go开始管理它的缓冲区,返利平台,而Python则完全用C来管理它们。进一步的研究揭示了一些原因间接费用:Cgo有与go调度程序进行一些协调,以便它知道调用goroutine被阻塞,这可能涉及创建另一个线程来防止死锁。获取并释放锁。那个Go堆栈必须换成C堆栈,因为它不知道C堆栈的内存需求是什么,然后必须再次交换它们回来。那里是为C函数调用生成的一个C填充程序,它以一种干净的方式将C和Go的调用/返回语义映射到一起;例如,C中的struct返回在中作为多值返回去吧。差不多对于通过上述通道进行通信,Go函数调用和C函数调用之间的通信是有负担的。如果我想获得更好的性能,我必须通过增加每次通话的工作量来减少通信量。由于通道的改变,整个消息现在是我的管道中最小的可处理单元,因此流式gzip阅读器无可置疑的好处相对减少了。我使用Python的zlibmodule.c作为模板,在c中完成所有的缓冲区处理,返回一个原始字符*我可以在Go端复制到一个[]字节中,并执行一些操作分析:452字节测试有效载荷(1071 orig)基准无需压缩200000 9509 ns/opBenchmarkFzlib解压缩200000 10302 ns/op基准CZLIB解压缩100000 26893 ns/opBenchmarkZlibDecompress 50000 46063 ns/op7327字节测试有效载荷(99963原始)基准无需压缩10000 198391 ns/op基准Fzlibdecompress 10000 244449 ns/op基准CZLIB解压缩10000 276357 ns/op基准zlibdecompress

Tags: 故事  表演 

标签云

站点信息

  • 文章统计3903篇文章
  • 标签管理标签云
  • 微信公众号:扫描二维码,关注我们