Rust 与 C/C++ 混合编程:如何控制 LTO 导出函数?
2024-07-17 04:59:24
在 Rust 与 C/C++ 代码进行 LTO 时如何控制导出函数的可见性
在追求极致性能的道路上,混合语言编程往往是不可避免的选择。将 Rust 和 C/C++ 代码链接在一起,取长补短,可以实现 1+1>2 的效果。而为了将性能压榨到极致,链接时优化 (LTO) 就成了必不可少的利器。LTO 可以跨越语言的边界,将函数内联到调用方,最大程度地减少函数调用开销。
然而,在享受 LTO 带来的性能红利的同时,我们也需要面对一个棘手的问题:如何控制导出函数的可见性。
试想一下,我们有一个 Rust 库,其中包含许多函数,但只想将其中一部分暴露给 C/C++ 代码使用。如果将所有函数都导出为公共符号,不仅会增加库的体积,还会影响 LTO 的效果。因为链接器无法对公共符号进行内联优化,毕竟它不知道其他库是否会依赖这些符号。
那么,有没有一种方法,既可以将 Rust 函数暴露给 C/C++ 代码使用,又能利用 LTO 进行内联优化呢?答案是肯定的,我们可以借助链接器版本脚本和 #[link_section]
属性来实现这一目标。
链接器版本脚本:掌控符号可见性的利器
链接器版本脚本,如同它的名字一样,是一个用于控制链接过程的脚本文件。通过它,我们可以精细地控制最终生成库中的符号可见性。
想象一下,链接器版本脚本就像是一个过滤器,它可以将符号分为“全局可见”和“局部可见”两类。只有被标记为“全局可见”的符号才会被导出到最终的库文件中,而“局部可见”的符号则会被隐藏起来,仅供库内部使用。
{
global:
rust_function_to_export;
local:
*;
};
以上就是一个简单的版本脚本示例。它将 rust_function_to_export
函数标记为“全局可见”,而其他所有符号,包括那些使用 #[no_mangle]
或 #[export_name]
标记的 Rust 函数,都会被视为“局部可见”。
#[link_section]
属性:将函数归类到特定段
光有版本脚本还不够,我们还需要使用 #[link_section]
属性将 Rust 函数放入特定的段中。
段,是 ELF 文件中的一个基本概念,它就像是一个个容器,用于存放不同类型的代码或数据。链接器在链接时,会根据段名将不同目标文件中的代码或数据合并在一起。
#[link_section = "my_rust_functions"]
#[no_mangle]
pub fn rust_function_to_export() {
// ...
}
#[link_section = "my_rust_functions"]
#[no_mangle]
fn another_rust_function() {
// ...
}
以上代码中,rust_function_to_export
和 another_rust_function
都被放入了名为 .my_rust_functions
的段中。
构建脚本:将版本脚本与代码联系起来
最后,我们需要在构建脚本中将版本脚本链接到最终的共享库。
rustc --crate-type=cdylib -C link-arg=-Wl,--version-script=version.lds ...
这行命令告诉链接器,在链接时使用 version.lds
文件作为版本脚本。
通过以上三个步骤,我们就可以精细地控制 Rust 导出函数的可见性,将需要暴露给 C/C++ 代码的函数导出为公共符号,同时将其他函数隐藏起来,以便 LTO 进行内联优化。
常见问题解答
-
为什么不能直接使用
#[no_mangle]
将所有函数都导出?如果将所有函数都导出为公共符号,会导致库文件体积增大,同时也会影响 LTO 的效果。因为链接器无法对公共符号进行内联优化,因为它不知道其他库是否会依赖这些符号。
-
链接器版本脚本应该放在哪里?
链接器版本脚本一般放在项目的根目录下,与 Cargo.toml 文件在同一级目录。
-
除了控制函数可见性,链接器版本脚本还能做什么?
链接器版本脚本的功能非常强大,它还可以用于控制符号版本、定义弱符号、排除特定符号等。
-
除了
#[link_section]
,还有其他方法可以将函数放入特定段吗?可以使用
#[cfg_attr(target_os = "...", link_section = "...")]
来根据目标操作系统选择不同的段。 -
如何验证 LTO 是否生效?
可以使用
objdump -d
命令查看目标文件的汇编代码,如果看到函数被内联了,就说明 LTO 生效了。
希望这篇文章能够帮助你更好地理解如何在 Rust 与 C/C++ 代码进行 LTO 时控制导出函数的可见性。如果你有任何问题或建议,欢迎在评论区留言。