为什么 Ruby `num >> 1` 比 `num/2` 慢

问题描述 投票:0回答:1

令我惊讶的是 Ruby

num >> 1
num/2
慢,因为我一直认为
bit operators
应该比普通运算符更快。

# ruby 3.0.3

require "benchmark/ips"

Benchmark.ips do |bm|
    num = 1000000

    bm.report("num/2") do
        num/2
    end

    bm.report("num >> 1") do
        num >> 1
    end

    bm.compare!
end
# num/2:        16045584.8 i/s
# num >> 1:     14591335.3 i/s - 1.10x  slower


Benchmark.ips do |bm|
    num = 1000000

    bm.report("num * 2") do
        num * 2
    end

    bm.report("num << 1") do
        num << 1
    end

    bm.compare!
end
# num * 2:      18252815.2 i/s
# num << 1:     14289700.6 i/s - 1.28x  slower
ruby benchmarking ruby-3
1个回答
0
投票

≅ 1 微秒内进行 5 亿次迭代

我没有使用 benchmark/ips 进行测试,因为它不是核心 gem,也不是 Ruby 标准库的一部分。然而,基于使用标准 Benchmark#bmbm 方法的一些重构测试,似乎您的一些结果可能会因某些版本的 Ruby 中字符串赋值的开销而产生偏差,因为我注意到一个更常见的差异为多次运行仅需 1 微秒,且字符串不会冻结。在启用 YJIT 的情况下,我也得到了稍微更加一致的结果,因为这似乎会增加一些轻微的开销。然而,无论是否启用 YJIT,我都没有注意到 Ruby 3.3.0 在有或没有冻结字符串的情况下存在material差异。 考虑以下内容,它在 Ruby 3.3.0 上使用冻结字符串:

# frozen_string_literal: true require "benchmark" NUM = 500_000_000 Benchmark.bmbm do |x| x.report("NUM / 2") { NUM/2 } x.report("NUM * 2") { NUM * 2 } x.report("NUM >> 1") { NUM >> 1 } x.report("NUM << 1") { NUM << 1 } end

即使在 
5 亿次迭代中

,在启用了 YJIT 和冻结字符串的 Ruby 3.3.0 上,在丢弃排练值后,我始终得到以下正负一微秒的系统时间: user system total real NUM / 2 0.000001 0.000000 0.000001 ( 0.000001) NUM * 2 0.000001 0.000000 0.000001 ( 0.000001) NUM >> 1 0.000001 0.000000 0.000001 ( 0.000001) NUM << 1 0.000001 0.000000 0.000001 ( 0.000001)

大约一微秒的挂钟时间中的 5 亿次迭代对于我来说对于任何实际目的来说似乎都足够快,并且当总时间在合理精度内基本上无法测量时担心每秒迭代似乎是不必要的优化。

如果您确实需要更快的性能,请考虑使用线程、ractor 或某些针对并发性进行优化的多核方法。

parallel

gem 可以在主线 Ruby 上提供帮助,其他引擎可能会提供其他替代方案。 JRuby 和 TruffleRuby 在此基准测试中表现更差

但是,值得注意的是,上面的代码在 C Ruby 上的运行速度比在 JRuby 或 TruffleRuby 上运行速度更快。这是可以预料的,因为这些引擎针对不同的用例进行了优化,但仍然值得注意的是,参考实现目前在以纯粹迭代方式实现时是最快的。

在 TruffleRuby 24.0.0 上进行同样的 5 亿次迭代,无需任何代码更改或特定于引擎的优化,结果是: user system total real NUM / 2 0.000038 0.000025 0.000063 ( 0.000012) NUM * 2 0.000036 0.000003 0.000039 ( 0.000013) NUM >> 1 0.000039 0.000002 0.000041 ( 0.000011) NUM << 1 0.000024 0.000007 0.000031 ( 0.000010)

对于任何实际目的来说,这仍然足够快,但确实强调了问题在于底层 VM 或运算符的实现,而不是 Ruby 语言本身。

优化 IRB 会议以进行基准测试

请注意,如果您在 IRB 内运行而不是调用文件,则应按以下方式启动 IRB:

RUBYOPT="--enable=frozen_string_literal" irb --nocolorize

确保默认启用冻结字符串并避免 ANSI 颜色影响性能。

© www.soinside.com 2019 - 2024. All rights reserved.