押大小赌钱软件下载

新闻你的位置:押大小赌钱软件下载 > 新闻 >

赌钱赚钱app测量值介于 -99.9 到 99.9 之间-押大小赌钱软件下载

发布日期:2024-06-19 11:47    点击次数:66

赌钱赚钱app

2024 年的第一天,Decodable 高档软件工程师 Gunnar Morling 曾向 Java 社区发起了一个十亿行挑战(1BRC),即条目在最快技艺内处理 10 亿行数据。针对这个挑战,本文作家 ŠIMON TÓTH 尝试用 C++ 来完成,从领先结束到经过一系列优化后,最终运行速率提高了 87 倍。

原文汇集:https://simontoth.substack.com/p/daily-bite-of-c-optimizing-code-to

作家 | ŠIMON TÓTH

翻译 | 郑丽媛

“十亿行挑战”是对 Java 开发者的一项挑战,预计是要在最快技艺内处理 10 亿行数据。诚然领先这个挑战是针对 Java 的,但本次挑战是展示 C++ 代码优化和关系性能用具的绝佳契机。(本文波及的竣工源代码地址:https://github.com/HappyCerberus/1brc。)

挑战内容

咱们的输入是一个名为 measurements.txt 的文献,其中包含来自不同测量站的温度测量数据。该文献包含十亿行数据,局势如下:

station name;value\nstation name;value

测量站称号是一个最多 100 字节的 UTF-8 字符串,包含任何 1 字节或 2 字节的字符(但不成包含‘;’或‘\\n’),测量值介于 -99.9 到 99.9 之间,均保留一位极少。另外,惟一键的总和最多为 10,000 个。

输出是一个按字母限定排序的测量站列表,每个站点都有测得的最低温度、平均温度和最高温度。

{Abha=-23.0/18.0/59.2, Abidjan=-16.2/26.0/67.3, Abéché=-10.0/29.4/69.0, ...}

领先结束

天然,咱们开始要从基准结束启动。咱们的范例将分为两个阶段:处理输入和局势化输出。关于输入部分,咱们领会测量站称号和测量值,并将它们存储在一个 std::unordered_map 中。

为了生成输出收尾,咱们整理了惟一称号并按字母限定对其进行排序,然后打印出最低、平均和最高测量值。

只剩下一个下里巴人的主函数,将这两个部分勾通在整个。

这个结束特别粗拙,但它有两个主要问题:一是不正确(暂时先岂论),二是速率特别慢。为此,咱们在以下三台设备上追踪性能:

● Fedora 39 上的 Intel 9700K

● Windows Subsystem for Linux(WSL)上的 Intel 14900K

● Mac Mini M1(8 核)

在 9700K 上用 clang 17 编译二进制文献,在 14900K 上用 clang 18 编译二进制文献,在 Mac Mini 上用 Apple clang 15 编译二进制文献。这三台设备都用了探讨的编译象征:

-O3 -march=native -g0 -DNDEBUG -fomit-frame-pointer

由于咱们主要善良的是相对性能的提高,因此测量收尾仅为相应设备上的最快运行技艺。

● 9700K(Fedora 39):132 秒

● 14900K(WSL Ubuntu):67 秒

● Mac Mini M1:113 秒

遗弃拷贝

关于第一步优化,咱们并不需要特殊用具:高性能代码最伏击的原则便是幸免过多拷贝,而咱们却意外间进行了广博拷贝。

当咱们处理一瞥数据时(记取,共有 10 亿行数据),会将测量站称号和测量值读入 std::string 中。这骨子上就产生了数据拷贝,因为当咱们从文献中读取数据时,文献内容依然在内存中的缓冲区了。

为了遗弃这个问题,咱们有几个选拔:改用非缓冲读取,手动处理 istream 的缓冲区;用更系统级的边幅,举例对文献进行内存映射——在本文中,咱们将采纳后者。

当咱们将文献进行内存映射时,操作系统会为文献内容分派地址空间,而数据仅在需要时才会读入内存。这么作念的克己是,咱们可以将整个文献视为一个数组;流毒是,咱们无法戒指刻下内存汉文献的哪些部分是可用的。

既然咱们用的是 C++,那么就用 RAII 对象来封装底层系统逻辑吧。

因为咱们将内存映射文献封装在 std::span 中,因此只需将 std::ifstream 换成 std::ispanstream 就能考证一切是否平素。

不外,诚然这可以确保一切平素,但并不成遗弃任何过多的拷贝。为此,咱们必须将输入处理从在 istream 上操作切换为将输入视为一个大 C 格调字符串。

咱们需要调整哈希映射以复旧异构查找,因为咫尺用 std::string_view 查找测量站,这会波及改革比较器和添加自界说哈希函数。

这一优化使咱们的处置决策运行速率大幅提高(暂时跳过 M1,因为 clang 15 不复旧浮点类型的std::from_chars)。

● 9700K(Fedora 39):47.6 秒(2.8 倍)

● 14900K(WSL Ubuntu):29.4 秒(2.3 倍)

分析情况

为了进一步优化处置决策,咱们必须分析哪些部分是主要瓶颈,这就需要一个性能分析用具。

至于分析用具的选拔,咱们必须在精度和低支出之间作念出均衡。在本文中,咱们将使用 perf:这是一个 Linux 性能分析用具,支出极低的同期能提供合理精度。

要记载性能分析,咱们必须在二进制文献中注入一些调试信息:

-fno-omit-frame-pointer # do not omit the frame pointer\n-ggdb3 # detailed debugging information

接着在 perf 用具下运行二进制文献:

perf record --call-graph dwarf -F999 ./binary

callgraph 选项允许 perf 期骗二进制文献中存储的调试信息,将初级函数包摄于正确的调用者。第二个选项会镌汰 perf 捕捉样本的频率;如若频率过高,可能会丢失一些样本。

然后,咱们就可以巡视性能分析了:

perf report -g 'graph,caller'

不外,如若咱们在刻下实施决策中运行 perf,只可得到一个信息量较为有限的性能分析。

可以揣度出,运行技艺的最大部分都奢华在 std::unordered_map 中,而其余操作都在初级函数中被合并了。举例,你可能会得出这么的论断:领会测量值只占 3%(std::from_chars 函数)——但这其实是错的。

由于咱们将悉数逻辑都放在了一个细巧的轮回中,这么的性能分析就很差。诚然这对性能来说有克己,但却十足失去了正在践诺的逻辑操作的兴致:

● 领会测量站称号;

● 领会测量值;

● 将数据存储到数据库中。

如若咱们将这些逻辑操作封装成单独的函数,性能分析的了了度将大幅提高。

可以看到,咱们奢华了 62% 的技艺将数据插入数据库,26% 的技艺领会测量值,以及 5% 的技艺领会测量站称号。

接下来应该处理哈希映射,但在此之前,咱们先处理一下数值领会问题,这也将开导咱们代码中一个历久存在的失实(四舍五入失实)。

虚伪的整数

输入包含 -99.9 到 99.9 领域内的测量值,并永久保留一位极少——这意味着,咱们开始处理的不是浮点数,因为测量值是定点数。

暗示定点数值的正确边幅,是将其暗示为整数,咱们可以手动领会(先用成功领会):

这种变化也会影响到记载的结构:

数据库插入可以保抓原样,但咱们可借此契机略微优化一下代码。

终末,咱们需要修改输出局势的代码。由于咫尺用的是定点数,是以咱们必须将存储的整数值诊治并四舍五入成浮点数。

这一优化开导了前边提到的四舍五入失实,并提高了运行时遵守(浮点运算速率较慢),且该结束也与 M1 Mac 兼容。

● 9700K (Fedora 39):35.5 秒(3.7 倍)

● 14900K(WSL Ubuntu):23.7 秒(2.8 倍)

● Mac Mini M1:55.7 秒(2.0 倍)

自界说哈希映射

范例库中的 std::unordered_map 以速率慢而著明,这是因为它使用了节点结构(施行上是节点链表数组)。按理说,咱们可以改用平面映射(来自 Abseil 或 Boost),但这会不服 1brc 挑战赛的端正——即不容使用外部库。

更伏击的是,咱们的输入也特别有限。关于 10 亿札记载,咱们唯有 1 万个惟一键,因此射中率会极高。

由于咱们只可用 1 万个惟一键,因此可用基于 16 位哈希的线性探伤哈希映射,成功索引静态数组。当碰到碰撞时(两个不同站点映射到探讨的哈希/索引)时,咱们将使用下一个可用的槽位。

这就意味着,在最坏的情况下(悉数站点都映射到探讨的哈希/索引),咱们最终会进行线性复杂度查找。然则,这种情况极少发生,关于使用 std::hash 的输入示例,咱们最终会碰到 500 万次碰撞,即 0.5%。

这一变动,带来了相配大的速率提高:

● 9700K(Fedora 39):25.6 秒(5.1 倍)

● 14900K(WSL Ubuntu):18.4 秒(3.6 倍)

● Mac Mini M1:49.4 秒(2.3 倍)

微调代码

以上,咱们依然梳理了高头绪的优化道路,接下来是技艺真切挖掘并微调代码中的要害部分了。先让咱们回来一下咫尺的情况。

咱们可以在哈希(17%)和整数领会(21%)方面进行一些初级优化。微调代码的正确用具是基准测试框架,咱们将结束几个预计函数的版块,并把收尾互比较较。在本文中,咱们选拔使用 Google Benchmark。

(1)领会整数

刻下版块的整数领会(专诚)写得很差,有过多的分支。

由于数值可以短至三个字符,咱们无法有用地期骗宽提示(AVX)。在这种情况下,惟一能遗弃分支的边幅便是通过查找表。

当咱们领会数字时,唯有两种可能的情况(忽略象征):

● 碰到一个数字:累加器乘以 10 并加上数字值。

● 碰到一个非数字:累加器乘以 1 并加上 0。

咱们可以把这个流程编码为一个二维的、在编译时生成的数组,其中包含 char 类型悉数 256 个值的信息。

如若咱们把这两个版块插入到微基准测试中,就会得到一个特别明确的收尾。

但很可惜,咱们并不成把这两个版块成功插入到 Google Benchmark 中。咱们的结束是一个进步 5 个字符的细巧轮回,因此对布局特别敏锐,可以用 LLVM 象征来对皆这些函数。

-mllvm -align-all-functions=5

不外即使这么,收尾也会有很大波动(高达 40%)。

(2)哈希处理

在进行哈希处理时,咱们有两个优化的契机。

咫尺,咱们是先领会站点的称号,然后在 lookup_slot 入网算哈希值,这意味着要对数据进行两次遍历。此外,咱们打算的是 64 位的哈希值,但施行上只需要 16 位的哈希值。

为了减少整数领会时碰到的问题,咱们将把领会合并为一个门径,生成一个站点称号的 string_view、一个 16 位哈希和定点测量值。

咱们可以用一个粗拙公式来打算自界说的 16 位哈希,依赖无象征溢出而不是取模。

这将大幅加速打算速率(同期具有合理的显露性)。

当咱们将这个转换纳入处置决策中后,就会得到举座速率的提高:

● 9700K (Fedora 39):19.2 秒(6.87 倍)

● 14900K(WSL Ubuntu):14.1 秒(4.75 倍)

● Mac Mini M1:46.2 秒(2.44 倍速)

开释线程

如若咫尺磋商一下性能分析,就会发现咱们依然达到了可行的极限。

因此,下一步天然是并行化咱们的代码。最粗拙的边幅是将输入分红约略探讨的块,在单独的线程中处理每个块,然后合并收尾。咱们可以扩张 MappedFile 类型,以提供分块考查。

这么,咱们就可以粗拙地在每个线程中按块运行现存代码。

这么的优化恶果相配可以。

以下是最好收尾。请注意,这些仅仅相对比较下的最好运行收尾,而不是严格的基准测试。

● 9700K(Fedora 39):2.6 秒(50 倍)(在 8 个线程上)

● 14900K(WSL Ubuntu):0.89 秒(75 倍)(在 32 个线程上)

● Mac Mini M1:10.2 秒(11 倍)(在 24 个线程上)

(1)处理非对称处理速率

9700K 的缩放特别任性,但这是因为这款处理器有 8 个不复旧超线程的探讨内核。一朝咱们升级到 14900K,架构中的性能和遵守内核就会变得复杂得多。

如若咱们粗拙地将输入分红探讨的块,遵守中枢会逾期于主中枢,拖慢举座运行速率。因此,咱们不再将输入分红每个线程一个块,而是让线程按需恳求块。

以及 MappedFile 中相应的 next_chunk 边幅。

这么咱们就可以从 14900K 中挤出终末一点性能:

● 14900K(WSL Ubuntu):0.77 秒(87 倍)(在 32 个线程上)

论断

对比领先结束的性能,咫尺咱们依然提高了 87 倍——你可能会问:这值得吗?

那要看情况了。写这篇著述我花了很长技艺,也奢华了广博元气心灵,阑珊是用微基准测试时出现的对皆问题是一个很大的贫穷。如若我正在优化一段出产代码,我可能会留步于基本优化这个关节。微调代码的恶果可能可以,但要插足广博技艺,况兼这些优化在当代架构上的显露性很差。

终末,本文中波及的竣工源代码可在这个 GitHub 存储库中赢得:https://github.com/HappyCerberus/1brc。