C++ 如何确保某些代码得到优化?
tl;dr:是否可以以某种方式(例如,通过编写单元测试)确保某些东西得到优化,例如,整个循环 通常的方法是用C++ 如何确保某些代码得到优化?,c++,unit-testing,c++11,compiler-optimization,C++,Unit Testing,C++11,Compiler Optimization,tl;dr:是否可以以某种方式(例如,通过编写单元测试)确保某些东西得到优化,例如,整个循环 通常的方法是用#if…#endif包装生产构建中不包含的内容。但我更喜欢用C++机制来代替。即使在那里,我也不喜欢复杂的模板专门化,我喜欢保持实现的简单,并争辩说“嘿,编译器无论如何都会优化这个” 上下文是嵌入在汽车软件中的(二进制大小问题),编译器通常很差。它们在安全性方面得到认证,但在优化方面通常不太好 示例1:在容器中,元素的销毁通常是一个循环: for(size_t i = 0; i<el
#if…#endif
包装生产构建中不包含的内容。但我更喜欢用C++机制来代替。即使在那里,我也不喜欢复杂的模板专门化,我喜欢保持实现的简单,并争辩说“嘿,编译器无论如何都会优化这个”
上下文是嵌入在汽车软件中的(二进制大小问题),编译器通常很差。它们在安全性方面得到认证,但在优化方面通常不太好
示例1:在容器中,元素的销毁通常是一个循环:
for(size_t i = 0; i<elements; i++)
buffer[i].~T();
我可以看看拆卸,当然。但作为一个上游平台软件,很难控制所有的目标、编译器及其可能使用的选项。人们担心下游项目由于任何原因会出现代码膨胀或性能问题
底线:是否有可能以某种方式编写软件,即已知某些代码可以像那样以安全的方式进行优化?或者一个单元测试,如果优化不符合预期,它会失败
[对于第一个问题,我想到了计时测试,但作为裸机,我还没有方便的工具。]可能有一种更优雅的方法,它不是单元测试,但如果您只是寻找特定的字符串,并且可以使其独特
strings $COMPILED_BINARY | grep "Arg was"
如果字符串为包含< /p> 代码>,如果CONSTEPRP</C>是这种测试的规范C++表达式(C++ 17)。< /P>
constexpr bool DEBUG = /*...*/;
int main() {
if constexpr(DEBUG) {
std::cerr << "We are in debugging mode!" << std::endl;
}
}
constexpr bool DEBUG=/*…*/;
int main(){
if constexpr(调试){
STR::CURR 查看你的问题,我看到其中的几个(子)问题需要回答。不是所有的答案都可能是你的裸机编译器,因为硬件厂商不太关心C++。
第一个问题是:我如何以一种我确信会得到优化的方式编写代码。这里最明显的答案是将所有内容都放在一个编译单元中,以便调用者可以看到实现
第二个问题是:如何强制编译器进行优化。这里的constepr
是一个祝福。根据您是否支持C++11、C++14、C++17甚至即将推出的C++20,您将获得不同的功能集,说明您可以在constepr函数中执行哪些操作。用法:
constexpr char c = std::string_view{"my_very_long_string"}[7];
使用上面的代码,c
被定义为一个constexpr变量。因为您将其应用于变量,所以需要一些东西:
- 您的编译器应该优化代码,以便在编译时知道
c
的值。这甚至适用于-O0版本
- 用于计算
c
的所有函数都是constexpr且可用。(根据结果,强制执行第一个问题的行为)
- 在计算
c
时,不允许触发任何未定义的行为(对于给定值)
消极的一面是:您的输入需要在编译时被知道
C++17还提供了if constexpr
,它具有类似的要求:需要在编译时计算条件。结果是不允许编译代码的一个分支(因为它甚至可以包含对您正在使用的类型不起作用的元素)
这就引出了一个问题:即使我的编译器运行不好,我如何确保足够的优化使我的程序运行得足够快。这里唯一相关的答案是:创建基准测试并比较结果。努力设置一个CI作业,为您实现自动化。(是的,你甚至可以使用外部硬件,虽然不是那么容易)最后,你有一些要求:处理A应该不到X秒。做几次,然后计时。即使他们不能处理所有事情,只要在要求范围内,就可以了
注意:由于这是关于调试的,您很可能也可以跟踪可执行文件的大小。一旦您开始使用流,大量转换为字符串…您的exe大小将增加。(您会发现这是一个祝福,因为您将立即发现提交,它将使图像大小增加10%)
最后一个问题是:你有一个有缺陷的编译器,它不符合我的要求。这里唯一的答案是:替换它。最后,你可以使用任何编译器将你的代码编译成裸机,只要链接器脚本可以工作。如果你需要一个开始,它会让你很好地了解使用不同的编译器需要什么。(比如最近的Clang或GCC,如果优化不够好,你甚至可以在上面记录bug)这不是对“如何确保某些代码被优化掉?”的回答,而是对你的总结行“是否可以编写一个单元测试,例如,整个循环被优化掉?”
首先,答案取决于您对单元测试范围的了解程度——因此,如果您进行性能测试,您可能会有机会
相比之下,如果您将单元测试理解为测试代码功能行为的一种方法,那么您就没有。首先,优化(如果编译器工作正常)不会改变符合标准的代码的行为
对于不正确的代码(具有未定义行为的代码),优化器可以做他们想做的事情。(对于具有未定义行为的代码,编译器也可以在非优化情况下这样做,但有时只有在优化过程中形成的更深入的分析才有可能让编译器检测到某些代码具有未定义行为。)因此,如果您为某段行为未定义的代码编写单元测试,那么在运行优化和未优化的测试时,测试结果可能会有所不同。但是,严格地说,这只会告诉您编译器以不同的方式翻译了两次代码-这并不能保证代码是正确的
constexpr char c = std::string_view{"my_very_long_string"}[7];
extern void nop();
constexpr bool isDebugging = false; // somehow a global flag
void foo(int arg) {
if( isDebugging ) {
nop();
std::cout << "Arg was " << arg << std::endl; // may not appear in production binary!
}
// normal code here...
}
void nop() {}
before:
for(size_t i = 0; i<elements; i++)
buffer[i].~T();
behind:
if (intptr_t(&&behind) != intptr_t(&&before)) abort();
constexpr bool isDebugging = false; // somehow a global flag
void foo(int arg) {
if( isDebugging ) {
asm(".globl _marker\n_marker:\n");
std::cout << "Arg was " << arg << std::endl; // may not appear in production binary!
}
// normal code here...
}
constexpr bool isDebugging = false; // somehow a global flag
void foo(int arg) {
if( isDebugging ) {
asm("_marker=1\n");
std::cout << "Arg was " << arg << std::endl; // may not appear in production binary!
}
asm volatile (
".ifdef _marker\n"
".err \"code not optimised away\"\n"
".endif\n"
);
// normal code here...
}