第
如何使用.NET在2.2秒内处理10亿行数据(1brc挑战)
在上周我就关注到了在github上有1brc这样一个挑战,当时看到了由VictorBaybekov提交了.NET下最快的实现,当时计划抽时间写一篇文章解析他的代码实现,今天突然看到作者自己写了一篇文章,我感觉非常不错,在这里分享给大家。
这篇文章是关于.NET开发者VictorBaybekov参加的一个名为OneBillionRowChallenge的编程挑战,他使用.NET语言编写了一个实现,这个实现的性能不仅打败了Java,甚至超过了C++。
这个挑战的目标是处理一亿行数据,并提供对数据的快速查询。原始版本只允许Java参与,但其他语言的开发者也希望参与其中,因此挑战对其他语言开放。VictorBaybekov的实现不仅在特定的数据集上表现优秀,而且在处理更通用的数据上也表现出色。他使用.NET的原因是,它的运行速度快且易于使用。
文章中,VictorBaybekov详细介绍了他的优化过程,包括使用内存映射文件,优化哈希函数,使用输入规范,使用自定义字典,优化内部循环等。他还强调了.NET的速度和易用性,同时提到了.NET提供的不安全选项,并不会使代码自动变得不安全。
对于.NET开发者来说,这篇文章提供了很多关于如何优化代码性能的实用信息。对于非.NET开发者来说,这篇文章也是一次了解.NET强大性能的好机会。
总的来说,这篇文章非常专业,为.NET开发者提供了一种思路,即通过使用.NET的功能和优化代码,可以实现非常高的性能。同时,这篇文章也证明了.NET在处理大量数据时的优秀性能和易用性。
在处理真实输入数据时,.NET平台上的十亿行挑战比Java更快,甚至比C++还要快。
上周,GitHub上因为GunnarMorling发起的十亿行挑战而热闹非凡。最初这是一个仅限Java参与的比赛,但后来其他语言的开发者也想加入这场乐趣。如果你不了解这个挑战及其规则,请先阅读这些链接。
/gunnarmorling/1brc/discussions/categories/show-and-tell
我也被这个挑战深深吸引了。截至撰写本文时,我编写的是目前最快的托管1BRC实现版本,它不仅在大家优化的特定数据集上表现出色,而且在更通用的数据上也有很好的性能。更重要的是,我的结果在默认数据上非常接近整体最优的C++版本,并且在通用数据的情况下超过了它。
/buybackoff/1brc
在下面的结果部分,我展示了不同语言和数据集的不同计时结果。在我的#1BRC之旅中,我展示了我的优化历程和性能时间线。然后我讨论了为什么.NET在编写这类代码时既快速又易用。最后,我描述了我如何在日常工作中编写高性能的.NET代码,并邀请你如果对现代且快速的.NET感兴趣,就来申请加入我们。
除了我的代码之外,我还在我的家庭实验室中专门搭建了一个基准测试服务器。它拥有固定的CPU频率并且能够提供非常稳定的结果。我投入了大量的精力来比较不同实现的性能。对于.NET和Java,我测量了同一代码的JIT和AOT性能。
我没有添加排名,因为结果会根据数据的不同而有所不同。我用粗体突出显示了按语言/JIT-AOT/数据集分组的最佳结果,并用黄色背景突出显示了按数据集分组的整体最佳结果。
Windows上以\r\n结束。重复1B次。
关键Idea
提交时的文件:/buybackoff/1brc/tree/f1b81f8a590a8a42d5be8358e6ba30489e678592/1brc
与上一版本的差异:/buybackoff/1brc/compare/82a17bc..f1b81f8diff=splitw=
时间:13.490/17.991(10K)
我的实现的关键思想直到最后都没有改变。魔鬼隐藏在最微小的细节中。
内存映射文件
使用mmap是显而易见的,因为我之前在高性能场景下多次使用它,比如IPC环形缓冲区。它非常简单易用,所有复杂性都由操作系统管理。最近数据库社区就是否使用mmap还是手动内存管理,即LMDB与其他方式之间进行了激烈的讨论。顺便说一句,我是LMDB的大粉丝,甚至为其编写了最快的.NET封装。
尽管如此,为了避免munmap的慢速时间,我在这里尝试了不使用mmap的方法。结果确实慢了一些,但并不太多。仅将文件复制到内存中最多需要大约200毫秒的CPU带宽,再加上不可避免的开销,这就很能说明问题了。
Utf8Span
Utf8Span可能是实现高性能