Как оптимизация компилятора ускоряет время между простыми операциями?
У меня есть встроенный проект C++, который использует SPI. Когда я компилирую и запускаю свою программу без оптимизации (-O0), периферийное устройство (ЖК-панель) работает нормально. Когда я компилирую его с оптимизацией (-O1), периферийное устройство работает неправильно.
Я проверил оба случая с помощью логического анализатора, и единственное отличие состоит в том, что время между записываемыми байтами намного меньше с оптимизированным кодом (тактовая частота, записанные байты и т. Д. Идентичны). Как оптимизация компилятора может влиять на время между последующими операциями, которые просто записывают в аппаратный регистр одну за другой? Если я добавляю задержку после каждой команды записи в классе SPI, она работает в оптимизированном случае.
В отличие от приведенного ниже примера, в исходном коде WriteCommand()
а также WriteData()
через указатель.
Фрагмент кода, последовательная запись в периферийное устройство через SPI:
{
SPI m_spiPort();
m_spiPort.Init();
m_spiPort.WriteCommand(SLEEPOUT);
// Color Interface Pixel Format (command 0x3A)
m_spiPort.WriteCommand(COLMOD);
m_spiPort.WriteData(0x03); // 0x03 = 12 bits-per-pixel
// Memory access controller (command 0x36)
m_spiPort.WriteCommand(MADCTL);
m_spiPort.WriteData(0x00);
// Write contrast (command 0x25)
m_spiPort.WriteCommand(SETCON);
m_spiPort.WriteData(0x39); // contrast 0x30
// Display On (command 0x29)
m_spiPort.WriteCommand(DISPON);
}
Класс SPI:
class SPI {
public:
void Init();
void WriteCommand(unsigned int command);
void WriteData(unsigned int data);
private:
void Write(unsigned int value);
};
Реализация этого класса:
void SPI::WriteCommand(unsigned int command)
{
command &= ~0x100; //clear bit 8
Write(command);
}
void SPI::WriteData(unsigned int data)
{
data |= 0x100; //set bit 8
Write(data);
}
void SPI::Write(unsigned int value)
{
LPC_SSP->DR = value;
}
void SPI::Init( void )
{
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<11); //Enables clock for SPI
LPC_SYSCON->SSPCLKDIV = 0x01;
LPC_IOCON->PIO0_14 &= ~(0x7); // SCK
LPC_IOCON->PIO0_14 |= 0x2;
LPC_IOCON->PIO0_17 &= ~(0x7); // MOSI
LPC_IOCON->PIO0_17 |= 0x2;
/* SSP SSEL is a GPIO pin */
LPC_IOCON->PIO0_27 = 0x0; // configure as GPIO pin
LPC_GPIO0->MASK = (1<<27);
LPC_GPIO0->DIR |= (1<<27); // set in output mode */
LPC_GPIO0->CLR = 1 << 27;
/* Set DSS data to 9-bit, Frame format SPI, CPOL = 0, CPHA = 0, and SCR is 0 */
LPC_SSP->CR0 = 0x0008;
/* SSPCPSR clock prescale register, master mode, minimum divisor is 0x02 */
LPC_SSP->CPSR = 0x4; // SPI clock will run at 6 MHz
/* set Master mode and enable the SPI */
LPC_SSP->CR1 = 0x2;
}
Редактировать - удалить DelayInCycles() из SPI::Write(). Различия все еще очевидны без этого, и я не хотел включать это в этот пост.
1 ответ
Для каждой команды и байта данных ваш код вызывает две функции, и обе функции не имеют локальных переменных или временных переменных.
При правильной реализации каждая из этих функций создаст стековый фрейм (требуется несколько инструкций для настройки и разборки) для хранения любых локальных переменных и временных значений, которые не могут храниться в регистрах. Это, вероятно, то, что происходит в режиме компиляции -O0.
Две важные оптимизации, которые могут повлиять на время выполнения для такого кода:
- Пропуск фрейма стека: компилятор замечает, что фрейм стека
Write
(и, возможно, также дляWriteCommand
а такжеWriteData
) не используется и решает исключить инструкции по настройке (и удалению) стекового фрейма. Функция встраивания: как
Write
,WriteCommand
а такжеWriteData
Все эти функции очень просты, и компилятор может решить полностью исключить вызовы функций и сгенерировать код, как если бы вы написали (не считая проблем с доступностью):{ SPI m_spiPort(); m_spiPort.Init(); m_spiPort.LPC_SSP->DR = (SLEEPOUT & ~0x100); // Color Interface Pixel Format (command 0x3A) m_spiPort.LPC_SSP->DR = (COLMOD & ~0x100); m_spiPort.LPC_SSP->DR = (0x03 & 0x100); // Memory access controller (command 0x36) m_spiPort.LPC_SSP->DR = (MADCTL & ~0x100); m_spiPort.LPC_SSP->DR = (0x00 & 0x100); // Write contrast (command 0x25) m_spiPort.LPC_SSP->DR = (SETCON & ~0x100); m_spiPort.LPC_SSP->DR = (0x39 & 0x100); // Display On (command 0x29) m_spiPort.LPC_SSP->DR = (DISPON & ~0x100); }
Обе оптимизации устраняют ряд (бухгалтерских) инструкций между фактическими записями в регистр и, таким образом, делают записи быстрее друг за другом.