克隆语句是否被优化过?
Is a clone statement ever optimised out?
我有类似下面的代码
let x = Arc::new(Mutex::new(Thing::new()));
work_on_data(x.clone());
do_more_work_on_data(x.clone());
x
在第二个函数之后不使用,因此不需要第二个克隆。我应该手动删除 clone()
还是对其进行优化?
克隆可能会打印出某些内容或写入文件。优化克隆会改变行为,因此无法完成。
当然,如果编译器完全了解克隆在做什么,并且可以计算出它永远不会有副作用,它可能会优化它,但不要屏住呼吸。
作为一般规则,如果您知道一个函数总是 return 相同的值,请将该值缓存在一个变量中,而不是多次调用一个函数。
为什么不呢?
优化编译器的首要原则是 as-if 规则,它指定任何东西都可以优化,只要编译器可以证明优化不是可观察到。
注意:这是在某些允许特定优化的语言之上的。
例如:
#[derive(Clone, Debug)]
struct MyDummyType(u64);
extern {
fn print_c(_: *const ());
}
#[inline(never)]
fn print(dummy: MyDummyType) {
unsafe { print_c(&dummy as *const _ as *const _) }
}
fn main() {
let x = MyDummyType(42);
print(x.clone());
print(x.clone());
}
产生以下内容main
:
; Function Attrs: nounwind uwtable
define internal void @_ZN8rust_out4main17h0c6f2596c7f28a79E() unnamed_addr #1 {
entry-block:
tail call fastcc void @_ZN8rust_out5print17h1f2d1a86beea10d7E(i64 42)
tail call fastcc void @_ZN8rust_out5print17h1f2d1a86beea10d7E(i64 42)
ret void
}
编译器完全看穿了我们的代码(实际上我不得不使用一个外部函数来强制它在 main
中发出 一些 代码)。
那么,你的情况呢?
老实说,这更难。
具体而言,由于 Drop
:
,语义可能发生变化
- with
do_more_work_on_data(x.clone())
, x
保证在 执行结束后 被丢弃,因此 Drop
的任何副作用到在当前函数、 结束时执行
- with
do_more_work_on_data(x)
, x
可能 在 do_more_work_on_data
的末尾被丢弃,或者它可能被丢弃在更早的某个地方。
所以为了证明优化不可观察,编译器必须证明:
- 或者
Drop
没有效果,
- 或者说
Drop
会在do_more_work_on_data
的最后执行,跟紧跟其后的一样,
- 或...?
这可能性有多大?
Mutex
的 Drop
实现需要调用 FFI,因此从优化器的角度来看,它 具有 可观察到的效果。
所以这一切都取决于 do_more_work_on_data
是否被内联。如果是这样,是的,确实可以优化额外的 clone
。如果没有,我不会屏住呼吸。
我有类似下面的代码
let x = Arc::new(Mutex::new(Thing::new()));
work_on_data(x.clone());
do_more_work_on_data(x.clone());
x
在第二个函数之后不使用,因此不需要第二个克隆。我应该手动删除 clone()
还是对其进行优化?
克隆可能会打印出某些内容或写入文件。优化克隆会改变行为,因此无法完成。
当然,如果编译器完全了解克隆在做什么,并且可以计算出它永远不会有副作用,它可能会优化它,但不要屏住呼吸。
作为一般规则,如果您知道一个函数总是 return 相同的值,请将该值缓存在一个变量中,而不是多次调用一个函数。
为什么不呢?
优化编译器的首要原则是 as-if 规则,它指定任何东西都可以优化,只要编译器可以证明优化不是可观察到。
注意:这是在某些允许特定优化的语言之上的。
例如:
#[derive(Clone, Debug)]
struct MyDummyType(u64);
extern {
fn print_c(_: *const ());
}
#[inline(never)]
fn print(dummy: MyDummyType) {
unsafe { print_c(&dummy as *const _ as *const _) }
}
fn main() {
let x = MyDummyType(42);
print(x.clone());
print(x.clone());
}
产生以下内容main
:
; Function Attrs: nounwind uwtable
define internal void @_ZN8rust_out4main17h0c6f2596c7f28a79E() unnamed_addr #1 {
entry-block:
tail call fastcc void @_ZN8rust_out5print17h1f2d1a86beea10d7E(i64 42)
tail call fastcc void @_ZN8rust_out5print17h1f2d1a86beea10d7E(i64 42)
ret void
}
编译器完全看穿了我们的代码(实际上我不得不使用一个外部函数来强制它在 main
中发出 一些 代码)。
那么,你的情况呢?
老实说,这更难。
具体而言,由于 Drop
:
- with
do_more_work_on_data(x.clone())
,x
保证在 执行结束后 被丢弃,因此Drop
的任何副作用到在当前函数、 结束时执行
- with
do_more_work_on_data(x)
,x
可能 在do_more_work_on_data
的末尾被丢弃,或者它可能被丢弃在更早的某个地方。
所以为了证明优化不可观察,编译器必须证明:
- 或者
Drop
没有效果, - 或者说
Drop
会在do_more_work_on_data
的最后执行,跟紧跟其后的一样, - 或...?
这可能性有多大?
Mutex
的 Drop
实现需要调用 FFI,因此从优化器的角度来看,它 具有 可观察到的效果。
所以这一切都取决于 do_more_work_on_data
是否被内联。如果是这样,是的,确实可以优化额外的 clone
。如果没有,我不会屏住呼吸。