Julia 中的 One Billion Row Challenge
原文towardsdatascience.com/the-one-billion-row-challenge-in-julia-bdd19cde58d5?sourcecollection_archive---------9-----------------------#2024-06-05如果数据科学家决定接受这个任务他们能学到什么https://medium.com/vikas.negi10?sourcepost_page---byline--bdd19cde58d5--------------------------------https://towardsdatascience.com/?sourcepost_page---byline--bdd19cde58d5-------------------------------- Vikas Negi·发布于 Towards Data Science ·阅读时间 8 分钟·2024 年 6 月 5 日–https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/dded10279fcf25261adbea5aecf60745.png图片来源Indira Tjokorda 在 Unsplash今年早些时候Gunnar Morling 启动了 One Billon Row Challenge自那以后它获得了很大的关注。虽然最初的挑战是使用 Java 完成的但令人惊叹的开源社区随后分享了多种编程语言下的精彩解决方案。我注意到使用 Julia 的人并不多至少没有公开分享过结果所以决定通过这篇文章分享我自己的尝试。我常常想一个问题这个挑战对数据科学家有什么价值除了做一个有趣的练习之外我们能学到更多的东西吗毕竟这个挑战的目标是“简单地”解析一个大型虚拟数据文件计算基本统计量最小值、最大值和均值并以特定格式输出数据。这对于大多数数据科学家通常从事的项目来说可能并不是一个现实的情况。嗯问题的一个方面涉及数据的大小与可用 RAM 之间的关系。在本地工作笔记本电脑或桌面时对大多数人来说难以将所有数据一次性加载到内存中。因此处理比内存更大的数据集成为一项必要的技能这在原型化大数据管道或进行大数据分析/可视化任务时可能会派上用场。原始挑战的规则也指出应该避免使用外部库/包。这迫使你思考新颖的解决方案并为学习语言本身的细节提供了一个迷人的机会。在接下来的文章中我将分享两种方法的结果——使用基础的 Julia 以及通过外部包进行的结果。通过这种方式我们可以比较每种方法的优缺点。所有实验均在配备 AMD Ryzen 9 5900X12 核心24 线程、32 GB RAM 和三星 NVMe SSD 的桌面上进行。Julia 1.10.2 运行在 LinuxElementary OS 7.1 Horus上。所有相关代码可以在这里找到。请注意这里的性能也与硬件有关因此如果你决定在自己的系统上运行脚本结果可能会有所不同。前提条件推荐使用最近发布的Julia版本例如 1.10。对于那些想使用笔记本的人上述仓库还包含一个 Pluto 文件使用该文件需要安装Pluto.jl。挑战的输入数据文件是每个人独特的必须使用这个 Python 脚本生成。请记住该文件大约有 15 GB。python3 create_measurements.py1000000000此外我们还将使用BenchmarkTools.jl包来运行基准测试。请注意这不会影响挑战只是用来收集适当的统计数据以衡量和量化 Julia 代码的性能。使用基础的 Julia输入数据文件measurements.txt的结构如下这里只显示前五行attipūdi;-49.2Bas Limbé;-43.8Oas;5.6Nesebar;35.9Saint George’s;-6.6该文件包含十亿行也称为记录或行。每一行都有一个站点名称后跟分隔符然后是记录的温度。唯一的站点数量最多可达 10,000。这意味着同一个站点会出现在多行中。因此我们需要收集文件中所有不同站点的所有温度数据然后计算所需的统计信息。很简单对吧让我们从简单但慢慢来开始我的第一次尝试是简单地逐行解析文件然后将结果收集到一个字典中每个站名作为键温度作为Float64类型的向量添加到值中。我预期这会很慢但我们的目标是获得基准性能的数字。一旦字典准备好我们就可以计算所需的统计数据所有数据处理的输出需要以某种特定格式显示。以下函数实现了这一点由于这个实现预计需要较长时间我们可以通过仅运行一次time来进行简单的测试timeget_stations_dict_v2(measurements.txt)|calculate_output_v3|print_output_v1output omittedforbrevity526.056399seconds(3.00G allocations:302.881GiB,3.68%gc time)我们这个简单的实现大约需要 526 秒即约 9 分钟。虽然它确实很慢但也并没有那么糟糕升级一下——进入多线程与其一次读取输入文件的一行不如尝试将文件拆分成多个块然后并行处理所有块。Julia 让实现并行for循环变得非常简单。然而在这样做时我们需要采取一些预防措施。在进入循环之前我们首先需要弄清楚如何将文件拆分成多个块。可以通过使用内存映射来读取文件。然后我们需要确定每个块的start和end位置。需要注意的是输入数据文件中的每一行以换行符结尾其字节表示为0x0a。因此每个块应该在该字符处结束以确保在解析文件时不出错。以下函数将块的数量num_chunks作为输入参数然后返回一个数组其中每个元素都是内存映射的块。由于我们从不同的块中解析站点和温度数据我们还需要在最后将它们合并。每个块将首先被处理为字典如前所示。然后我们按如下方式合并所有块现在我们知道如何将文件拆分成多个块以及如何在最后将各个块解析出的字典合并。然而只有在我们能够并行处理这些块时才能获得所需的加速效果。这可以通过for循环来实现。请注意Julia 应该使用多线程启动julia -t 12否则这个解决方案不会产生任何影响。此外我们现在希望运行一个正式的统计基准测试。这意味着该挑战应该执行一定次数然后我们可以可视化结果的分布。幸运的是所有这些都可以通过BenchmarkTools.jl轻松完成。我们将最大样本数限制为 10整个运行的最大时间设为 20 分钟并启用垃圾回收将在样本之间释放内存。所有这些都可以在一个脚本中整合。请注意输入参数现在是文件名fname和数据块数num_chunks。以下是基准测试结果和所用输入。请注意我们在这里使用了 12 个线程。juliaThreads.nthreads()12juliaARGS[measurements.txt,48]2-element Vector{String}:measurements.txt48https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/72ec4e552a082a0938866ae4f1e12de3.png12 个线程数据块数 48图片由作者提供多线程显著提升了性能我们现在的时间大约降至 2 分钟多一点。接下来我们看看还能做什么改进。避免存储所有温度数据到目前为止我们的方法是存储所有的温度数据然后在最后确定所需的统计值最小值、平均值和最大值。然而我们已经可以在解析输入文件的每一行时实现相同的功能。每当找到一个新的值时如果是更大的值则更新最大值或更小的值则更新最小值我们就替换现有的值。对于平均值我们将所有的值相加并保持一个单独的计数器用于记录某个站点的温度出现了多少次。总体而言我们的新逻辑如下所示合并所有结果来自不同数据块的函数也需要相应地进行更新。让我们进行一个新的基准测试看看这个改变是否能改善时间表现。https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/03e629680c5f7ea6a82d412a8dfd74b9.png12 个线程数据块数 48图片由作者提供中位数时间似乎有所改善但改进幅度很小。不过依然算是一个胜利更多性能提升我们之前的逻辑用于计算和保存温度的最大值和最小值可以进一步简化。此外按照这个Julia Discourse 帖子中的建议当解析站点名称和温度数据时我们可以使用视图通过view。这一点在 Julia 性能手册中也有讨论。由于我们在解析每一行时使用了切片表达式view帮助我们避免了分配和复制的开销。其余的逻辑保持不变。现在运行基准测试得到以下结果https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/f521124dfe4e634f7f0478fcbea99c64.png12 个线程数据块数 48图片由作者提供哇我们成功地将时间缩短到了接近一分钟。看起来切换到不同的视图确实有很大的影响。也许还有更多的调整可以进一步提高性能。如果你有任何建议请在评论中告诉我。使用外部包仅限使用基础 Julia 进行限制是很有趣的。然而在实际应用中我们几乎总是会使用包因此可以利用现有的高效实现来执行相关任务。在我们的例子中CSV.jl并行解析文件和 DataFrames.jl执行groupby和combine将会派上用场。以下函数执行以下任务使用Mmap读取大文件将文件分割成预定义数量的块遍历这些块使用CSV.read将 12 个线程传递给ntasks并行读取每个块到 DataFrame 中。使用 DataFrame 的groupby和combine获取每个车站的结果将所有 DataFrame 连接起来合并所有分块的结果循环结束后执行一次groupby和combine以获取所有车站的最终结果集。现在我们可以以与之前相同的方式运行基准测试。https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/72da5f5c1ebda12a8453bd0ddd76df4a.png12 个线程分块数 48使用外部包图片由作者提供使用 CSV.jl 和 DataFrames.jl 的性能相当不错尽管比我们使用基础 Julia 实现的要慢。在实际项目中这些包是数据科学家工具包中的重要组成部分。因此探索使用这种方法是否可以进行进一步优化将是很有趣的。结论在这篇文章中我们通过 Julia 解决了十亿行挑战。从一个非常简单的实现开始花费大约 10 分钟我们通过对代码的迭代修改成功地大幅提升了性能。最优化的实现可以在约 1 分钟内完成挑战。我确信仍然有改进的空间。作为额外的收获我们学到了一些宝贵的技巧如何处理超出内存的数据集。当使用 Julia 进行大数据分析和可视化时这可能会派上用场。希望你觉得这个练习有用。感谢你的时间可以通过 LinkedIn 与我联系或者访问我的 Web 3.0 网站。参考文献www.morling.dev/blog/one-billion-row-challenge/docs.julialang.org/en/v1/manual/performance-tips/index.html#man-performance-annotations
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2625477.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!