我正在使用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中启动了这个项目,但非常很快放弃了!)。
为什么这种核心语言功能在这种情况下要慢得多?随着我的项目变得越来越复杂,我应该考虑哪些其他优化措施?
答案 0 :(得分:3)
考虑在最里面的循环中完成的“有效负载”操作:
b
PORTB |= 1 << SDA
与PORTB &= ~(1 << SDA)
PORTB
这是循环的所有展开版本;没有其他事情需要做,甚至没有将1
转移到左j
次,因为编译器评估常量表达式,1 << 4
变为16
。
另一方面,没有展开的循环必须做其他事情来保持循环:
j
j
到8 1
位置移动j
当循环展开时,CPU不再需要这些“非有效负载”指令,因此执行速度会提高。
为什么这种核心语言功能在这种情况下要慢得多?
许多现代编译器会根据优化设置自动为您展开循环。