当前位置: 首页 > 知识库问答 >
问题:

rust - Rust代码优化?

朱风史
2023-05-04

我想写一个模拟 DNF 装备增幅的程序,通过多次样本执行得到平均每件增幅 10 装备需要增幅多少次。装备 +4 之前不会失败,+4 之后会失败且失败后还会掉级,具体如下图所示:

image.png

公会秘药和普雷宠物会额外增加每次增幅的成功率 1% 和 4%,所以一共分了三种情况。

我最开始用 js 写了一版:

const times = 1_000_000;
const data = [
  // 0 -> 1
  {
    rate: 1,
    failedTo: 0,
  },
  // 1 -> 2
  {
    rate: 1,
    failedTo: 1,
  },
  // 2 -> 3
  {
    rate: 1,
    failedTo: 2,
  },
  // 3 -> 4
  {
    rate: 1,
    failedTo: 3,
  },
  // 4 -> 5
  {
    rate: 0.8,
    failedTo: 3,
  },
  // 5 -> 6
  {
    rate: 0.7,
    failedTo: 4,
  },
  // 6 -> 7
  {
    rate: 0.6,
    failedTo: 5,
  },
  // 7 -> 8
  {
    rate: 0.7,
    failedTo: 4,
  },
  // 8 -> 9
  {
    rate: 0.6,
    failedTo: 5,
  },
  // 9 -> 10
  {
    rate: 0.5,
    failedTo: 6,
  },
];

function zengfuTest(title, ex = 0) {
  const log = {
    total: 0,
    0: 0,
    1: 0,
    2: 0,
    3: 0,
    4: 0,
    5: 0,
    6: 0,
    7: 0,
    8: 0,
    9: 0,
    _0: 0,
    _1: 0,
    _2: 0,
    _3: 0,
    _4: 0,
    _5: 0,
    _6: 0,
    _7: 0,
    _8: 0,
    _9: 0,
  };

  for (let i = 0; i < times; i++) {
    zengfuOne(0);
  }

  showLog(title);

  /**
   * @param {string} key
   */
  function logData(key) {
    log[key]++;
    log.total++;
  }

  /**
   * @param {number} rate
   */
  function zengfu(rate) {
    return Math.random() <= rate;
  }

  /**
   * @param {number} lv
   */
  function zengfuOne(lv) {
    if (lv === 10) return;

    const { rate, failedTo } = data[lv];
    const result = zengfu(rate + ex);

    if (result) {
      logData(lv);
      zengfuOne(lv + 1);
    } else {
      logData("_" + lv);
      zengfuOne(failedTo);
    }
  }

  function showLog(title) {
    console.log(`==== ${title} ====`);
    console.log(`测试数量:${times}`);
    console.log("平均每件红 10 所需增幅次数如下所示:");

    for (let i = 0; i <= 9; i++) {
      const success = (log[i] / times).toFixed(3);
      const fail = (log["_" + i] / times).toFixed(3);
      console.log(
        `增幅${i + 1}:成功 ${success} 次${
          log["_" + i] ? `,失败 ${fail} 次` : ""
        }`
      );
    }
    console.log(
      `\n平均每件红 10 需要增幅 ${(log.total / times).toFixed(3)} 次\n`
    );
  }
}

zengfuTest("红 10 增幅次数模拟");
zengfuTest("【公会秘药】红 10 增幅次数模拟", 0.01);
zengfuTest("【公会秘药】【普雷宠物】红 10 增幅次数模拟", 0.05);

后来想到我刚学了 rust,不如练练手,而且 rust 很快,于是又写了一版:

use rand::prelude::*;

struct Logger {
  title: String,
  total: i32,
  success: Vec<i32>,
  fail: Vec<i32>,
}

impl Logger {
  fn new(title: String) -> Self {
    Logger {
      title,
      total: 0,
      success: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      fail: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    }
  }

  fn print(&self) {
    println!("==== {} ====", &self.title);
    println!("测试数量:{}", TEST_TIMES);
    println!("平均每件红 10 所需增幅次数如下所示:");

    let times: f64 = TEST_TIMES.try_into().unwrap();
    let total: f64 = self.total.try_into().unwrap();

    for i in 0..9 {
      let success: f64 = self.success[i].try_into().unwrap();
      let fail: f64 = self.fail[i].try_into().unwrap();

      println!(
        "增幅 {}:成功 {} 次,失败 {} 次",
        i + 1,
        success / times,
        fail / times
      );
    }

    println!("\n平均每件红 10 需要增幅 {} 次\n", total / times);
  }
}

#[derive(Debug)]
struct AmplifyData {
  rate: f64,
  fall: Option<i32>,
}

const TEST_TIMES: i32 = 1_000_000;

fn main() {
  test(String::from("红 10 增幅次数模拟"), 0.0);
  test(String::from("【公会秘药】红 10 增幅次数模拟"), 0.01);
  test(
    String::from("【公会秘药】【普雷宠物】红 10 增幅次数模拟"),
    0.05,
  );
}

fn test(title: String, ex: f64) {
  let mut logger = Logger::new(title);
  let data: Vec<AmplifyData> = vec![
    AmplifyData {
      rate: 1.0,
      fall: None,
    },
    AmplifyData {
      rate: 1.0,
      fall: None,
    },
    AmplifyData {
      rate: 1.0,
      fall: None,
    },
    AmplifyData {
      rate: 1.0,
      fall: None,
    },
    AmplifyData {
      rate: 0.8,
      fall: Some(3),
    },
    AmplifyData {
      rate: 0.7,
      fall: Some(4),
    },
    AmplifyData {
      rate: 0.6,
      fall: Some(5),
    },
    AmplifyData {
      rate: 0.7,
      fall: Some(4),
    },
    AmplifyData {
      rate: 0.6,
      fall: Some(5),
    },
    AmplifyData {
      rate: 0.5,
      fall: Some(6),
    },
  ];

  for _ in 0..TEST_TIMES {
    amplify_one(0, &data, ex, &mut logger);
  }

  logger.print();
}

fn amplify_once(rate: f64) -> bool {
  return thread_rng().gen::<f64>() <= rate;
}

fn amplify_one(lv: i32, data: &Vec<AmplifyData>, ex: f64, logger: &mut Logger) {
  if lv == 10 {
    return;
  }

  let index: usize = lv.try_into().unwrap();
  let item = &data[index];
  let AmplifyData { rate, fall, .. } = item;

  let true_rate = *rate + ex;

  let result = amplify_once(true_rate);

  logger.total += 1;
  let index: usize = lv.try_into().unwrap();

  if result {
    logger.success[index] += 1;
    amplify_one(lv + 1, data, ex, logger);
  } else {
    logger.fail[index] += 1;
    if let Some(next_lv) = fall {
      amplify_one(*next_lv, data, ex, logger);
    }
  }
}

然而实际上 rust 代码执行非常慢,js 那边三种模拟都走完了,这边还得等好几秒才出了第一个结果...不知道应该怎么优化一下我的代码?

共有1个答案

越信鸥
2023-05-04

优化了一下你看看:

use rand::prelude::*;
use std::convert::TryInto;

struct Logger {
    // ...
}

impl Logger {
    // ...
}

#[derive(Debug)]
struct AmplifyData {
    rate: f64,
    fall: Option<i32>,
}

const TEST_TIMES: i32 = 1_000_000;

fn main() {
    test(String::from("红 10 增幅次数模拟"), 0.0);
    test(String::from("【公会秘药】红 10 增幅次数模拟"), 0.01);
    test(
        String::from("【公会秘药】【普雷宠物】红 10 增幅次数模拟"),
        0.05,
    );
}

fn test(title: String, ex: f64) {
    let mut logger = Logger::new(title);
    let data: Vec<AmplifyData> = vec![
        // ...
    ];

    for _ in 0..TEST_TIMES {
        amplify_one(0, &data, ex, &mut logger);
    }

    logger.print();
}

fn amplify_once(rate: f64) -> bool {
    return thread_rng().gen::<f64>() <= rate;
}

fn amplify_one(lv: i32, data: &Vec<AmplifyData>, ex: f64, logger: &mut Logger) {
    let mut lv = lv;
    while lv != 10 {
        let index: usize = lv.try_into().unwrap();
        let item = &data[index];
        let AmplifyData { rate, fall, .. } = item;

        let true_rate = *rate + ex;

        let result = amplify_once(true_rate);

        logger.total += 1;
        let index: usize = lv.try_into().unwrap();

        if result {
            logger.success[index] += 1;
            lv += 1;
        } else {
            logger.fail[index] += 1;
            if let Some(next_lv) = fall {
                lv = *next_lv;
            }
        }
    }
}

用 rayon库实现并行计算再优化:

use rand::prelude::*;
use rayon::prelude::*;
use std::convert::TryInto;

struct Logger {
    // ...
}

impl Logger {
    // ...
}

#[derive(Debug, Clone, Copy)]
struct AmplifyData {
    rate: f64,
    fall: Option<i32>,
}

const TEST_TIMES: i32 = 1_000_000;

fn main() {
    test(String::from("红 10 增幅次数模拟"), 0.0);
    test(String::from("【公会秘药】红 10 增幅次数模拟"), 0.01);
    test(
        String::from("【公会秘药】【普雷宠物】红 10 增幅次数模拟"),
        0.05,
    );
}

fn test(title: String, ex: f64) {
    let mut logger = Logger::new(title);
    let data: Vec<AmplifyData> = vec![
        // ...
    ];

    let num_threads = rayon::current_num_threads();
    let chunk_size = TEST_TIMES / num_threads as i32;

    (0..num_threads)
        .into_par_iter()
        .for_each(|_| {
            let mut thread_local_logger = Logger::new("".to_string());
            for _ in 0..chunk_size {
                amplify_one(0, &data, ex, &mut thread_local_logger);
            }
            logger.merge(&thread_local_logger);
        });

    logger.print();
}

impl Logger {
    fn merge(&mut self, other: &Self) {
        self.total += other.total;
        for i in 0..10 {
            self.success[i] += other.success[i];
            self.fail[i] += other.fail[i];
        }
    }
}

fn amplify_once(rate: f64) -> bool {
    return thread_rng().gen::<f64>() <= rate;
}

fn amplify_one(lv: i32, data: &Vec<AmplifyData>, ex: f64, logger: &mut Logger) {
    let mut lv = lv;
    while lv != 10 {
        // ...
    }
}
 类似资料:
  • 我们知道,Node.js 不适合 CPU 密集型计算的场景,通常的解决方法是用 C/C++ 编写 Node.js 的扩展(Addons)。以前只能用 C/C++,现在我们有了新的选择——Rust。 3.5.1 环境 node@8.9.4 rust@1.26.0-nightly 3.5.2 Rust Rust 是 Mozilla 开发的注重安全、性能和并发的现代编程语言。相比较于其他常见的编程语言,

  • Rust 是 Mozilla 的一个新的编程语言,由web语言的领军人物Brendan Eich(js之父),Dave Herman以及Mozilla公司的Graydon Hoare 合力开发。 创建这个新语言的目的是为了解决一个很顽疾的问题:软件的演进速度大大低于硬件的演进,软件在语言级别上无法真正利用多核计算带来的性能提升。Rust是针对多核体系提出的语言,并且吸收一些其他动态语言的重要特性,

  • Rust-GPU 是面向 GPU 编程的开源项目,目标是使 Rust 成为 GPU 着色器开发的“一等公民”编程语言和生态系统。 示例 use glam::{Vec3, Vec4, vec2, vec3};#[spirv(fragment)]pub fn main( #[spirv(frag_coord)] in_frag_coord: &Vec4, #[spirv(push_cons

  • Visual Rust 是一个可视化的 Rust 语言集成开发环境。这是一个 Visual Studio 的扩展,首先是一个 Rust 语言编辑器,同时可创建、编译和运行 Rust 项目。

  • Goose 是受 Locust 启发的 Rust 负载测试工具。用户行为通过标准的 Rust 代码定义,负载测试是依赖于 Goose 库的应用程序,Web 请求是使用 Reqwest HTTP 客户端发出的。 主要特性 文档齐全 基于 Reqwest,支持异步发起请求 所提供的指标与 Locust 报告一致

  • rbatis 是一个用 Rust 编写的高性能、安全、动态 SQL(编译时)ORM 框架,受 Mybatis 和 MybatisPlus 的启发。它提供高性能,基于 Future,带有 async_std/tokio,单线程基准测试可以轻松达到 200,000 QPS。 特性: 编译时动态 sql(mybatis 动态 sql 标签)、全异步(Future)、生产实践(abs_admin) 内存安