我知道C++ 中的"未定义行为"几乎可以让编译器做任何想做的事情.但是,我遇到了让我感到惊讶的崩溃,因为我认为代码足够安全.
在这种情况下,真正的问题仅发生在使用特定编译器的特定平台上,并且仅在启用了优化时才发生.
我尝试了几件事来重现问题并将其简化到最大程度.这是一个名为的函数的摘录Serialize,它将获取bool参数,并将字符串true或复制false到现有的目标缓冲区.
如果bool参数是未初始化的值,那么这个函数是否会在代码审查中,没有办法告诉它实际上可能会崩溃?
// Zero-filled global buffer of 16 characters
char destBuffer[16];
void Serialize(bool boolValue) {
// Determine which string to print based on boolValue
const char* whichString = boolValue ? "true" : "false";
// Compute the length of the string we selected
const size_t len = strlen(whichString);
// Copy string into destination buffer, which is zero-filled (thus already null-terminated)
memcpy(destBuffer, whichString, len);
}
Run Code Online (Sandbox Code Playgroud)
如果使用clang 5.0.0 +优化执行此代码,它将/可能崩溃.
boolValue ? "true" …
据我所知,引用/指针别名会阻碍编译器生成优化代码的能力,因为它们必须确保在两个引用/指针确实是别名的情况下,生成的二进制文件的行为正确。例如,在以下C代码中,
void adds(int *a, int *b) {
*a += *b;
*a += *b;
}
Run Code Online (Sandbox Code Playgroud)
当clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)用-O3标志编译时,它发出
0000000000000000 <adds>:
0: 8b 07 mov (%rdi),%eax
2: 03 06 add (%rsi),%eax
4: 89 07 mov %eax,(%rdi) # The first time
6: 03 06 add (%rsi),%eax
8: 89 07 mov %eax,(%rdi) # The second time
a: c3 retq
Run Code Online (Sandbox Code Playgroud)
下面的代码存回(%rdi)两次的情况下,int *a和int *b别名。
当我们明确告诉编译器这两个指针不能使用restrict关键字别名时:
void adds(int * restrict a, int * restrict …Run Code Online (Sandbox Code Playgroud) 当在Rust中的一个数组上运行求和循环时,我发现CAPACITY> = 240 时性能会大幅下降。CAPACITY= 239的速度大约是80倍。
Rust对“短”数组进行了特殊的编译优化吗?
与编译rustc -C opt-level=3。
use std::time::Instant;
const CAPACITY: usize = 240;
const IN_LOOPS: usize = 500000;
fn main() {
let mut arr = [0; CAPACITY];
for i in 0..CAPACITY {
arr[i] = i;
}
let mut sum = 0;
let now = Instant::now();
for _ in 0..IN_LOOPS {
let mut s = 0;
for i in 0..arr.len() {
s += arr[i];
}
sum += s;
}
println!("sum:{} time:{:?}", sum, …Run Code Online (Sandbox Code Playgroud) Rust具有128位整数,这些整数用数据类型表示i128(u128对于无符号整数):
let a: i128 = 170141183460469231731687303715884105727;
Run Code Online (Sandbox Code Playgroud)
Rust如何使这些i128值在64位系统上工作?例如,如何对这些进行算术运算?
据我所知,既然该值不能容纳在x86-64 CPU的一个寄存器中,那么编译器是否会以某种方式使用2个寄存器i128?还是他们改用某种大整数结构来表示它们?
我在Rust写了一个线性代数库.
我有一个函数来获取给定行和列的矩阵单元格的引用.此函数以一对断言开始,行和列在边界内:
#[inline(always)]
pub fn get(&self, row: usize, col: usize) -> &T {
assert!(col < self.num_cols.as_nat());
assert!(row < self.num_rows.as_nat());
unsafe {
self.get_unchecked(row, col)
}
}
Run Code Online (Sandbox Code Playgroud)
在紧密的循环中,我认为跳过边界检查可能会更快,所以我提供了一个get_unchecked方法:
#[inline(always)]
pub unsafe fn get_unchecked(&self, row: usize, col: usize) -> &T {
self.data.get_unchecked(self.row_col_index(row, col))
}
Run Code Online (Sandbox Code Playgroud)
奇怪的是,当我使用这些方法来实现矩阵乘法(通过行和列迭代器)时,我的基准测试显示,当我检查边界时它实际上快了大约33%.为什么会这样?
我在两台不同的计算机上试过这个,一台运行Linux,另一台运行OSX,两者都显示效果.
完整的代码在github上.相关文件是lib.rs.感兴趣的功能是:
get 在第68行get_unchecked 在第81行next 在第551行mul 在第796行matrix_mul (基准)在第1038行请注意,我正在使用类型级数来参数化我的矩阵(通过虚拟标记类型也可以选择动态大小),因此基准测试将两个100x100矩阵相乘.
更新:
我已经大大简化了代码,删除了在基准测试中没有直接使用的东西并删除了泛型参数.我还编写了一个不使用迭代器的乘法实现,并且该版本不会产生相同的效果.有关此版本的代码,请参见此处.克隆minimal-performance分支并运行cargo bench将对两种不同的乘法实现进行基准测试(请注意,断言在该分支中以注释开头).
另外值得注意的是,如果我更改get*函数以返回数据的副本而不是引用(f64而不是&f64 …
以下指针别名示例:
pub unsafe fn f(a: *mut i32, b: *mut i32, x: *const i32) {
*a = *x;
*b = *x;
}
Run Code Online (Sandbox Code Playgroud)
编译成以下程序集(with -C opt-level=s):
example::f:
push rbp
mov rbp, rsp
mov eax, dword ptr [rdx]
mov dword ptr [rdi], eax
mov eax, dword ptr [rdx]
mov dword ptr [rsi], eax
pop rbp
ret
Run Code Online (Sandbox Code Playgroud)
请注意,x它被解除引用两次.LLVM没有将其视为noalias.我的第一个想法是避免在赋值中使用指针,而是使用安全引用(因为那些" 遵循LLVM的作用域noalias模型 ")来给优化器提示:
pub fn g(a: *mut i32, b: *mut i32, x: *const i32) {
let safe_a = unsafe { …Run Code Online (Sandbox Code Playgroud) 我最近一直在研究Rust编程语言.它是如何工作的?Rust代码似乎被编译成ELF或PE(等)二进制文件,但我无法找到有关如何完成的任何信息?它被编译成中间格式,然后用gxx编译其余部分吗?任何帮助(或链接)将非常感激.
执行rustc -C help节目(除其他外):
-C opt-level=val -- optimize with possible levels 0-3, s, or z
Run Code Online (Sandbox Code Playgroud)
我认为级别0到3非常直观:级别越高,执行的优化程度越高.但是,我不知道这些s和z选项正在做什么,我找不到与它相关的Rust相关信息.
考虑一下片段
struct Foo {
dummy: [u8; 65536],
}
fn bar(foo: Foo) {
println!("{:p}", &foo)
}
fn main() {
let o = Foo { dummy: [42u8; 65536] };
println!("{:p}", &o);
bar(o);
}
Run Code Online (Sandbox Code Playgroud)
该计划的典型结果是
0x7fffc1239890
0x7fffc1229890
Run Code Online (Sandbox Code Playgroud)
地址不同的地方.
显然,大型数组dummy已被复制,正如编译器的移动实现中所期望的那样.不幸的是,这可能会产生非平凡的性能影响,因为这dummy是一个非常大的阵列.这种影响可以迫使人们选择通过引用来传递参数,即使函数实际上在概念上"消耗"了参数.
由于Foo没有派生Copy,对象o被移动.由于Rust禁止访问被移动的对象,是什么阻止bar了"重用"原始对象o,迫使编译器生成一个可能昂贵的按位复制?是否存在根本性的困难,或者我们是否会看到编译器有一天会优化掉这个逐位复制?
llvm-codegen ×10
rust ×9
performance ×2
abi ×1
arrays ×1
bigint ×1
c++ ×1
inline ×1
int128 ×1
llvm ×1
optimization ×1
x86-64 ×1