我在存储在程序内存中的几个长度约为30-200的struct数组中进行搜索,线性搜索如下所示:
Glyph getGlyph(Font font, uint16_t code) {
for (size_t i = 0; i < font.length; i++) {
if (pgm_read_word(&font.glyphs[i].code) == code) {
static Glyph glyph;
memcpy_P(&glyph, &font.glyphs[i], sizeof (Glyph));
return glyph;
}
}
// return question mark if unknown code point
return getGlyph(font, 0x003f);
}
假设我正确地阅读了反汇编,每次迭代有20条指令,据我所知,其中3条指令相当昂贵:brcc
、brne
和rjmp
。
只是评估这是否可以更有效地完成(功率),我实现了二进制搜索:
Glyph getGlyph(Font font, uint16_t code) {
// https://en.wikipedia.org/wiki/Binary_search_algorithm
int16_t l = 0;
int16_t r = font.length - 1;
while (l <= r) {
uint8_t m = (l + r) / 2;
uint16_t c = pgm_read_word(&font.glyphs[m].code);
if (c < code) {
l = m + 1;
} else if (c > code) {
r = m - 1;
} else {
// found code point, get and return glyph
static Glyph glyph;
memcpy_P(&glyph, &font.glyphs[m], sizeof (Glyph));
return glyph;
}
}
// return question mark if unknown code point
return getGlyph(font, 0x003f);
}
这导致反汇编中每次迭代有35条指令,其中5条我认为相当昂贵:brlt
、brcc
(2x)和rjmp
(2x)。
考虑到数组相当短,我想二分搜索的好处并不是很多,可能会被增加的复杂性所补偿?
它是否值得或是否有意义,在所有试图基准这一点?
程序是用-Os
编译的。按照注解中的建议使用-O3
编译,看起来指令的数量是一个倍数,但也许我只是没有正确解释反汇编。它运行在ATmega 328 p上,我目前正在用gcc 5.4.0和avr-libc 2.0构建它。我也试过gcc 13和avr-libc 2.1,但我发现反汇编很难阅读。
这只是一个simple thermometer and hygrometer with an e-ink display,它运行良好,我很满意的速度和功耗,虽然我缺乏经验,不能与其他任何东西相比。我真的只是好奇,想学习。
3条答案
按热度按时间8mmmxcuj1#
在这个程序中有很多大的瓶颈,它们不一定与搜索有关。为了解决这些问题,你更需要代码审查:
static
变量(. xml)中,然后再次将它复制到函数调用堆栈中,最后在调用者中再次复制它。当你可以返回一个指向原始项的const
指针时,这是非常低效的,或者如果这些需要在运行时修改,只需复制它 * 一次 * -可能仍然返回一个const
指针,让调用者复制数据。(可能需要使用一些哈佛架构调整来直接访问闪存数据-无论您的编译器使用什么来处理,“PROGMEM”宏或类似的东西。)
uint16_t
。而且几乎从不使用size_t
,这将是一个32位类型,因此非常慢。bsearch
并不一定比手动推出自己的版本慢得多。使用示波器进行基准测试。总的来说,我认为你会从选择现代MCU中受益,比如Cortex M。在C中编程旧的8位实际上是相当困难的,很容易出错。AVR是20世纪90年代的技术,没有太多的理由继续使用它们。(它们是学习汇编程序的不错选择,但仅此而已。
koaltpgm2#
在检查了你的github项目并提供了约束条件(ATmega328p)后,我想我理解了你的问题。
你必须保存你宝贵的2048字节RAM的每一位这ATmega。由于可用内存,将
lookuptable[256]
放置在RAM中是没有选择的。将您的字体表转换为:
在以这种方式准备好数据之后,最终的
getGlyph()
变成了一行程序:jutyujz03#
如果你追求快速的代码,查找表可能是一个不错的选择。
有两种口味。下面的代码在GNU-C99中,因此
__flash
限定符被支持。这允许更容易理解的代码,而不是混乱的pgm_read_xxx
。如果你坚持使用pgm_read_xxx
,你将不得不翻译代码。此外,代码使用 * 指针 * 代替来回复制
Font
和Glyph
。完整的avr-gcc v5.4可编译代码在答案的末尾。Flash中的查找表
这是首选的方式,因为它不需要宝贵的RAM。下面的代码假设查找表是用
0
初始化的,这是静态存储中数据的默认值。因此,当我们读取0
的id时,我们必须检查is是否实际上是glyph[0]
,或者是一些默认值,如code = '?'
的glyph[0]
备注:
?
的id初始化.lookup_flash
的所有未使用的条目,则不再需要if
指令。font->glyphs[0].code
在编译时是已知的。在答案末尾的示例代码中,它可以替换为'a'
。uint8_t
,因此每个字体将花费额外的256字节程序内存。内存查找表
当程序内存中的查找表由于某种原因不可行时,这是一种替代方法。实际上,我在ATmega 168/328中使用这种方法来处理具有可变字体大小的矢量字体,其中Flash中的查找表对于预先计算来说太痛苦了。当您基本上使用从0x 20到0x 7 f(ASCII)的代码并带有一些异常时,那么您可以手动处理异常,从而将表大小减少到例如0x 60字节。
代码使用相同的查找和不同的字体。如果检测到字体切换,则重置表格。对于未知的条目,使用线性搜索来查找索引及其ID。
avr-gcc v5.4可替换代码
最后,这里是完整的可编译示例,以供参考,
我添加了
Font
和Glyph
的样板定义。