bzip2 crate 从 C 切换到 100% rust

今天,我们发布了 bzip2 版本 0.6.0,该版本默认使用我们对 bzip2 算法的 rust 实现 libbz2-rs-sys。现在,bzip2 crate 速度更快,更易于交叉编译。

如果你有 C 项目可以从这些改进中获益,libbz2-rs-sys crate 也可以作为 C 动态库来构建。

为什么要这样做?

为什么要花时间研究这个在当今几乎不再使用的 90 年代算法?问题是,许多协议和库仍需支持 bzip2 以符合其规范,因此许多项目在依赖树的深层仍依赖于 bzip2。我们利用在 zlib-rs 中的经验对 bzip2 实现进行了现代化改造。

我们之前在“使用c2rust转换bzip2”中详细介绍了libbz2-rs-sys的实现细节,现在让我们看看这项工作的优势。

性能提升

元素周期表我们的 Rust 实现通常优于 C 实现,尽管在某些情况下,我们的性能与 C 实现相当。我们尚未发现任何性能明显较慢的情况。

在压缩方面,我们的实现明显更快。对于 bzip2,level 参数表示使用的内存大小。它对性能影响不大,且对于 sample3.ref 文件,级别 1 已经分配的内存超过文件大小,因此更高级别无关紧要。

名称 c(CPU 周期) rust(CPU 周期) Δ
sample3.ref (level 1) 38.51M ± 77.03K 33.53M ± 90.52K -14.87%
silesia-small.tar (level 1) 3.43G ± 2.06M 3.00G ± 6.31M -14.30%
silesia-small.tar (level 9) 3.47G ± 4.86M 3.17G ± 4.43M - 9.66%

在解压缩方面,性能差异稍大,但我们仍然看到整体速度显著提升。

名称 c(CPU 周期) rust(CPU 周期) Δ
sample3.bz2 2.53M ± 30.08K 2.42M ± 8.95K - 4.48%
sample1.bz2 9.63M ± 40.44K 8.86M ± 10.64K - 8.63%
sample2.bz2 20.47M ± 55.28K 19.02M ± 36.13K - 7.67%
dancing-color.ps.bz2 87.46M ± 481.02K 83.16M ± 548.86K - 5.17%
re2-exhaustive.txt.bz2 1.89G ± 12.29M 1.76G ± 12.64M - 7.65%
zip64support.tar.bz2 2.32G ± 12.09M 2.11G ± 15.42M -10.00%

需要注意的是,在我们的 macOS 基准测试机器上,有时会看到解压速度较低的数值。我们不确定导致这种差异的原因,而且在 macOS 上以详细方式测量性能证明是困难的(例如,没有像 perf 这样的工具可以自动化性能跟踪并使其正常工作)。

启用交叉编译

具有 C 依赖关系的 Rust 项目的交叉编译通常可以直接使用(因为 cc crate 会尝试处理它),但当无法使用时,错误可能难以调试。同样,链接到系统库也会导致令人困惑且难以重现的问题。

对于 bzip2 来说,编译到 WebAssembly 一直是一个问题。通过删除 C 依赖项并改用 Rust 代码,编译 C 的复杂性就消失了:交叉编译可以正常工作。此外,在 Windows 或 Android 上进行构建也可以正常工作。除了为用户提供更好的体验外,此更改还大大简化了维护工作。

符号不会被导出(默认情况下)

使用 C 依赖项意味着其符号会被导出(以便 rust extern 块能够找到它们)。当另一个依赖项声明相同的符号时,导出的名称可能会发生冲突。

默认情况下,libbz2-rs-sys 不会导出其符号,这意味着它永远不会与其他依赖项发生冲突。如果您的 rust 项目确实需要输出符号,则有一个功能标志可用于启用符号导出。

使用 miri 运行测试

编写高性能的 bzip2 实现需要一些不安全的代码,而在 Rust 中复制 C 接口需要更多代码。幸运的是,我们能够在 MIRI 下运行该代码。

更重要的是,使用 bzip2 的更高层次的库或应用程序现在也可以在 MIRI 下运行了。

审计

审计发现了一个逻辑错误(一个偏移量错误),并修复了我们模糊测试工具的一些限制。除此之外,没有发现其他重大问题(太棒了!)。我们特别感谢来自Radically Open Security的审核人员,尤其是Christian Reitter,分享了他们的模糊测试经验。完整的审计报告可在此处查看这里

结论

bzip2 crate 现在运行速度更快了。您可以放心使用,无需再担心这个问题。

本文文字及图片出自 bzip2 crate switches from C to 100% rust

共有 172 条讨论

  1. Trifecta Tech 的实现取代 Linux 发行版中使用的“官方”实现(该实现自 2019 年以来未发布上游版本)的可能性有多大?

    Fedora 近期已用 zlib-ng 替换了原有的 Adler zlib 实现,因此这种情况并非不可能。只需确保新实现与原实现的 C ABI 兼容即可。

    1. 如果自2019年以来没有上游发布,难道这不意味着该实现已经……完成了吗?也许没有更多的 bug 需要修复,也没有更多的功能需要添加。在这种情况下,我看不出来有什么问题。

      1. 10-15%的压缩速度提升和5-10%的解压缩速度提升,难道不是一个非常不错的“功能”吗?

        > […] 这难道不意味着实现已经……完成了?

        我认为这并不一定意味着,例如,自2019年以来没有发布版本的所有项目都已完成?很可能其中大多数只是被放弃了?

        另一方面,一个完成的实现确实可能是没有发布版本的可能解释。

        在这种具体情况下,他们的问题跟踪器上还有几个未解决的 bug。这表明该项目尚未完成。

        参考:https://sourceware.org/bugzilla/buglist.cgi?product=bzip2

        1. “10-15%的压缩速度提升和5-10%的解压速度提升,难道不是一个很棒的‘功能’吗?”

          虽然可以通过优化代码使其运行速度提升10-15%,但如果这牺牲了代码的可读性,那么这样的“功能”如今会被拒绝。将现有代码库翻译成一种使事情变得更困难¹的语言(因此)拥有(并且很可能将继续拥有)更少愿意在此工作的工程师,这在我看来与应用影响可读性的优化非常相似。

          ¹ …而且不仅如此。我在其他评论中已经描述了作为软件开发工具时我看到的某些问题,这里 https://news.ycombinator.com/item?id=31565024 这里 https://news.ycombinator.com/item?id=33390634 和这里 https://news.ycombinator.com/item? id=42241516

    2. Ubuntu 正在使用 Rust sudo,所以这绝对是可能的。

    3. 他们确实提供了兼容的 C ABI。有人“只需”完成相关工作即可实现。

      1. 1) 这是一个很酷的项目,我希望他们成功。如果这些工具很快成为默认工具,那就太棒了。

        2) 我认为 MIT 许可证是一个错误。这些工具通常是 GNU 工具的克隆,因此,参考 GNU 源代码的原始语言,然后在 Rust 中重新实现它,显然是应该做的。但将 GPL 许可的代码移植到 MIT 许可的项目是不允许的。相反,这些实用程序必须从头重新实现,这似乎是在浪费精力。我对将 GNU 源代码移植到 Rust 的工作很感兴趣,但我不感兴趣从头重写它们,所以我没有为这个项目做出贡献。

        1. 许多人讨厌 GNU 工具中所谓的冗余——对他们来说,从头重写是一个功能,而不是一个 bug。

        2. 提到你怀疑这种情况发生,除了作为对他们的挖苦,还有什么意义?

      2. 我希望有些工具也能得到改进。

        像 ripgrep 和 tokei 这样的工具与它们替换的工具(分别是 grep 和 cloc)相比,性能提升简直令人难以置信。

        1. 我之前没听说过 tokei,所以我在自己的一个小项目上试用了它。

          Tokei 在 cloc 打印帮助文本之前就完成了。我写这篇帖子所花的时间比 `cloc .` 统计项目中所有文件所需的时间还要短,可能是因为它不知道要忽略 `target/` 目录。

          1. > Tokei 在 cloc 打印帮助文本之前就完成了。

            cloc是一个Perl脚本,因此它有解释器的启动时间。

        2. 我非常讨厌人们将他们的工具称为某种核心标准的“替代品”,而这种标准已经很好地运行了几十年。

          ripgrep是一个优秀的工具。但它不是grep的替代品。而且永远不应该成为替代品。

          1. GNU 工具集是 BSD 工具集的替代品,而 BSD 工具集又是对原始 AT&T 工具集的替代。每次替代都会添加新功能和改进,而每次有人抱怨它们没有更紧密地遵循被替代的工具集。以 grep 为例,曾经有 egrep 和 fgrep 等新版本,它们添加了超出标准 grep 的功能,但这些功能最终被合并到“标准”grep(GNU 或 BSD)中。如果我们坚持使用标准,我们现在可能仍在使用 Bourne 壳。GNU 实用程序已经存在了很长时间,现在感觉已经成了标准,但我很高兴我们正在进入命令行实用程序创新的新阶段。这并不是从 Rust 开始的——新一代搜索实用程序是从 ack(Perl)开始的,然后是 ag(C)。

            1. > 我们仍然会使用 Bourne shell

              请原谅我的无知,但 Bash 有什么问题吗?我仍在所有服务器和工作站上使用它,我不断为它编写脚本,其中一些相当复杂。它不是一个过时的项目,对我来说它看起来像是一个主流的 shell。我错了嗎?

              更新:是的,我现在意识到这是关于原始的 Bourne 壳,而不是 Bash。

              1. Bash不是Bourne,这就是重点。Bash是Bourne Again Shell,一个在GNU生态系统中改进并取代Bourne shell的shell。现代Bash相较于原始的Bourne shell有了巨大改进,我确信你每天都在使用Bash的各种功能,如果有人强迫你使用真正的Bourne shell,你一定会非常恼火。

                1. 啊,对!我确实记得原始的Bourne Shell。但我不想回到使用它。不过,如果能让我回到年轻时的状态,我可能会同意。

              2. Bash不是Bourne Shell(sh)!它是替代品(Bourne Again Shell)。但有趣的是,这个替代品已经深入人心,以至于人们认为它就是原版。

            2. > 这是对原版的替代

              你说的有道理。我同意。

              “用Z重写X”是一种糟糕的营销策略。这让我立刻就讨厌这个工具和它的作者了。(喜欢 Rust,讨厌这种感觉)。

              1. 这是一个 Rust crate,旨在作为 Rust c 包装 crate 的原生 Rust 替代品。它更快,更易于在 Rust 项目中链接。

                如果你不使用“Rust”这个词,你怎么能告诉别人你制作了一个更好的 Rust crate 呢?

                1. 你会说“为 Rust 重写”,而不是“用 Rust 重写”。

                  1. 这是一个依赖于 C 的 Rust crate,现在真的“用 Rust 重写”了。

                2. 为什么不花精力来加快真正的 zlib 的速度呢?这样整个世界就能真正地运转得更快一些了。

                  Rust 的人声称它与 C 二进制文件具有出色的互操作性。那么为什么还需要重写呢?

                  1. 也许是因为在一种无需时刻担心引入错误的语言中,更容易实现速度的提升?也许在一种具有更现代工具的语言中,开发更容易?

                    互操作性是双向的,目前依赖 C 库的每个人都可以将其替换为 Rust 库,并获得同样的好处。

              2. 为什么讨厌它?这是一个真诚的问题。当你重写某件事时,你需要以某种方式证明这种努力是合理的。GNU coreutils 最初是“BSD 实用程序,但采用 GPL 许可!”。

                1. 因为重新实现的作者跳过了设计工具的所有复杂性,直接进入有趣的部分(即编码),然后他们就可以称自己为一个著名的基础设施工具的作者了。

                  比较一下“我用 rust 编写了一个 setuid() 包装器”和“我是 sudo-rs 的作者”。

          2. 而且永远不应该被取代。

            你所说的那些“核心标准”并不是凭空出现的。它们是通过竞争、淘汰和取代旧的“核心标准”而产生的,而这些旧标准曾被许多人强烈主张不应被取代。在我刚开始职业生涯时,有经验的人告诉我,不应依赖GNU工具的特性,因为它们远非普遍存在,而且很可能不会安装在我将要工作的绝大多数系统上。

            1. 没错,这一点至今仍成立:GNU工具在主流计算机上仍未普及。我指的不是那台仍在运行的Ultrix服务器。例如,最新版本的macOS自带BSD tar工具,所以。

    4. 我简单查看了一下,发现已经有了 cargo-c 配置,这很好,但目前它使用了不同的命名空间,因此不会被 C 程序自动识别为 `libbz2`:

      https://github.com/trifectatechfoundation/libbzip2-rs/blob/8

      我对 bzip2 的符号不够熟悉,无法对 ABI 兼容性发表意见。

      我有一个小型项目用于探索此类问题,但要抽出足够时间维护GNU操作系统的实现较为困难。不过我欢迎提交pull请求:

      https://github.com/kpcyrd/platypos

    5. > 你只需提供与原版兼容的C ABI即可。

      这与动态链接有什么关系?目前的 Rust 工具链不是要求静态链接吗?

      1. 下面的评论者混淆了两件事——Rust 二进制文件可以动态链接,但由于 Rust 没有稳定的 ABI,因此无法像 C 一样在不同编译器版本之间进行动态链接。因此,实际上,一切都是静态链接的。

        1. Rust 的稳定 ABI 是 C ABI。因此,您可以完全动态链接 Rust 编写的二进制文件和/或 Rust 编写的共享库,但接口必须是纯 C。(这还可以为您提供大多数其他编程语言的免费 FFI。)您可以使用轻量级的静态链接包装器在两侧的 Rust 和 C 接口之间进行转换,并保持一些实际的安全性。

          1. > 但接口必须是纯 C 接口。(这同时也为你提供了与大多数其他编程语言进行自由 FFI 的能力。)

            简单,但不免费。在许多语言中,提供 C 接口需要额外的工作。字符串可能需要转换为以零终止的字节数组,可被垃圾回收的内存可能需要被锁定,结构体可能需要转换为 C 结构体布局等。

        2. 具体来说,rust 的依赖项是静态链接的。从 rust 动态链接任何具有 C ABI 的东西非常容易。

        3. 与 C++ 世界一样,在苹果和微软的生态系统中,发布二进制 C++ 库是一种常见的做法,即使它依赖于编译器版本。

          这就是为什么苹果在经历了 C++ 和 Objective-C 之后,如此重视在 Swift 上采用更好的 ABI 方法。

          而在微软方面,你会注意到,维克多·丘拉(Victor Ciura)在 Rust 会议上的所有演讲都涉及 ABI,这是微软在采用 Rust 时关注的一个关键点。

        4. 静态链接会生成更小的二进制文件,并允许进行链接时优化。

          1. 静态链接并不会生成更小的二进制文件。你实际上是将库中的符号直接添加到可执行文件中,而非仅仅提及这些符号并让动态链接器在运行时自动映射它们。

            动态二进制文件加上动态库的总大小可能大于一个静态链接的二进制文件,但对于更多静态二进制文件(2、3 或数百个)是否成立,取决于应用程序使用这些库的范围。常见的做法是将某些大型库仅进行动态链接,并在构建过程中采取特殊措施,将特定库构建为共享对象,同时让可执行文件通过位置相关的 RPATH(使用 $ORIGIN 特性)进行链接,以避免在大量二进制文件中因额外符号而导致的文件大小膨胀。

            1. 静态链接在打包依赖项时确实会生成更小的二进制文件。你混淆了两个概念——静态链接与动态链接,以及打包依赖与共享依赖。

              它们常被混淆是因为静态链接无法使用共享依赖,而动态链接库的打包在开源 Linux 软件中并不常见。但在 Windows 或 Linux 上的商业软件中则非常常见。

              1. 你知道页面缓存的工作原理吗?静态链接会使其失效。因此,3000个进程不会共享相同的libc页面,而是必须加载3000次。

                1. 有点离题。但确实,操作系统保证提供非常常用的库(如libc)以便共享是个好主意。

                  Mac 就是这样做的,Windows 基本上也这样做。Linux 曾尝试通过 Linux 标准基础(Linux Standard Base)实现这一点,但从未真正成功,几年前就放弃了。因此在 Linux 上,如果你想创建一个真正可移植的应用程序,基本上只能依赖系统提供非常旧版本的 glibc。

                  1. 标准库就是整个发行版 🙂

                    与旧版 Linux 发行版相比,这根本不是公平的比较,因为 OS X 肯定无法运行任何旧版软件……记得他们已经放弃了 Rosetta、Rosetta 2、32 位支持、OpenGL……(列表继续)。

                    我也不认为你可以指望 Windows XP 运行 Windows 11 的二进制文件。

                    所以我无法理解为什么你认为这在 Linux 上是完全合理的期望,而其他任何操作系统都从未支持过这一点。

                    愿意解释一下吗?

                2. 你可以继续静态链接你自己的所有代码,但动态链接 libc/其他系统依赖项。

                    1. 我很好奇那些完全否定现实的人脑子里在想什么。他们是否希望别人说:“好吧,我想你一定是正确的,而宇宙是错误的”?他们是否只是想贬低整个真理的概念?

                      [如果有人对你的说法感到困惑,是的,当然,这在 Rust 中是可行的]

                    2. 你可以在你机器上运行 ldd 命令来检查你当前拥有的任何用 rust 编写的二进制文件吗?

                      我迫不及待地想要知道结果!

                    3. 我的意思是,当然可以,但你的观点是什么?

                      这是 nu,一个 Rust 中的 shell:

                          $ ldd ~/.cargo/bin/nu
                              linux-vdso.so.1 (0x00007f473ba46000)
                              libssl.so.3 => /lib/x86_64-linux-gnu/libssl.so.3 (0x00007f47398f2000)
                              libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007f4739200000)
                              libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f473b9cd000)
                              libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f4739110000)
                              libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4738f1a000)
                              /lib64/ld-linux-x86-64.so.2 (0x00007f473ba48000)
                              libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f473b9ab000)
                              libzstd.so.1 => /lib/x86_64-linux-gnu/libzstd.so.1 (0x00007f4738e50000)
                      

                      以下是 Debian 版本的 ash,一个用 C 语言编写的 shell:

                          $ ldd /bin/sh     
                              linux-vdso.so.1 (0x00007f88ae6b0000)
                              libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f88ae44b000)
                              /lib64/ld-linux-x86-64.so.2 (0x00007f88ae6b2000)
                    4. 看来我对从 rust 链接 C 库的看法是错误的。

                      RAM 需求增加和不断重建的问题仍然非常现实,只是由于动态链接 C,问题稍微减轻了一些。

                    5. 如果你在第一段就停下来,这会是一篇很好的帖子。

                      你的第二段要么是对静态链接和动态链接差异的无意义观察,要么也是错误的。不确定你的意图是什么。

                    6. 为什么事实会让你感到冒犯?

                    7. 我现在真的很好奇,是什么让你如此确信它会完全静态链接?

                    8. 我认为人们经常说 Rust 只支持静态链接,所以他可能推断它无法与任何东西动态链接。

                      此外,Go 在 Linux 上确实会生成完全静态的二进制文件,因此,错误地猜测 Rust 也会这样做至少是合理的。

                      不过绝对不应该如此自信!

                    9. Go在Linux上是否这样做取决于你导入的内容。例如,如果你调用`os/user`中的内容,除非你使用`-tags osusergo`构建,否则你会得到一个动态链接的二进制文件。`net`模块也有类似的情况。

          2. 静态链接会生成巨大的二进制文件,它允许你使用LTO,但实际能进行的优化程度受限于你的内存。静态链接还会导致整个存档需要不断重新构建。

            1. 你不需要 LTO 来缩小静态二进制文件(尽管 LTO 可以做到),编译器标志中的 `-ffunction-sections -fdata-sections` 结合链接器标志中的 `–gc-section`(或等效选项)即可实现。

              这样你可以获得小巧且汇编代码可读的二进制文件。

        5. C++ 二进制文件应该做同样的事情。外部使用 C ABI。内部静态链接 Rust stdlib 或 C++ stdlib。

          1. 从 C++ 项目导出 C API 以在另一个 C++ 项目中使用非常痛苦。这就是你得到 COM 的方式。

            (实际上,我认为它比 C++ 稍早一些?

            1. > 这就是 COM 的由来。(实际上,COM 的出现比 C++ 稍早一些,我认为?)

              不。C++ 诞生于 1985 年(https://en.wikipedia.org/wiki/C%2B%2B),COM 诞生于 1993 年(https://en.wikipedia.org/wiki/Component_Object_Model)

            2. COM 其实还是不错的。如果你想要另一个对象系统,可以试试 GObject,它与 Rust、C-+、Python、JavaScript 以及许多其他东西都能很好地配合。

          2. OWL、MFC、Qt、VCL、FireMonkey、AppFramework、PowerPlant…

            很多都不支持,尤其是在苹果和微软平台上,因为它们在动态链接器和C++编译器上一直更倾向于其他方法来实现基础UNIX支持。

      2. Rust 无法与 Rust 进行动态链接。它可以与 C 进行动态链接,也可以被 C 动态链接——如果你将两者结合在一起,你可以作弊,但即使两边都是 Rust,你处理的仍然是 C,而不是 Rust。

        1. Rust 完全可以动态链接到 Rust 库。由于没有稳定的 ABI,因此必须使用相同的编译器版本,但仍然可以动态链接。

        2. 可以将其视为两个用 Rust 编写的 IPC“服务器”,它们恰好使用 C ABI 接口作为通信协议。

      3. 编号:https://doc.rust-lang.org/reference/linkage.html#r-link.dyli…

      4. Rust 允许你生成动态 C 链接库。

        使用 crate-type=[“cdylib”]

      5. 如果目标是 C ABI,动态链接没有问题。

      6. Rust 导入 Rust 必须进行静态链接,没错。不过,您可以将 Rust 静态链接到其他库链接的动态库中!

      7. 你可以使用 Rust 与 C ABI 进行动态链接。这意味着要使用 `unsafe` 关键字——也称为“相信我,兄弟”。直接静态链接到 Rust 源代码意味着它由编译器检查,因此无需使用 unsafe。

    6. 我等着他们遇到像 awk、sed 和 grep 这样的难点。

      1. ripgrep 是最好的 grep 替代品之一,甚至可能是最好的,也是最著名的 Rust 项目之一。

        我不知道有没有与 sed 相当的项目,但我猜这很容易实现,因为 Rust 对正则表达式有很好的支持(参见 ripgrep),而且 90% 以上的 sed 使用都是搜索和替换。其他命令看起来也不难实现,而且由于它们的使用频率不高,优化这些命令的优先级较低。

        我对 awk 不太了解,它是一个完整的编程语言,但我想实现它绝非不可能的任务。

        现在真正的难点在于实现与 GNU 版本完全兼容的替代工具,虽然这很好,但并非严格必要。例如,Busybox 非常流行,甚至在设备数量上可能比 GNU 更受欢迎,它有自己(可能简化的)版本的 grep、sed 和 awk。

  2. 我用这个 crate 来处理数百 TB 的 Common Crawl 数据,我很欣赏它的速度提升。

    1. 为什么在这里使用 bz2?难道一次性转换为 zstd 不会更快吗?据我所知,在更高压缩级别下,zstd 在所有指标上都优于 bzip2。

      1. Common Crawl 以 bz2 格式提供数据。确实,我使用 ZFS 将中间数据存储为 zstd。

      2. 这假设你需要多次处理数据。

    2. 这些数据是否以种子文件形式提供?

    3. 是的,我来这里就是想说压缩速度提升14%已经相当不错了!

      1. bzip2(特别是其并行实现)在压缩方面已经相对具有竞争力。解压缩时间是其落后之处,因为基于lz77的算法在解压缩方面可以快得惊人。

    1. 对于该 crate 报告的“哎呀,一个巨大的文件导致运行时失败”与 C 中的“哎呀,我们有边界错过”之间肯定存在差异。我不知道大家在尝试利用边界错过来执行代码方面付出了多少努力。实现这种升级可能不可能,也可能可能。

    2. > 0.4.4 之前的 bzip2 crate

      他们今天发布了 0.6.0:>

      1. 但它确实适用于 bzip2 crate,这是讨论的主题。其新的纯 Rust 实现是 libbz2-rs-sys,而不是 bzip2-rs。最后一句与主题无关。

      2. 这篇文章是关于 bzip2 crate 的,而不是 bzip2-rs crate,尽管前者的存储库名称与后者相同。

      3. 使用正确的数据结构和算法的 ergonomics 也可以发挥重要作用。在 C 中,除了基本数组之外的一切都太麻烦了。

        1. 是的,这是布莱恩·坎特里尔(Brian Cantrill)在为了学习而用 Rust 重写了 dtrace 的一部分时得出的结论。当他看到自己天真的重写版本比原来的代码快得多时,他感到非常震惊,而答案归结为“我使用了 Rust 中的 BTreeMap,因为它在 std 中”。

          1. 嗯……我好奇它与 clang+Linux、clang+STL 或 HotSpot+J2EE 的对比结果如何。

            这让我想起当年 Perl 程序在常见任务中常能超越原生 C/C++ 的日子,因为 Perl 语言内置了最高效的字符串处理库。

            空间效率如何?我上次检查时,由于库很大且易于添加到项目中,许多 Rust 二进制文件往往比传统的二进制文件大得多。如果大量进行这种权衡,会对整体系统性能产生什么影响?(即使添加了更多的 RAM 来抵消 vm 页面缓存的损失,它也会开始影响局部性和缓存利用率吗?

            我很好奇像 Redox 这样的系统在实际工作负载和交互性指标上与传统 Linux 相比的基准测试结果。

              1. 挺酷的!在孤立环境下看起来很棒!我仍然对可执行文件大小增加的影响感到好奇,尤其是在完整系统中。

                如果所有二进制文件都很大,是否会开始挤占缓存空间?静态链接对完整系统是否有意义?

  3. 原文:

    > 为什么要花时间研究这个在当今几乎不再使用的90年代算法?

    现在使用的是什么?zstd?

    啊,看到这个:https://quixdb.github.io/squash-benchmark/

  4. 我很好奇,他们是否为 C 和 Rust 版本使用了相同的 llvm 代码生成器(具有相同的优化)后端。如果是的话,速度提升是从哪里来的?

    (即,这是一种 Rust 自动 SIMD 功能吗?他们是否借此机会手动优化了其他部分?还是利用了更新的优化库?还是……其他原因?

    这对我来说很有道理,不过我不知道官方答案,所以我也只能跟着猜测。

    文章中还链接了另一篇关于他们如何使用c2rust进行初始翻译的文章。

    https://trifectatech.org/blog/translating-bzip2-with-c2rust/

    就我们的目的而言,它指出了代码中一些不太理想的地方,因为 C 代码无法保证变量的范围等。

    它还指出,很多人即使数字不会很大,也只使用“int”。

    但使用正确的类型,Rust 编译器可以决定做其他事情,以获得更好的性能。

    因此,我怀疑你的想法——通过更深入的理解来解锁更好的优化——可能是正确的答案。

    1. 坦白说,C语言并不是编写现代高性能代码的理想选择。从C99到C21,中间有大约20年的时间,语言本身并未添加所需的特性来自然地支持新增的指令集(不使用内联汇编)。仅仅为clz/popcnt/clmul/pdep等指令提供良好的抽象机器指令,就能大大提升此类代码的编写效率。

      1. Popcount、clz 和 ctz 在 GCC 中作为非标准函数提供(clang 在 GNU 模式下可能也支持它们,但我不能确定)。PDEP 和 PEXT 似乎不提供,但我认为它们应该提供(而且 PEXT 是 INTERCAL 已经拥有的功能,无论如何)(尽管 PDEP 和 PEXP 可以与 x86 上的 -mbmi2 选项一起使用,但不适用于一般用途)。MMIX 中的 MOR 和 MXOR 也是我希望作为内置函数提供的功能。

    2. 在 X、Y、Z 语言中进行任何重写都能加快速度,这并不是 Rust 的固有特性。

  5. 我希望他们或 Prossimo 也能以类似的方式查看并重新实现核心互联网协议——BGP、OSPF 和 RIP、其他路由实现、DNS 服务器等。

      1. 这个域名实际上是关于内存安全还是关于 Rust 的?

    1. 有人用 SPARK Ada 做了 Ironsides DNS,它有更强的证明。

      1. 我对Ada没有意见,它是一门很好的语言。唯一的问题是,在这种情况下找到贡献者可能会比较困难。

  6. 关于macOS上没有perf的问题:你可以用dtrace进行性能分析。这是Perl中的原始火焰图脚本提到的,Rust的火焰图重实现也使用了它。它没有一些指标,比如缓存未命中或微指令退休,但仍然非常有用。

  7. 有人知道它是否支持并行解压,类似于lbzip2的风格?(或者只是使用迭代器进行块魔法的预扫描,从而实现并行解压。)

    编辑:它可能不支持。

  8. Lbzip2的解压速度快得多,利用了所有可用的CPU核心。

    现在是2025年,大多数像Python这样的程序仍然只能使用一个CPU核心。

    1. 感谢你展示了你对Python现状的无知。

  9. 除了 Rust 之外,我真的很喜欢看到这些不同的实现基准测试,阅读起来非常满足。

  10. 我喜欢 Rust,也希望学习它(我曾几次尝试过,但都以失败告终……)。我遇到的问题之一是,我遇到的每个库(略微夸张)似乎都还是 0.x.y 版本。以这个库为例。0.1.0 版本于 2014 年发布,但至今仍未发布 1.0.0 版本,Rust 社区是否对达到 1.0.0 版本有抵触情绪?

      1. 严肃回答:对于一些人来说,他们确实会频繁更新,但并不觉得有必要宣布稳定性。在其他情况下,这是一个稳定且广泛使用的 0.x 包,将其升级到 1.0 通常意味着某种破坏性更改。(我不知道这是否应该如此,但我知道如果我看到一个依赖项从 0.x 升级到 1.0,我会谨慎对待并等到有更多时间再进行更新。)

        总体而言:人们通常不太在意这一点。

    1. 是的,在 Rust 中,软件包管理器内置了关于何时更新软件包的规则。它不会自动更新主要版本,因为这意味着会破坏某些功能。只要你的软件包可以安全地自动更新,你就不会想更改主要版本号。

    1. > 在 uutils 事件之后

      哪个事件?

        1. 所以我的理解是

          1. uutils 项目并未让所有本地化场景的排序速度更快,尽管大多数人会使用 UTF-8、C 或 POSIX,而这些场景下排序确实更快

          2. 围绕不同测试用例的争论层出不穷,这在排序例程中是永无止境的争论(去看看一些前沿的排序算法开发)。

          这种抱怨过于关注他们声称速度更快的众多实用程序中的 1 个,并对我来说很重要但最终只是微不足道的批评进行争论。我真的看不到有什么大问题。

          至于许可证,这更多是你的看法。Rust 作为一种语言,通常将代码以 MIT 和 Apache2 进行双重许可,大多数开源项目都遵循这一传统。我没有看到你所说的阴谋。为了明确起见,你在此批评的作为模糊邪恶实体资助此项目的公司是Ubuntu对吧?

          1. >1. uutils项目并未让所有本地化场景的排序速度更快,尽管大多数人会使用UTF-8、C或POSIX,而这些场景下确实更快

            本地化设置 ≠ 编码。

            尝试用tr_TR.UTF-8和en_US.UTF-8对电话簿进行排序

            1. 我知道。UTF-8、C和POSIX是语言环境(至少这些是语言环境字符串)

        2. 那么我应该从那个4chan山寨网站得到什么?项目目前不如 GNU 快?哪里在说谎?

    2. 当然,你应该在自己的场景中验证这些结果。然而,我有点怀疑是否有人既非常关心性能,又愿意考虑 bzip2。在设计空间的任何地方,bzip2 都无法超越 zstd。对于许多常见输入,zstd可以在1/20的时间内生成更小的输出,或者你可以在相同时间内获得显著更小的输出,而zstd的解压速度又快20-50倍,具体取决于情况。因此,你对bzip2实现速度的争论似乎毫无意义。

      1. 当然有:有人给你提供了 bzip2 文件。或者要求你提供该格式的文件。

        那么你别无选择。

        如果你必须使用它,14% 的速度提升确实非常不错。

  11. 他们使用任何大语言模型 (LLM) 将 C 转换为 Rust 吗?

    1. 如果你要使用工具进行转译,不要使用会产生幻觉的东西。你希望它精确。

      据称 https://github.com/immunant/c2rust 效果不错。几年前他们将 quake3 转译为 rust 的博客文章:https://immunant.com/blog/2020/01/quake3/。生成的 rust 代码并不美观,但你可以进行清理,使其更“rust”。

    2. 需要精确度且可能难以审计的任务?这正是我使用LLM的地方 /s

      1. 不评论LLM是否是正确的方法,但我认为这个任务并不特别难以审计。bzip2存档几乎肯定有一个庞大的测试套件;模糊测试文件格式非常容易;而且你可以限制/审计翻译器对不安全功能的使用。

        1. 你说的没错,确实有一个庞大的现有测试套件。本文链接的一篇文章中提到了这一点。

          https://trifectatech.org/blog/translating-bzip2-with-c2rust/

          但我猜尝试调试它会是一场噩梦。鉴于大语言模型(LLM) 可以在任何地方产生幻觉,你可能会浪费大量时间。

          我猜,根据规范尝试编写一个新的实现,然后根据测试套件进行调试,可能会更快一些。你可能会更接近目标。

          事实上,由于他们使用了 c2rust,从一开始就拥有了一个完美运行的版本。从那里开始,他们只需要清理 Rust 代码,确保它不会破坏任何东西。显然,这是三个选项中最好的。

        2. > 您可以限制/审核翻译器对 unsafe 的使用。

          不,除了安全性之外,您还需要审核正确性。

  12. 很多“用 Rust 重写 X”的事情都像是在烧毁自己的房子,然后重新建造并涂上不同的颜色。

    在现代 CPU 资源有 50% 用于 UI 视觉效果的世界里,将 CPU 周期视为成就似乎无关紧要。

    1. > 在现代 CPU 资源有 50% 用于 UI 视觉效果的世界里,将 CPU 周期视为成就似乎无关紧要。

      正是这种态度导致了现代CPU资源的50%被分配给UI视觉效果。

    2. 每个节省的周期都意味着更长的电池寿命。有人承担了一次性的移植成本,现在我们可以永远享受更好的性能。

      1. 文章开头就说没人再用bzip2了。节省了一百万个周期用于没人使用的功能(据他们说),电池寿命仍为0%。

        如果现代CPU如此节能,且有大量闲置周期可用于没人要求的视觉效果,那么没人会去计算,这种比较也就无关紧要了。

        1. 听起来转换的主要动机是简化构建流程并降低安全问题风险。协议中那些无人关注的旧部分确实是安全问题的高发区。性能提升更像是重写带来的额外好处,我猜他们最多只是追求性能持平。

          1. 没错,即使我们无法移除“那个依赖项”(https://xkcd.com/2347/),我们也可以强化所有使用它的部分。

        2. bzip不是被广泛使用吗,尤其是对于tar文件?

          1. 如果有人这么做,那一定是误操作。为什么有人会在2025年选择bz2?

            1. 为了解压一个在bz2被使用时创建的存档?

            2. 当然没有人会使用2025年前创建的系统、工具和文件!

              1. bzip2至少在过去20年里从未在任何方面做到最好。

                1. 许多事物尽管并非最佳,却仍被广泛使用,并将在未来数十年继续被使用。一件事物无需成为最佳,也能有理由让人们希望将其稍作改进。

                2. 我几乎每天都使用普通的zip文件。

                  “最佳”的衡量标准远不止性能这一维度。而且你并不总能选择使用的格式,它可能由你无法影响的第三方强制规定。

            3. 如果想要优化以下方面,bzip2仍然相当不错:

                - 比 gzip 更高的压缩率
                - 比许多比 gzip 更好的竞争对手更快的压缩速度
                - 在相同的压缩率/时间下,更低的 CPU/内存使用率
              

              这是一个小众需求,但有时确实会出现。bzip2 的缺点是解压速度较慢,但对于写入密集型工作负载来说,这并不重要。

            4. 那么?如果我需要使用 bz2 压缩的资源,我不会坐等他们改用 zstd。我会直接使用 bz2。如果能使用更快的现代重写版本,我会尽可能利用所有优势。

    3. 我个人认为“启用交叉编译”这一部分更为相关,在我看来这非常重要且具有优势。

      同样,关于导出符号以及能够轻松编译为 WASM 的功能也至关重要。

    4. > 将CPU周期计数视为一项成就,在当今世界似乎毫无意义,因为现代CPU资源的50%都被用于UI视觉效果。

      这种态度导致电子应用取代原生应用,而我对此深恶痛绝。我不会仅仅为了让资源被如此浪费而购买更好的CPU和更多内存。

      1. 你知道这只是维特定律在起作用:“软件变慢的速度比硬件变快的速度更快。”[^1]

        事实上,这是杰文斯悖论:当技术进步提高了资源的使用效率,但由于需求增加,该资源的消耗率也随之上升——本质上,效率的提高可能导致消耗增加,而非预期的节约。[^2][^3]

        [^1]: https://www.comp.nus.edu.sg/~damithch/quotes/quote27.htm

        [^2]: https://www.greenchoices.org/news/blog-posts/the-jevons-para

        [^3]: https://quickonomics.com/terms/jevons-paradox/

        1. 我认为问题更深层。存在一个导致用户痛苦的延迟阈值。当达到该阈值时,市场力量会促使人们关注软件效率。

          硬件效率的提升只是为软件膨胀提供了更多空间。痛苦阈值是人类因素,且保持不变。

          因此,是时候适应维尔茨定律:软件变慢的程度与硬件变快的程度完全相同

    5. 我认为二进制文件格式解析(和构建)可能是使用不太容易发生缓冲区溢出等问题的语言的好地方。尤其是如果这是常见格式,且代码可能在各种安全环境中使用。

      1. 缓冲区溢出更像是一个库问题,而不是语言问题,尽管对于像 Rust 这样的新生态系统,人们已经不太能分辨出这两者的区别了。但关键是,如果你使用与 std::Vec 相当的语言重写 bzip2,结果还是会一样。遗憾的是,C开发者过去的惯例是手动编写大部分缓冲区操作代码,因此你可能会得到1000个手动编写的溢出检查,其中一些是错误的或完全缺失的,而共享实现中只需一个检查。事实上,即使 Rust 代码存在一个偏移量(在“安全”代码中),它也不会被视为安全问题,因为这只会导致数据损坏,而不是溢出。

        Rust 语言提供的是临时安全性(即借用检查器),而 C 语言无法轻松实现这一点。

    6. 如此简短的论点竟如此自相矛盾,实在令人惊叹。抱怨计数 CPU 周期并实际测量性能,因为……现代软件开发糟糕且不关心性能?

    7. 这些周期直接转化为某些地方节省的 $。主要是在远离任何 UI 的地方。

    8. 你只是一个最终用户,你不需要维护该套件。

      在 OSS 中,志愿者每小时的时间都是珍贵的来自天堂的甘露,带着独角兽的泪水。因此,任何能够消除辛苦工作并引入自动化的方法都是宝贵的。

      Rust 严格的编译器和适当的测试套件保证了远超 C 语言的正确性。在审查拉取请求时,审查者需要确保一切仍按预期运行,因此负担较轻。

      这是双赢的局面。

    9. 我完全同意你的第一句话,但第二句话让我无言以对…

    10. > 很多“用 Rust 重写 X”的东西感觉像是

      的确如此。你知道 react-angular-vue 之类的项目在不断更迭吗?看来人们为了职业发展而推动项目发展的趋势已经蔓延到底层世界。

      我个人仍然觉得林纳斯·托瓦兹让这些人进入内核是令人费解的。林纳斯之所以禁止 C++ 进入内核,并不是因为 C++ 本身,而是为了禁止 C++ 程序员的文化。

    11. 这就像“改编”《阿卡拉贝斯》以便为现代观众讲述自己的励志故事。

    12. 这与X11与Wayland的对比非常相似。当前的图形开发者,他们更年轻,不愿维护由老一辈编写的X服务器中的C代码。太过冒险且耗时。因此,Wayland的一个目标就是彻底废除X,用更具长期可维护性的方案取代它。事实上,当前的系统级开发者也不愿维护婴儿潮一代编写的GNU代码,甚至任何C代码,原因类似。C语言本身存在问题,就连资深开发者也难以避免其陷阱。因此,Rust 一个未明言但重要的目标是废除所有关键的 C 代码,并用 Rust 代码取代它。Ubuntu 支持这个目标。

      1. 除了 Wayland 是由那些在 X 上工作了多年的同一个人开发的。他们并不因为 C 而讨厌 X。他们也没有用 Rust 编写 Wayland。

        1. > 除了 Wayland 是由那些多年来一直致力于 X 开发的人员创建的。

          是的,他们讨厌它,并“努力想要终结它”,正如 Jordan Petridis 所言。需要注意的是,Wayland 时代维护 X 的人员与 X 的原始作者并非同一批人。

      2. 那么,你是说年轻的程序员没有应对 C 代码所需的编码功夫吗?我希望你错了。在日常设备上使用 Rust 之类的东西的想法真的让我感到恐惧。C 就像计算机的通用语一样。几乎所有与硬件打交道的人都能读懂它。我是婴儿潮一代中的一员,但我无法正确读懂 Rust 代码,因为它的语法太学术化了。越来越多的代码是用 Rust 编写的,这减少了能够阅读程序的人数。

        1. 更确切地说,任何年龄段的程序员都没有应对 C 代码所需的编码功夫。他们引入有问题的代码是不可避免的。几十年来,我们看到了许多这样的例子。我们之所以能够接受它,是因为长期以来没有真正有竞争力的替代方案。

          我能够阅读和编写没有竞争对手的时代里的 C 代码。我阅读和编写 Rust 代码没有问题。事实上,它比 C 代码更易于理解,我能够立即理解用 Rust 编写的代码,而用 C 编写的代码则难以理解。

    13. > 计算CPU周期

      而且这假设他们没有在计算上撒谎:https://desuarchive.org/g/thread/104831348/#q104831479

      1. 你有理由认为他们的数字是错误的吗,还是你的论点是“别人曾经撒过谎,也许他们也是”?

      2. Rust 开发人员继续使用误导性的基准测试?我个人对此感到非常震惊。甚至感到非常惊讶。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

你也许感兴趣的: