介绍
大家好,我是@yagisawa,一位传统的嵌入式工程师。
在网上浏览有关 Rust 的信息时,
毕竟,它不是C的对手。嵌入式是装配速度调整的世界。
有时我看到这样的写作
所以,他们无敌可能是真的,但他们到底有多无敌呢?我试图找出答案。
在研究 Rust 的速度时,发现了下面这篇文章,所以我使用了蒙特卡洛方法进行比较过程。
由于内置倾向于避免浮点运算(出于速度原因),本文处理整数值乘以 100(即计算结果在 314 左右)。
环境
常见的
- 操作系统
- Windows 10 家庭版 21H2(64 位)
- 板
- NUCLEO-F401RE
- 时钟:高达 84MHz
- ROM:512KB
- 内存:96KB
C。
- arm-none-eabi-gcc
- 10.3.1 20210824
- 优化
- -Ofast
锈
- rustc・货物
- 1.65.0-每晚
- 优化
- --release(速度优化MAX)
代码
我编写了一个代码,它使用 SysTick 以 1 毫秒为单位测量算法的执行时间。
这可能是包括库在内的语言的技能,但我使用线性同余方法生成了自己的随机数,因此不会有任何区别。C。static int cnt = 0; void SysTick_Handler() { cnt++; } int32_t rand() { static int64_t x = 10; const int64_t a = 48271; const int64_t b = 2147483647; x = (a * x) & b; return (int32_t)x; } int32_t montecarlo() { const int32_t times = 100000; int32_t cnt = 0; for (int32_t i = 0; i < times; i++) { const int32_t x = rand() % 1000; const int32_t y = rand() % 1000; if ((x * x + y * y) <= 1000000) { cnt++; } } return cnt * 400 / times; } int main() { const int32_t times = 10; volatile int32_t result = 0; volatile uint32_t* stk_ctrl_addr = (uint32_t*)0xE000E010; volatile uint32_t* stk_load_addr = (uint32_t*)0xE000E014; volatile uint32_t* stk_val_addr = (uint32_t*)0xE000E018; *stk_val_addr = 0; *stk_load_addr = 2000; *stk_ctrl_addr = 0x3; for (int32_t i = 0; i < times; i++) { result += montecarlo(); } result = (result + times - 1) / times; while ( 1 ); }锈static mut CNT: i32 = 0; #[no_mangle] unsafe extern "C" fn SysTick() { CNT += 1; } fn rand() -> i32 { static mut X: i64 = 10; let a: i64 = 48271; let b: i64 = 2147483647; unsafe { X = (a * X) & b; X as i32 } } fn montecarlo() -> i32 { let times = 100000; let mut cnt = 0; for _ in 0..times { let x = rand() % 1000; let y = rand() % 1000; if (x * x + y * y) <= 1000_000 { cnt += 1; } } cnt * 400 / times } fn main() -> ! { let times = 10; let mut result = 0; let stk_ctrl_addr = 0xE000_E010 as *mut usize; let stk_load_addr = 0xE000_E014 as *mut usize; let stk_val_addr = 0xE000_E018 as *mut usize; unsafe { write_volatile(stk_val_addr, 0); write_volatile(stk_load_addr, 2_000); write_volatile(stk_ctrl_addr, 0x3); } for _ in 0..times { result += montecarlo(); } unsafe { write_volatile(&mut result, (result + times - 1) / times); } loop {} }结果
首先,当我在没有优化的情况下进行比较时,它变成了如下。
C。 锈 11,090 毫秒 22,832 毫秒 啊,这是个坏人……
结果慢了两倍多。
看,时间不早了!好像说...嗯,不优化嵌入式软件是不可能的,所以当我将它与最喜欢的优化进行比较时,如下所示。
C。 锈 1,126 毫秒 1,186 毫秒 哦,差别不大!
由于相同的过程重复了 10 次,因此每次有 6ms 的差异。
老实说,嵌入式 6 ms 的差异是巨大的,但我认为你已经尽力了。借口是启动是一个懒惰的实现,它运行在 16MHz (1/5.25) 而它应该运行在 84MHz,所以如果你认真一点,它会相差大约 1ms。虽然取决于要制作的系统,但最近出现了频率从几百MHz到1GHz的微型计算机,因此这种微型计算机的差异甚至更小。
好吧,这并没有改变迟到的事实,但是_| ̄|○检查
如果我有时间,我会尝试比较生成什么样的机器语言。
首先,如果您公开发布版本的部分大小,
C。Sections: Name Size VMA LMA File off Algn .isr_vector 00000040 08000000 08000000 00010000 2**0 .text 000001b4 08000040 08000040 00010040 2**2 .rodata 00000000 080001f4 080001f4 00020008 2**0 .data 00000008 20000000 080001fc 00020000 2**3 .bss 00000020 20000008 08000204 00020008 2**2锈Sections: Name Size VMA LMA Type .vector_table 00000040 08000000 08000000 DATA .text 000017b6 08000040 08000040 TEXT .rodata 00000000 080017f6 080017f6 TEXT .bss 00000004 20000000 20000000 BSS .data 00000008 20000008 080017f6 DATA这就是结果。
Rust 的优化以牺牲二进制大小为代价优化速度,因此文本部分的大小为 6,070 字节,而 C 为 436 字节。
综上所述
可能还有调整的余地,但是当我正常执行 Rust 时(比 C 慢)。
不过,我不认为有没有希望的区别,而且我认为 Rust 会生活在中大型系统中,所以我不会悲观。
另外,在这个比较中,我没有写出利用 Rust 零成本抽象等优点的代码,所以我认为如果我写的代码考虑到安全性,比如错误检查(Rust 是在构建时可以进行错误检查)。
如果你没有足够的资源并且想要编写速度调整的代码,你应该使用 C,如果你有足够的资源并且想要编写高度安全的代码,你应该使用 Rust。
贪得无厌的人你甚至可以在 Rust 中编写程序集, 可能有一个选项可以使用汇编作为核心部分,其余部分使用 Rust。后记
我尝试了@fujitanozomu 建议的代码。
变化是C。// rand内 int64_t → uint32_t // montecarlo内 % 1000 → % 1024U <= 1000000 → <= 1046529U锈// rand内 i64 → u32 X = (a * X) & b → X = a.wrapping_mul(X) & b // オーバーフロー対策 // montecarlo内 % 1000 → % 1024u32 <= 1000000 → <= 1_046_529u32变成。
结果如下。
C。 锈 813毫秒 690毫秒 Rust 击败 C!
似乎每个编译器都擅长优化而不擅长优化。使用 gcc,后端不是 LLVM,而且不公平,所以请注意,这个结果并不是全部。
有时间我会试着用clang比较一下速度。正如我在评论中所写,速度比较是语言与语言这不是一个简单的故事,而是一个依赖于实施者的知识和技能的世界。
我想小心不要编写廉价代码并说“我不能使用这种语言”。
原创声明:本文系作者授权爱码网发表,未经许可,不得转载;
原文地址:https://www.likecs.com/show-308629112.html