我最近意识到我可以在Rust中创建本地函数(函数中的函数)。似乎是一种清理代码而不会污染文件功能空间的好方法。我在下面通过本地功能与外部'的小样本。功能:
fn main() {
fn local_plus(x: i64, y: i64) -> i64 {
x + y
}
let x = 2i64;
let y = 5i64;
let local_res = local_plus(x, y);
let external_res = external_plus(x,y);
assert_eq!(local_res, external_res);
}
fn external_plus(x: i64, y: i64) -> i64 {
x + y
}
我想知道这样做是否有任何负面的性能影响?就像Rust每次运行包含函数时重新声明函数或占用一些不需要的函数空间一样吗?或者它几乎没有性能影响?
稍微提一下,关于我如何能够找到自己答案的任何提示(通过阅读任何特定的文档或我可以使用的工具)都会受到欢迎。
答案 0 :(得分:6)
稍微提一下,关于我如何能够找到自己答案的任何提示(通过阅读任何特定的文档或我可以使用的工具)都会受到欢迎。
您知道the Rust playground吗?
输入您的代码,单击“LLVM IR”,“Assembly”或“MIR”而不是“Run”,您将看到为该代码发出的低级表示形式。
我个人更喜欢LLVM IR(我以前从C ++中读取它),这仍然比汇编更高,但仍然是 post 语言。
我想知道这样做是否有任何负面影响?
这是一个非常复杂的问题;实际上。
在Rust 中本地或外部声明函数之间的唯一差异是范围之一。在本地声明它只会缩小其范围。 没有别的。
但是......范围和用法会对编译产生严重影响。
例如,仅使用一次的函数比使用10次的函数更有可能被内联。编译器不能轻易估计pub
函数(无界)的使用次数,但对本地或非pub
函数具有完全的知识。函数是否内联可能会严重影响性能概况(更糟或更好)。
因此,通过缩小范围,从而限制使用,您鼓励编译器考虑您的内联函数(除非您将其标记为“冷”)。
另一方面,由于范围缩小,因此无法共享(显然)。
那么......什么?
遵循用法:尽可能在最严格的范围内定义项目。
这是封装:现在,下次您需要修改此部分时,您将确切知道受影响的范围。
对Rust有一定的信任,如果可以避免,就不会引入开销。
答案 1 :(得分:5)
没有影响;我检查了为两个变体生成的程序集,它是完全相同的。
我比较的两个版本:
"外部":
fn main() {
let x = 2i64;
let y = 5i64;
let external_res = external_plus(x,y);
}
fn external_plus(x: i64, y: i64) -> i64 {
x + y
}
"本地":
fn main() {
fn local_plus(x: i64, y: i64) -> i64 {
x + y
}
let x = 2i64;
let y = 5i64;
let local_res = local_plus(x, y);
}
两者都产生相同的结果(今天及每晚的发布模式):
.text
.file "rust_out.cgu-0.rs"
.section .text._ZN8rust_out4main17hb497928495d48c40E,"ax",@progbits
.p2align 4, 0x90
.type _ZN8rust_out4main17hb497928495d48c40E,@function
_ZN8rust_out4main17hb497928495d48c40E:
.cfi_startproc
retq
.Lfunc_end0:
.size _ZN8rust_out4main17hb497928495d48c40E, .Lfunc_end0-_ZN8rust_out4main17hb497928495d48c40E
.cfi_endproc
.section .text.main,"ax",@progbits
.globl main
.p2align 4, 0x90
.type main,@function
main:
.cfi_startproc
movq %rsi, %rax
movq %rdi, %rcx
leaq _ZN8rust_out4main17hb497928495d48c40E(%rip), %rdi
movq %rcx, %rsi
movq %rax, %rdx
jmp _ZN3std2rt10lang_start17h14cbded5fe3cd915E@PLT
.Lfunc_end1:
.size main, .Lfunc_end1-main
.cfi_endproc
.section ".note.GNU-stack","",@progbits
这意味着生成的二进制文件中存在零差异(不仅仅是性能方面)。
更重要的是,如果你使用一个功能,它甚至不重要;以下方法:
fn main() {
let x = 2i64;
let y = 5i64;
let res = x + y;
}
也产生相同的组件。
最重要的是,一般来说,无论是在main()
还是在fn main() {}
之外声明它们,函数都会被内联。
编辑:正如Shepmaster指出的那样,在这个程序中没有任何副作用,因此两个变体生成的程序集实际上与以下部分相同:
main()
然而,两者的MIR输出也相同(并且与空白ENABLE_SOAP = TRUE
ALLOW_SOAP = *
SCHEDD_ARGS = -p 8080
ENABLE_WEB_SERVER = TRUE
WEB_ROOT_DIR=/usr/share/condor/webservice/
的MIR输出不同),因此即使副作用是来自功能位置也不应该有任何差异。本。