Как оптимизация компилятора ускоряет время между простыми операциями?

У меня есть встроенный проект 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);
    }
    

Обе оптимизации устраняют ряд (бухгалтерских) инструкций между фактическими записями в регистр и, таким образом, делают записи быстрее друг за другом.

Другие вопросы по тегам