【发布时间】:2016-08-28 23:08:30
【问题描述】:
我尝试使用get_unchecked() 而不是[] 索引运算符来提高我的des crate 的s() 函数的性能。
但是,即使 [](或 get_unchecked())函数在我的基准测试中被调用了 48 亿次,它也不会带来明显的性能提升。我原以为调用 get_unchecked() 48 亿次而不是 [] 会导致我的 Intel Core 2 Duo 2.4 GHz 处理器的时间提高 2 秒。
我做了这个小基准,有一个小代码给你看:
#![feature(test)]
extern crate test;
fn main() {
}
pub fn s(box_id: usize, block: u64) -> u64 {
const TABLES: [[[u64; 16]; 4]; 8] =
[[[ 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7]
, [ 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8]
, [ 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0]
, [ 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13]
],
[ [ 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10]
, [ 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5]
, [ 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15]
, [ 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9]
],
[ [ 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8]
, [ 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1]
, [ 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7]
, [ 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12]
],
[ [ 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15]
, [ 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9]
, [ 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4]
, [ 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14]
],
[ [ 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9]
, [ 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6]
, [ 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14]
, [ 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3]
],
[ [ 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11]
, [ 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8]
, [ 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6]
, [ 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13]
],
[ [ 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1]
, [ 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6]
, [ 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2]
, [ 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12]
],
[ [ 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7]
, [ 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2]
, [ 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8]
, [ 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11]
]];
let i = ((block & 0x20) >> 4 | (block & 1)) as usize;
let j = ((block & 0x1E) >> 1) as usize;
unsafe { *TABLES.get_unchecked(box_id).get_unchecked(i).get_unchecked(j) }
//TABLES[box_id][i][j]
}
#[cfg(test)]
mod bench {
use test::{Bencher, black_box};
use super::s;
#[bench]
fn bench_s(bencher: &mut Bencher) {
bencher.iter(|| {
let box_id = black_box(7);
(0 .. 40_000_000).fold(0, |acc, block| acc + s(box_id, block))
});
}
}
我第一次使用[] 运行此基准测试时,它比使用get_unchecked() 的版本花费的时间更少(尽管平均而言get_unchecked() 版本似乎快了一点)。
我不确定它是否真的反映了我在现实生活中的基准(包括加密一个大文件),但它给出了一个想法。
我检查了程序集以确保编译器没有优化边界检查。
这里是get_unchecked()的版本:
0000000000009360 <_ZN3des7s_table17hbabdd9dee72331a5E>:
9360: 48 89 f0 mov %rsi,%rax
9363: 48 c1 e8 04 shr $0x4,%rax
9367: 83 e0 02 and $0x2,%eax
936a: 89 f1 mov %esi,%ecx
936c: 83 e1 01 and $0x1,%ecx
936f: 48 09 c1 or %rax,%rcx
9372: 48 c1 e7 09 shl $0x9,%rdi
9376: 48 8d 05 93 57 04 00 lea 0x45793(%rip),%rax # 4eb10 <ref10404>
937d: 48 01 f8 add %rdi,%rax
9380: 48 c1 e1 07 shl $0x7,%rcx
9384: 48 01 c1 add %rax,%rcx
9387: 83 e6 1e and $0x1e,%esi
938a: 48 8b 04 b1 mov (%rcx,%rsi,4),%rax
938e: c3 retq
938f: 90 nop
这是带有[]的版本:
0000000000009390 <_ZN3des7s_table17hbabdd9dee72331a5E>:
9390: 50 push %rax
9391: 48 89 f8 mov %rdi,%rax
9394: 48 83 f8 07 cmp $0x7,%rax
9398: 77 30 ja 93ca <_ZN3des7s_table17hbabdd9dee72331a5E+0x3a>
939a: 48 89 f1 mov %rsi,%rcx
939d: 48 c1 e9 04 shr $0x4,%rcx
93a1: 83 e1 02 and $0x2,%ecx
93a4: 89 f2 mov %esi,%edx
93a6: 83 e2 01 and $0x1,%edx
93a9: 48 09 ca or %rcx,%rdx
93ac: 48 c1 e0 09 shl $0x9,%rax
93b0: 48 8d 0d 99 57 04 00 lea 0x45799(%rip),%rcx # 4eb50 <const10401>
93b7: 48 01 c1 add %rax,%rcx
93ba: 48 c1 e2 07 shl $0x7,%rdx
93be: 48 01 ca add %rcx,%rdx
93c1: 83 e6 1e and $0x1e,%esi
93c4: 48 8b 04 b2 mov (%rdx,%rsi,4),%rax
93c8: 59 pop %rcx
93c9: c3 retq
93ca: 48 8d 3d b7 a3 25 00 lea 0x25a3b7(%rip),%rdi # 263788 <panic_bounds_check_loc10404>
93d1: ba 08 00 00 00 mov $0x8,%edx
93d6: 48 89 c6 mov %rax,%rsi
93d9: e8 82 25 04 00 callq 4b960 <_ZN4core9panicking18panic_bounds_check17hb2d969c3cc11ed08E>
93de: 66 90 xchg %ax,%ax
我们可以看到get_unchecked() 的版本更小,[] 的版本检查边界。
两个版本都在发布模式下编译。
为什么get_unchecked() 版本没有比这更快?
当get_unchecked()/[]被调用48亿次时,我认为它应该比[]版本快至少几秒钟。
编辑:我使用valgrind分析了代码。
带有[] 的版本显示带有数组索引的行的成本为10,而带有get_unchecked() 的版本显示的成本小于1(见下图)。
但是,该功能的成本(参见图像左侧)保持不变。这很奇怪。有人解释一下吗?
【问题讨论】:
-
分支预测?您的分析器告诉您使用边界检查代码产生的 CPU 有多少错误预测?
-
如果
[]被调用 48 亿次,那么至少一条指令(用于边界检查)将被调用 48 亿次,对吧?所以即使 CPU 预测分支,我认为我们应该看到两个版本之间的显着差异。我错了吗? -
你正在做很多人都会做的事情——猜测,并要求其他人猜测。有no need to guess。
-
@MikeDunlavey 我使用了分析器(valgrind)。几天来,我一直在提高我的 crate 的性能,毕竟我的速度提高了 30 倍。从
[]切换到get_unchecked()使得这条特定线路的成本从10 降低到小于1。但是,该功能的总体成本保持不变。这对我来说很奇怪。 -
我认为这是一个很好的问题,但它与 Rust 无关。 IMO 与
get_unchecked没有区别,因为 CPU 不是瓶颈。
标签: performance assembly optimization rust