You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何同逻辑Rust代码比C++快?如何优化C++版本?

问题背景

我实现了逻辑完全一致的单线程、无堆分配Rust与C++程序,测试性能差异显著:

  • Rust版本:通过cargo run --release运行耗时12秒
  • C++版本:经MSVC /O2 Release或GCC编译后耗时17秒,剖面引导优化(PGO)未带来性能提升
    运行环境:Windows 10系统,处理器为Intel(R) Core(TM) i7-10710U @ 1.10GHz, 1608 Mhz, 6核12线程

核心疑问

  1. Rust版本为何性能领先这么多?是否是循环展开优化更出色?
  2. 如何优化C++版本的性能?

Rust代码

use std::time::SystemTime;

pub struct BWRNG {
  pub seed:u64,
}

impl BWRNG {
  const ADD: u64 = 0x269ec3;
  const MULT: u64 = 0x5d588b656c078965;

  pub fn new(seed:u64) -> BWRNG {
      BWRNG { seed }
  }
  pub fn next(&mut self) -> u64
  {
      self.seed = self.seed.wrapping_mul(Self::MULT).wrapping_add(Self::ADD);
      self.seed
  }
  pub fn advance(&mut self, advances:u32){
      for _ in 0..advances
      {
          self.next();
      }
  }
  pub fn next_uint2(&mut self, max:u32) -> u32
  {
      (((self.next() >> 32) * (max as u64)) >> 32) as u32
  }
  pub fn get_seed(&self) -> u64
  {
      self.seed
  }
  pub fn get_current_value(&self) -> u64
  {
      self.seed
  }
  pub fn next_uint(&mut self) -> u32
  {
      (self.next() >> 32) as u32
  }


  fn advance_probability_table(&mut self) -> u32 {
      let mut count:u32 = 0;

      count += 1;
      self.advance(1);

      count += 1;
      if self.next_uint2(101) > 50
      {
          count += 1;
          self.advance(1);
      }

      count += 1;
      if self.next_uint2(101) > 30
      {
          count += 1;
          self.advance(1);
      }

      count += 1;
      if self.next_uint2(101) > 25
      {
          count += 1;
          if self.next_uint2(101) > 30
          {
              count += 1;
              self.advance(1);
          }
      }

      count += 1;
      if self.next_uint2(101) > 20
      {
          count += 1;
          if self.next_uint2(101) > 25
          {
              count += 1;
              if self.next_uint2(101) > 33
              {
                  count += 1;
                  self.advance(1);
              }
          }
      }

      return count;
  }

  pub fn initial_advances_bw2(seed:u64) -> u32 {
      let mut rng = BWRNG::new(seed);
      let mut count:u32 = 0;

      for i in 0..5 {
          count += rng.advance_probability_table();

          if i == 0{
              count += 3;
              rng.advance(3);
          }
      }

      for _limit in 0..100 {
          count += 3;
          let rand1 = rng.next_uint2(15);
          let rand2 = rng.next_uint2(15);
          let rand3 = rng.next_uint2(15);

          if rand1 != rand2 && rand1 != rand3 && rand2 != rand3
          {
              break;
          }
      }

      return count;
  }

}

fn main() {
  let time_start = SystemTime::now();

  let mut result = 0u64;
  
  for i in 0..100_000_000u64 {
    result += BWRNG::initial_advances_bw2(i) as u64;
  }

  let dur = SystemTime::now().duration_since(time_start).unwrap().as_secs();
  println!("Dur={}sec. Result={}", dur, result % 255);
}

C++代码

using u64 = unsigned long long;
using u32 = unsigned int;
using u16 = unsigned short;
using u8 = unsigned char;

class BWRNG
{
public:
    BWRNG(u64 seed = 0) : seed(seed)
    {
    }

    void advance(u32 advances)
    {
        for (u32 advance = 0; advance < advances; advance++)
        {
            next();
        }
    }

    u64 next()
    {
        return seed = seed * 0x5d588b656c078965 + 0x269ec3;
    }

    u32 nextUInt(u32 max)
    {
        return ((next() >> 32) * max) >> 32;
    }

    u32 nextUInt()
    {
        return next() >> 32;
    }

    u64 getSeed() const
    {
        return seed;
    }


    static u32 initialAdvancesBW2(u64 seed)
    {
        BWRNG rng(seed);
        u32 count = 0;

        for (u8 i = 0; i < 5; i++)
        {
            count += advanceProbabilityTable(rng);

            if (i == 0)
            {
                count += 3;
                rng.advance(3);
            }
        }

        for (u8 limit = 0; limit < 100; limit++)
        {
            count += 3;
            u8 rand1 = rng.nextUInt(15);
            u8 rand2 = rng.nextUInt(15);
            u8 rand3 = rng.nextUInt(15);

            if (rand1 != rand2 && rand1 != rand3 && rand2 != rand3)
            {
                break;
            }
        }

        return count;
    }

    static u32 advanceProbabilityTable(BWRNG& rng)
    {
        u32 count = 0;

        count++;
        rng.advance(1);

        count++;
        if (rng.nextUInt(101) > 50)
        {
            count++;
            rng.advance(1);
        }

        count++;
        if (rng.nextUInt(101) > 30)
        {
            count++;
            rng.advance(1);
        }

        count++;
        if (rng.nextUInt(101) > 25)
        {
            count++;
            if (rng.nextUInt(101) > 30)
            {
                count++;
                rng.advance(1);
            }
        }

        count++;
        if (rng.nextUInt(101) > 20)
        {
            count++;
            if (rng.nextUInt(101) > 25)
            {
                count++;
                if (rng.nextUInt(101) > 33)
                {
                    count++;
                    rng.advance(1);
                }
            }
        }

        return count;
    }


private:
    u64 seed;
};

int main()
{
    u64 result = 0;
    for (u64 i = 0; i < 100000000; i++) 
        result += BWRNG::initialAdvancesBW2(i);

    return (int)(result % 255); // 169
}

解答

1. Rust版本性能领先的原因

从代码结构和编译行为来看,核心差异来自以下几点:

  • 激进的内联优化:Rust在release模式下对小函数的内联策略比C更主动,比如advance(1)会被直接替换为next()的逻辑,彻底消除循环的边界检查和函数调用开销;而C编译器可能未完全内联这类高频小函数,保留了不必要的控制流。
  • LLVM后端优化优势:Rust依赖的LLVM编译器针对你的RNG位运算、分支逻辑生成了更紧凑的指令序列,比如对next_uint2的乘法+移位组合优化更彻底,减少了寄存器的冗余操作。
  • 循环展开的实际效果:确实可能Rust的循环展开更到位。外层1亿次循环,Rust编译器会自动做部分展开,降低循环控制的开销;而C++默认的展开阈值可能未触发,或者对该代码结构的优化力度不足。
  • 类型精度的隐性开销:C++版本中nextUInt(15)的返回值被存入u8变量,引入了u32转u8的截断操作,单次开销微小但1亿次累积后会放大差异;Rust全程使用u32避免了这部分开销。

2. C++版本的优化方案

针对你的代码,可尝试以下优化手段:

(1)强制内联高频函数

给next()、advance()、nextUInt()这类高频调用的函数添加强制内联标记,确保编译器消除函数调用开销:

// MSVC用__forceinline,GCC用[[gnu::always_inline]]
__forceinline u64 next()
{
    return seed = seed * 0x5d588b656c078965 + 0x269ec3;
}

__forceinline void advance(u32 advances)
{
    for (u32 advance = 0; advance < advances; advance++)
    {
        next();
    }
}

__forceinline u32 nextUInt(u32 max)
{
    return ((next() >> 32) * max) >> 32;
}

(2)消除不必要的类型截断

将initialAdvancesBW2中的u8 rand1 = rng.nextUInt(15);改为u32 rand1 = rng.nextUInt(15);,避免无意义的类型转换开销。

(3)手动展开固定次数的循环

比如advance(3)和advance(1)可以直接展开为多次next()调用,彻底消除循环判断:

// 替换rng.advance(3);
rng.next();
rng.next();
rng.next();

// 替换rng.advance(1);
rng.next();

(4)调整编译器优化选项

  • MSVC:在/O2基础上添加/Ob2(最大化内联)、/Oi(生成内部函数)、/Ot(优先优化速度)
  • GCC:在-O3基础上添加-finline-functions、-funroll-loops(强制循环展开)、-march=native(针对当前CPU优化指令集)

(5)重构减少函数调用嵌套

将advanceProbabilityTable的逻辑直接内联到initialAdvancesBW2中,或者把nextUInt(101)的逻辑展开,减少多层函数调用的间接开销。

(6)对比汇编找冗余指令

用MSVC的/FA或GCC的-S生成汇编代码,对比Rust生成的汇编(cargo rustc --release -- --emit asm),定位是否存在冗余的寄存器操作、分支判断等问题。


内容的提问来源于stack exchange,提问作者RainingChain

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 05:47:02