[英]Why is this nested for loop so much slower than unrolling the same code?
我正在使用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这个项目,但很快就放弃了这一点!)。
为什么这种核心语言功能在这种情况下要慢得多? 随着我的项目变得更加复杂,我应该考虑哪些其他优化措施?
考虑在最内层循环内完成的“有效负载”操作:
b
中的特定位 PORTB |= 1 << SDA
与PORTB &= ~(1 << SDA)
PORTB
三个操作 这就是循环的所有展开版本; 没有别的需要做,甚至没有转移1
向左j
次,因为编译器计算常量表达式,而1 << 4
变得简单16
。
另一方面,没有展开的循环必须做额外的事情来保持循环:
j
j
到8 1
由左j
位置 当循环展开时,CPU不再需要这些“非有效负载”指令,因此执行速度会提高。
为什么这种核心语言功能在这种情况下要慢得多?
许多现代编译器会根据优化设置自动为您展开循环。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.