为什么嵌套for循环比展开相同代码要慢得多?

时间:2018-04-14 13:45:52

标签: c avr

我正在使用ATtiny85和128x64px OLED构建一个小玩具控制台。在我的初始构建中,我使用内置的shiftOut()digitalWrite()函数将显示数据移出到屏幕控制器。

这让我大约5fps,这有点令人失望。

我编写了自己的函数,它使用直接端口操作来发送数据并且速度大幅提升~23fps,这也不错。这就是这个功能:

void shift_out_block(block)
{
    byte b;
    for (byte i = 0; i < 8; i++)  
    {
        b = pgm_read_byte(block+i);

        for (byte j=0 ; j < 8 ; j++)
        {
            if ( !!( b & (1 << j)) )
            {
                PORTB |= 1 << SDA;
            }
            else
            {
                PORTB &= ~(1 << SDA);
            }

            PORTB |= 1 << SCL; // HIGH
            PORTB &= ~(1 << SCL); // LOW
        }
    }
}

23fps还可以,但它没有30甚至60fps(如果它是24fps,我实际上已经把它留在了这里,但奇数......)。

我理解为什么删除库调用和操作端口直接改进了很多东西 - 这些库被编写为适用于各种不同的MCU。

我模糊地记得循环解开是一个的东西,所以我解开了内部的for循环:

void shift_out_block()
{
    byte b;
    for (byte i = 0; i < 8; i++)  
    {
        b = pgm_read_byte(block+i);

        if ( !!( b & (1 << 0)) )
        {
            PORTB |= 1 << SDA;
        }
        else
        {
            PORTB &= ~(1 << SDA);
        }

        PORTB |= 1 << SCL; // HIGH
        PORTB &= ~(1 << SCL); // LOW

        if ( !!( b & (1 << 1)) )
        {
            PORTB |= 1 << SDA;
        }
        else
        {
            PORTB &= ~(1 << SDA);
        }

        PORTB |= 1 << SCL; // HIGH
        PORTB &= ~(1 << SCL); // LOW

        if ( !!( b & (1 << 2)) )
        {
            PORTB |= 1 << SDA;
        }
        else
        {
            PORTB &= ~(1 << SDA);
        }

        PORTB |= 1 << SCL; // HIGH
        PORTB &= ~(1 << SCL); // LOW

        if ( !!( b & (1 << 3)) )
        {
            PORTB |= 1 << SDA;
        }
        else
        {
            PORTB &= ~(1 << SDA);
        }

        PORTB |= 1 << SCL; // HIGH
        PORTB &= ~(1 << SCL); // LOW

        if ( !!( b & (1 << 4)) )
        {
            PORTB |= 1 << SDA;
        }
        else
        {
            PORTB &= ~(1 << SDA);
        }

        PORTB |= 1 << SCL; // HIGH
        PORTB &= ~(1 << SCL); // LOW

        if ( !!( b & (1 << 5)) )
        {
            PORTB |= 1 << SDA;
        }
        else
        {
            PORTB &= ~(1 << SDA);
        }

        PORTB |= 1 << SCL; // HIGH
        PORTB &= ~(1 << SCL); // LOW

        if ( !!( b & (1 << 6)) )
        {
            PORTB |= 1 << SDA;
        }
        else
        {
            PORTB &= ~(1 << SDA);
        }

        PORTB |= 1 << SCL; // HIGH
        PORTB &= ~(1 << SCL); // LOW

        if ( !!( b & (1 << 7)) )
        {
            PORTB |= 1 << SDA;
        }
        else
        {
            PORTB &= ~(1 << SDA);
        }

        PORTB |= 1 << SCL; // HIGH
        PORTB &= ~(1 << SCL); // LOW
    }
}

毫不费力,复制粘贴7次。给我近75fps - 原始函数在~42ms内执行,新的丑陋只需要~13ms。

出于兴趣,我将发送位部分作为一个单独的函数打破并调用了8次:

void shift_out_bit(bool bit)
{
    if ( bit )
    {
        PORTB |= 1 << SDA;
    }
    else
    {
        PORTB &= ~(1 << SDA);
    }

    PORTB |= 1 << SCL; // HIGH
    PORTB &= ~(1 << SCL); // LOW
}

void shift_out_block()
{
    byte b;
    for (byte i = 0; i < 8; i++)  
    {
        b = pgm_read_byte(block+i);

        shift_out_bit( !!( b & (1 << 0)) );
        shift_out_bit( !!( b & (1 << 1)) );
        shift_out_bit( !!( b & (1 << 2)) );
        shift_out_bit( !!( b & (1 << 3)) );
        shift_out_bit( !!( b & (1 << 4)) );
        shift_out_bit( !!( b & (1 << 5)) );
        shift_out_bit( !!( b & (1 << 6)) );
        shift_out_bit( !!( b & (1 << 7)) );
    }
}

〜22ms执行,或45.4545454545 fps,这甚至不是一个不错的数字。

在任何想象中我都不是C程序员 - Python是我惯常的困境(我最初在Python / RPi中启动了这个项目,但非常很快放弃了!)。

为什么这种核心语言功能在这种情况下要慢得多?随着我的项目变得越来越复杂,我应该考虑哪些其他优化措施?

1 个答案:

答案 0 :(得分:3)

考虑在最里面的循环中完成的“有效负载”操作:

  • 检查b
  • 中的特定位
  • 有条件跳转以处理PORTB |= 1 << SDAPORTB &= ~(1 << SDA)
  • 的对比
  • PORTB
  • 上的三项操作

这是循环的所有展开版本;没有其他事情需要做,甚至没有将1转移到左j次,因为编译器评估常量表达式,1 << 4变为16

另一方面,没有展开的循环必须做其他事情来保持循环:

  • 增加j
  • 比较j到8
  • 1位置移动j
  • 无条件跳转到循环的开头

当循环展开时,CPU不再需要这些“非有效负载”指令,因此执行速度会提高。

  

为什么这种核心语言功能在这种情况下要慢得多?

许多现代编译器会根据优化设置自动为您展开循环。