如何找到编译器优化的内容?
How to find what was optimized by compiler?
我的代码有问题。如果我用 -O0 或 -Og 编译它似乎工作正常。
但如果我使用任何其他标志,如 -Os、-O1 等,它就不起作用。如何找到编译器优化的内容?
编译器arm-none-eabi-g++ 8.3.1
源代码:https://github.com/bielu000/stm32-libopencm3/tree/uart_not_working_version
我将 link 添加到 repo,因为很难将整个代码放在这里。
主要代码:src/app/src
我认为问题出在 server_run 函数中。
看看屏幕。
左侧(有效)->
optimization: -O1,
attribute((optimize("-O0"))) void server_run();
右侧(无效)->
optimization: -O1,
void server_run();
我在优化(正确)版本中没有看到任何获取缓冲区容量的调用。但是为什么?
函数体
extern "C" {
#include <libopencm3/stm32/usart.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/cm3/nvic.h>
}
#include <server.hpp>
#include <stdint.h>
#include <ring_buffer.hpp>
#include <Os.hpp>
#include <Timer.hpp>
#include <target.h>
static uint8_t w_buffer[1024]; // write buffer
static uint8_t r_buffer[1024]; // read buffer
utils::containers::RingBuffer write_rb{w_buffer, sizeof(w_buffer)};
utils::containers::RingBuffer read_rb{r_buffer, sizeof(r_buffer)};
static void sendData()
{
if (write_rb.capacity() != 0)
{
usart_send(USART1, write_rb.read());
usart_enable_tx_interrupt(USART1);
}
else
{
usart_disable_tx_interrupt(USART1);
}
}
static void readData()
{
auto data = usart_recv(USART1);
read_rb.write(static_cast<uint8_t>(data));
}
void server_init()
{
//RCC
rcc_periph_clock_enable(RCC_USART1);
//GPIO
gpio_set_mode(GPIO_BANK_USART1_TX, GPIO_MODE_OUTPUT_50_MHZ,
GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO_USART1_TX);
gpio_set_mode(GPIO_BANK_USART1_RX, GPIO_MODE_INPUT,
GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_USART1_RX);
//USART
usart_set_mode(USART1, USART_MODE_TX_RX);
usart_set_baudrate(USART1, 9600);
usart_set_parity(USART1, USART_PARITY_NONE);
usart_set_databits(USART1, 8);
usart_set_stopbits(USART1, 1);
usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);
usart_enable_rx_interrupt(USART1);
//ISR
nvic_enable_irq(NVIC_USART1_IRQ);
//Enable
usart_enable(USART1);
}
void server_run()
{
while(true)
{
size_t xsize = read_rb.capacity();
if (xsize >= 64)
{
while (read_rb.capacity() != 0)
{
write_rb.write(read_rb.read());
}
sendData();
}
}
}
void usart1_isr()
{
if (usart_get_flag(USART1, USART_FLAG_TXE) != 0)
{
sendData();
}
if (usart_get_flag(USART1, USART_FLAG_RXNE) != 0) // when data is ready to read
{
readData();
}
}
更新:
我将 xsize 变量类型更改为
std::atomic<size_t> xsize = read_rb.capacity();
现在它甚至可以与 -Os 一起使用。但是为什么?
通常只在禁用优化的情况下工作的代码是未定义行为的标志:允许编译器做出您违反的假设。它并不总是在禁用优化的情况下利用这些假设,例如每个变量都被处理成 volatile
这样的东西,所以 strict-aliasing 和数据竞争 UB 很少是禁用优化的问题。例如忘记对共享变量使用 atomic<T>
通常只会导致优化问题,除非您使用像 ++
这样的 RMW 操作。 MCU programming - C++ O2 optimization breaks while loop
显然编译时出现完整警告 (-Wall -Wextra
); compile-time-visible UB 经常会被编译器注意到并发出警告,特别是在它放弃并假设 code-path 无法访问的情况下,因为它遇到 UB,甚至不发出 return 沿该路径的说明。
但是如果你真的想要你问的字面问题的答案(什么被优化/优化掉),而不是隐式调试问题:
对于此类一般问题的唯一答案是非常一般的答案:比较编译器的 asm 输出,或编译器对程序逻辑的内部表示。
比较 asm 文本输出很困难,因为寄存器分配选择的微小差异可能会使整个函数中的每条指令使用不同的寄存器。
因此,更好的选择可能是让 GCC 打印出其 GIMPLE 代码表示,即它用于表示大多数优化程序的形式。在某些阶段,它甚至可以将其转储为 C-like 形式。
例如对于这个例子,除了 z
被优化掉 (constant-propagation):
之外,我不确定它是否展示了关于优化的任何有趣的东西
int foo(int x) {
int z = 1;
int y = x * 2 + z;
return y;
}
使用 ARM(none) gcc 8.3.1 on Godbolt 中的 -O1
(它有一个 GIMPLE 树查看器)我们得到这个 asm 输出
foo(int):
lsl r0, r0, #1
add r0, r0, #1
bx lr
优化通过后的 GIMPLE 输出 232t.optimized:
;; Function foo (_Z3fooi, funcdef_no=0, decl_uid=4625, cgraph_uid=0, symbol_order=0)
foo (int x)
{
int y;
int _1;
<bb 2> [local count: 1073741825]:
# DEBUG BEGIN_STMT
# DEBUG z => 1
# DEBUG BEGIN_STMT
_1 = x_2(D) * 2;
y_3 = _1 + 1;
# DEBUG y => y_3
# DEBUG BEGIN_STMT
return y_3;
}
优化后的 RTL 输出 312r.final:
;; Function foo (_Z3fooi, funcdef_no=0, decl_uid=4625, cgraph_uid=0, symbol_order=0)
foo
Dataflow summary:
;; invalidated by call 0 [r0] 1 [r1] 2 [r2] 3 [r3] 12 [ip] 14 [lr] 15 [pc] 16 [s0] 17 [s1] 18 [s2] 19 [s3] 20 [s4] 21 [s5] 22 [s6] 23 [s7] 24 [s8] 25 [s9] 26 [s10] 27 [s11] 28 [s12] 29 [s13] 30 [s14] 31 [s15] 32 [s16] 33 [s17] 34 [s18] 35 [s19] 36 [s20] 37 [s21] 38 [s22] 39 [s23] 40 [s24] 41 [s25] 42 [s26] 43 [s27] 44 [s28] 45 [s29] 46 [s30] 47 [s31] 48 [d16] 49 [?16] 50 [d17] 51 [?17] 52 [d18] 53 [?18] 54 [d19] 55 [?19] 56 [d20] 57 [?20] 58 [d21] 59 [?21] 60 [d22] 61 [?22] 62 [d23] 63 [?23] 64 [d24] 65 [?24] 66 [d25] 67 [?25] 68 [d26] 69 [?26] 70 [d27] 71 [?27] 72 [d28] 73 [?28] 74 [d29] 75 [?29] 76 [d30] 77 [?30] 78 [d31] 79 [?31] 80 [wr0] 81 [wr1] 82 [wr2] 83 [wr3] 84 [wr4] 85 [wr5] 86 [wr6] 87 [wr7] 88 [wr8] 89 [wr9] 90 [wr10] 91 [wr11] 92 [wr12] 93 [wr13] 94 [wr14] 95 [wr15] 96 [wcgr0] 97 [wcgr1] 98 [wcgr2] 99 [wcgr3] 100 [cc] 101 [vfpcc]
;; hardware regs used 13 [sp]
;; regular block artificial uses 13 [sp]
;; eh block artificial uses 13 [sp] 103 [afp]
;; entry block defs 0 [r0] 1 [r1] 2 [r2] 3 [r3] 13 [sp] 14 [lr]
;; exit block uses 0 [r0] 13 [sp] 14 [lr]
;; regs ever live 0 [r0]
;; ref usage r0={3d,4u} r1={1d} r2={1d} r3={1d} r13={1d,2u} r14={1d,1u}
;; total ref usage 15{8d,7u,0e} in 4{4 regular + 0 call} insns.
(note 1 0 28 NOTE_INSN_DELETED)
(note 28 1 4 (var_location x (reg:SI 0 r0 [ x ])) NOTE_INSN_VAR_LOCATION)
(note 4 28 21 [bb 2] NOTE_INSN_BASIC_BLOCK)
(note 21 4 2 NOTE_INSN_PROLOGUE_END)
(note 2 21 3 NOTE_INSN_DELETED)
(note 3 2 25 NOTE_INSN_FUNCTION_BEG)
(note 25 3 29 ./example.cpp:2 NOTE_INSN_BEGIN_STMT)
(note 29 25 26 (var_location z (const_int 1 [0x1])) NOTE_INSN_VAR_LOCATION)
(note 26 29 30 ./example.cpp:3 NOTE_INSN_BEGIN_STMT)
(note 30 26 27 (var_location y (plus:SI (ashift:SI (reg:SI 0 r0 [ x ])
(const_int 1 [0x1]))
(const_int 1 [0x1]))) NOTE_INSN_VAR_LOCATION)
(note 27 30 11 ./example.cpp:4 NOTE_INSN_BEGIN_STMT)
(insn 11 27 31 (set (reg:SI 0 r0 [114])
(ashift:SI (reg:SI 0 r0 [ x ])
(const_int 1 [0x1]))) "./example.cpp":3 129 {*arm_shiftsi3}
(nil))
(note 31 11 32 (var_location y (plus:SI (ashift:SI (entry_value:SI (reg:SI 0 r0 [ x ]))
(const_int 1 [0x1]))
(const_int 1 [0x1]))) NOTE_INSN_VAR_LOCATION)
(note 32 31 12 (var_location x (entry_value:SI (reg:SI 0 r0 [ x ]))) NOTE_INSN_VAR_LOCATION)
(note 12 32 17 NOTE_INSN_DELETED)
(insn 17 12 33 (set (reg/i:SI 0 r0)
(plus:SI (reg:SI 0 r0 [114])
(const_int 1 [0x1]))) "./example.cpp":5 4 {*arm_addsi3}
(nil))
(note 33 17 18 (var_location y (reg/i:SI 0 r0)) NOTE_INSN_VAR_LOCATION)
(insn 18 33 22 (use (reg/i:SI 0 r0)) "./example.cpp":5 -1
(nil))
(note 22 18 23 NOTE_INSN_EPILOGUE_BEG)
(jump_insn 23 22 24 (return) "./example.cpp":5 220 {*arm_return}
(nil)
-> return)
(barrier 24 23 20)
(note 20 24 0 NOTE_INSN_DELETED)
如果您想真正了解“GCC 优化了什么”,您最好重温一下 GIMPLE and/or RTL。 (GCC 内部手册:https://gcc.gnu.org/onlinedocs/gccint/GIMPLE.html)
我不会用 -O0
的 GIMPLE 和 RTL 输出来混淆答案,但你可以(我认为)在 Godbolt 上设置 2 个编译器面板,这样你就可以区分它们。
我的代码有问题。如果我用 -O0 或 -Og 编译它似乎工作正常。
但如果我使用任何其他标志,如 -Os、-O1 等,它就不起作用。如何找到编译器优化的内容?
编译器arm-none-eabi-g++ 8.3.1
源代码:https://github.com/bielu000/stm32-libopencm3/tree/uart_not_working_version
我将 link 添加到 repo,因为很难将整个代码放在这里。 主要代码:src/app/src
我认为问题出在 server_run 函数中。
看看屏幕。
左侧(有效)->
optimization: -O1,
attribute((optimize("-O0"))) void server_run();
右侧(无效)->
optimization: -O1,
void server_run();
我在优化(正确)版本中没有看到任何获取缓冲区容量的调用。但是为什么?
函数体
extern "C" {
#include <libopencm3/stm32/usart.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/cm3/nvic.h>
}
#include <server.hpp>
#include <stdint.h>
#include <ring_buffer.hpp>
#include <Os.hpp>
#include <Timer.hpp>
#include <target.h>
static uint8_t w_buffer[1024]; // write buffer
static uint8_t r_buffer[1024]; // read buffer
utils::containers::RingBuffer write_rb{w_buffer, sizeof(w_buffer)};
utils::containers::RingBuffer read_rb{r_buffer, sizeof(r_buffer)};
static void sendData()
{
if (write_rb.capacity() != 0)
{
usart_send(USART1, write_rb.read());
usart_enable_tx_interrupt(USART1);
}
else
{
usart_disable_tx_interrupt(USART1);
}
}
static void readData()
{
auto data = usart_recv(USART1);
read_rb.write(static_cast<uint8_t>(data));
}
void server_init()
{
//RCC
rcc_periph_clock_enable(RCC_USART1);
//GPIO
gpio_set_mode(GPIO_BANK_USART1_TX, GPIO_MODE_OUTPUT_50_MHZ,
GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO_USART1_TX);
gpio_set_mode(GPIO_BANK_USART1_RX, GPIO_MODE_INPUT,
GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_USART1_RX);
//USART
usart_set_mode(USART1, USART_MODE_TX_RX);
usart_set_baudrate(USART1, 9600);
usart_set_parity(USART1, USART_PARITY_NONE);
usart_set_databits(USART1, 8);
usart_set_stopbits(USART1, 1);
usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);
usart_enable_rx_interrupt(USART1);
//ISR
nvic_enable_irq(NVIC_USART1_IRQ);
//Enable
usart_enable(USART1);
}
void server_run()
{
while(true)
{
size_t xsize = read_rb.capacity();
if (xsize >= 64)
{
while (read_rb.capacity() != 0)
{
write_rb.write(read_rb.read());
}
sendData();
}
}
}
void usart1_isr()
{
if (usart_get_flag(USART1, USART_FLAG_TXE) != 0)
{
sendData();
}
if (usart_get_flag(USART1, USART_FLAG_RXNE) != 0) // when data is ready to read
{
readData();
}
}
更新:
我将 xsize 变量类型更改为
std::atomic<size_t> xsize = read_rb.capacity();
现在它甚至可以与 -Os 一起使用。但是为什么?
通常只在禁用优化的情况下工作的代码是未定义行为的标志:允许编译器做出您违反的假设。它并不总是在禁用优化的情况下利用这些假设,例如每个变量都被处理成 volatile
这样的东西,所以 strict-aliasing 和数据竞争 UB 很少是禁用优化的问题。例如忘记对共享变量使用 atomic<T>
通常只会导致优化问题,除非您使用像 ++
这样的 RMW 操作。 MCU programming - C++ O2 optimization breaks while loop
显然编译时出现完整警告 (-Wall -Wextra
); compile-time-visible UB 经常会被编译器注意到并发出警告,特别是在它放弃并假设 code-path 无法访问的情况下,因为它遇到 UB,甚至不发出 return 沿该路径的说明。
但是如果你真的想要你问的字面问题的答案(什么被优化/优化掉),而不是隐式调试问题:
对于此类一般问题的唯一答案是非常一般的答案:比较编译器的 asm 输出,或编译器对程序逻辑的内部表示。
比较 asm 文本输出很困难,因为寄存器分配选择的微小差异可能会使整个函数中的每条指令使用不同的寄存器。
因此,更好的选择可能是让 GCC 打印出其 GIMPLE 代码表示,即它用于表示大多数优化程序的形式。在某些阶段,它甚至可以将其转储为 C-like 形式。
例如对于这个例子,除了 z
被优化掉 (constant-propagation):
int foo(int x) {
int z = 1;
int y = x * 2 + z;
return y;
}
使用 ARM(none) gcc 8.3.1 on Godbolt 中的 -O1
(它有一个 GIMPLE 树查看器)我们得到这个 asm 输出
foo(int):
lsl r0, r0, #1
add r0, r0, #1
bx lr
优化通过后的 GIMPLE 输出 232t.optimized:
;; Function foo (_Z3fooi, funcdef_no=0, decl_uid=4625, cgraph_uid=0, symbol_order=0)
foo (int x)
{
int y;
int _1;
<bb 2> [local count: 1073741825]:
# DEBUG BEGIN_STMT
# DEBUG z => 1
# DEBUG BEGIN_STMT
_1 = x_2(D) * 2;
y_3 = _1 + 1;
# DEBUG y => y_3
# DEBUG BEGIN_STMT
return y_3;
}
优化后的 RTL 输出 312r.final:
;; Function foo (_Z3fooi, funcdef_no=0, decl_uid=4625, cgraph_uid=0, symbol_order=0)
foo
Dataflow summary:
;; invalidated by call 0 [r0] 1 [r1] 2 [r2] 3 [r3] 12 [ip] 14 [lr] 15 [pc] 16 [s0] 17 [s1] 18 [s2] 19 [s3] 20 [s4] 21 [s5] 22 [s6] 23 [s7] 24 [s8] 25 [s9] 26 [s10] 27 [s11] 28 [s12] 29 [s13] 30 [s14] 31 [s15] 32 [s16] 33 [s17] 34 [s18] 35 [s19] 36 [s20] 37 [s21] 38 [s22] 39 [s23] 40 [s24] 41 [s25] 42 [s26] 43 [s27] 44 [s28] 45 [s29] 46 [s30] 47 [s31] 48 [d16] 49 [?16] 50 [d17] 51 [?17] 52 [d18] 53 [?18] 54 [d19] 55 [?19] 56 [d20] 57 [?20] 58 [d21] 59 [?21] 60 [d22] 61 [?22] 62 [d23] 63 [?23] 64 [d24] 65 [?24] 66 [d25] 67 [?25] 68 [d26] 69 [?26] 70 [d27] 71 [?27] 72 [d28] 73 [?28] 74 [d29] 75 [?29] 76 [d30] 77 [?30] 78 [d31] 79 [?31] 80 [wr0] 81 [wr1] 82 [wr2] 83 [wr3] 84 [wr4] 85 [wr5] 86 [wr6] 87 [wr7] 88 [wr8] 89 [wr9] 90 [wr10] 91 [wr11] 92 [wr12] 93 [wr13] 94 [wr14] 95 [wr15] 96 [wcgr0] 97 [wcgr1] 98 [wcgr2] 99 [wcgr3] 100 [cc] 101 [vfpcc]
;; hardware regs used 13 [sp]
;; regular block artificial uses 13 [sp]
;; eh block artificial uses 13 [sp] 103 [afp]
;; entry block defs 0 [r0] 1 [r1] 2 [r2] 3 [r3] 13 [sp] 14 [lr]
;; exit block uses 0 [r0] 13 [sp] 14 [lr]
;; regs ever live 0 [r0]
;; ref usage r0={3d,4u} r1={1d} r2={1d} r3={1d} r13={1d,2u} r14={1d,1u}
;; total ref usage 15{8d,7u,0e} in 4{4 regular + 0 call} insns.
(note 1 0 28 NOTE_INSN_DELETED)
(note 28 1 4 (var_location x (reg:SI 0 r0 [ x ])) NOTE_INSN_VAR_LOCATION)
(note 4 28 21 [bb 2] NOTE_INSN_BASIC_BLOCK)
(note 21 4 2 NOTE_INSN_PROLOGUE_END)
(note 2 21 3 NOTE_INSN_DELETED)
(note 3 2 25 NOTE_INSN_FUNCTION_BEG)
(note 25 3 29 ./example.cpp:2 NOTE_INSN_BEGIN_STMT)
(note 29 25 26 (var_location z (const_int 1 [0x1])) NOTE_INSN_VAR_LOCATION)
(note 26 29 30 ./example.cpp:3 NOTE_INSN_BEGIN_STMT)
(note 30 26 27 (var_location y (plus:SI (ashift:SI (reg:SI 0 r0 [ x ])
(const_int 1 [0x1]))
(const_int 1 [0x1]))) NOTE_INSN_VAR_LOCATION)
(note 27 30 11 ./example.cpp:4 NOTE_INSN_BEGIN_STMT)
(insn 11 27 31 (set (reg:SI 0 r0 [114])
(ashift:SI (reg:SI 0 r0 [ x ])
(const_int 1 [0x1]))) "./example.cpp":3 129 {*arm_shiftsi3}
(nil))
(note 31 11 32 (var_location y (plus:SI (ashift:SI (entry_value:SI (reg:SI 0 r0 [ x ]))
(const_int 1 [0x1]))
(const_int 1 [0x1]))) NOTE_INSN_VAR_LOCATION)
(note 32 31 12 (var_location x (entry_value:SI (reg:SI 0 r0 [ x ]))) NOTE_INSN_VAR_LOCATION)
(note 12 32 17 NOTE_INSN_DELETED)
(insn 17 12 33 (set (reg/i:SI 0 r0)
(plus:SI (reg:SI 0 r0 [114])
(const_int 1 [0x1]))) "./example.cpp":5 4 {*arm_addsi3}
(nil))
(note 33 17 18 (var_location y (reg/i:SI 0 r0)) NOTE_INSN_VAR_LOCATION)
(insn 18 33 22 (use (reg/i:SI 0 r0)) "./example.cpp":5 -1
(nil))
(note 22 18 23 NOTE_INSN_EPILOGUE_BEG)
(jump_insn 23 22 24 (return) "./example.cpp":5 220 {*arm_return}
(nil)
-> return)
(barrier 24 23 20)
(note 20 24 0 NOTE_INSN_DELETED)
如果您想真正了解“GCC 优化了什么”,您最好重温一下 GIMPLE and/or RTL。 (GCC 内部手册:https://gcc.gnu.org/onlinedocs/gccint/GIMPLE.html)
我不会用 -O0
的 GIMPLE 和 RTL 输出来混淆答案,但你可以(我认为)在 Godbolt 上设置 2 个编译器面板,这样你就可以区分它们。