我开始进入C的温和深度,使用arduinos之类的东西,只是想要一些关于我如何使用For循环产生随机噪声的建议。 重要的一点:
if sprite.parent == nil {
print("sprite has been removed from the parent")
}
该功能在屏幕上逐个绘制一个像素,一旦void testdrawnoise() {
int j = 0;
for (uint8_t i=0; i<display.width(); i++) {
if (i == display.width()-1) {
j++;
i=0;
}
M = random(0, 2); // Random 0/1
display.drawPixel(i, j, M); // (Width, Height, Pixel on/off)
display.refresh();
}
}
达到i
,就会向下移动到下一行。像素是显示在(黑色)还是关闭(白色)由display.width()-1
确定。
代码工作正常,但我觉得它可以做得更好,或者至少更整洁,也许更有效率。
非常感谢输入和评论。
答案 0 :(得分:1)
首先,你的循环永远不会结束,并继续无限制递增j
,因此,在你填满屏幕一次后,你继续在屏幕高度之外循环;虽然你的库does bounds checking,但是在j
溢出并返回到零之前,在没有实际执行有用工作的情况下继续循环使用CPU肯定不是很有效。
此外,签名溢出是C ++中未定义的行为,因此您在技术上处于不稳定的理由(我原本认为Arduino总是使用-fwrapv
编译,这保证了有符号整数溢出的循环,but apparently I was mistaken)。 / p>
鉴于您正在使用的库将整个帧缓冲区保留在内存中并在refresh
个调用上发送它,所以在每个像素重新发送它没有多大意义 - 特别是因为帧传输是可能是迄今为止这个循环中最慢的部分。所以,你可以将它移出循环。
将它们放在一起(加上缓存宽度和高度并使用random
的简单重载),您可以将其更改为:
void testdrawnoise() {
int w = display.width(), h = display.height();
for (int j=0; j<h; ++j) {
for (int i=0; i<w; ++i) {
display.drawPixel(i, j, random(2));
}
}
display.refresh();
}
(如果您在AVR Arduinos上的屏幕尺寸小于256,那么您可能通过将所有int
更改为byte
来获得某些东西,但不要相信我的话它)
请注意,这只会执行一次,您可以将其放入loop()
函数或无限循环中,以使其继续生成随机模式。
这是您可以使用提供的界面执行的操作;现在,进入无证领域我们可以更快。
如上所述,您正在使用的库将整个帧缓冲区保存在内存中,以每个字节8位打包(按预期方式),在一个名为sharpmem_buffer
的全局变量中,初始化with a malloc
of the obvious size。< / p>
还应该注意的是,当您在代码中要求一个随机位时,PRNG会生成一个完整的31位随机数,并且只取低位。为什么要浪费所有其他非常好的随机位?
同时,当您调用drawPixel
时,库会对内存中的相应字节执行一系列布尔操作,以便仅设置您要求的位而不触及其余位。非常愚蠢,因为你打算用随机的方式覆盖其他的。
所以,把这两个事实放在一起,我们可以做类似的事情:
void testdrawnoise() {
// access the buffer defined in another .cpp
extern byte *sharpmem_buffer;
byte *ptr = sharpmem_buffer; // pointer to current position
// end position
byte *end = ptr + display.width()*display.height()/8;
for (; ptr!=end; ++ptr) {
// store a full byte of random
*ptr = random(256);
}
display.refresh();
}
,减去refresh()
时间,应该至少比前一版本快8倍(我实际上期望更多,因为不仅循环的核心执行迭代的1/8,但它也更简单 - 除了random
之外没有函数调用,没有分支,没有内存上的布尔运算。
在AVR Arduinos上,唯一可以进一步优化的点可能是RNG - 我们仍然只使用8位的8位(如果它们实际上是31位?Arduino文档像往常一样糟透了提供有用的技术信息) RNG,所以我们可能在单个RNG调用中生成3个字节的随机数,或者如果我们切换到没有弄乱符号位的手动LCG则为4个字节。在ARM Arduinos上,在最后一种情况下,我们甚至可以通过在内存中执行完整的32位存储而不是写单个字节来获得一些东西。
然而,这些进一步的优化是(1)编写繁琐(如果你必须处理像素数不是24/32的倍数的屏幕)和(2)可能不是特别有利可图,因为大多数无论如何,时间将用于通过SPI进行传输。无论如何值得一提,因为它们可能在其他没有传输瓶颈的情况下有用,可以减慢一切。
鉴于OP的MCU实际上是Cortex M0(因此,32位ARM),因此使用完整的32位PRNG和32位存储器来尝试使其速度更快是值得的。
如上所述,内置random
会返回一个有符号的值,但它并不完全清楚它提供的范围;因此,我们必须推出自己的PRNG,保证提供32位完整的随机性。
一个体面且非常快速的PRNG,提供32个随机位,最小状态为xorshift;我们将直接使用维基百科中的xorshift32,因为我们并不需要改进的“*”或“+”版本(我们也不关心更大的时间段提供更大的时间段。)
struct XorShift32 {
uint32_t state = 0x12345678;
uint32_t next() {
uint32_t x = state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
state = x;
return x;
}
};
XorShift32 xorShift;
现在我们可以重写testdrawnoise()
:
void testdrawnoise() {
int size = display.width()*display.height();
// access the buffer defined in another .cpp
extern byte *sharpmem_buffer;
/*
we can access the framebuffer as if it was an array of 32-bit words;
this is fine, since it was alloc-ed with malloc, which guarantees memory
aligned for the most restrictive built-in type, and the library only
uses it with byte pointers, so there should be no strict aliasing problem
*/
uint32_t *ptr = (uint32_t *)sharpmem_buffer;
/*
notice that the division is an integer division, which truncates; so, we
are filling the framebuffer up the the last multiple of 4 bytes; with
"strange" sizes we may be leaving out up to 3 bytes (see later)
*/
uint32_t *end = ptr + size/32;
for (; ptr!=end; ++ptr) {
// store a full byte of random
*ptr = xorShift.next();
}
// now to fill the possibly missing last three bytes
// pick it up where we left it
byte *final_ptr = (byte *)end;
byte *final_end = sharpmem_buffer + size/8;
// generate 32 random bits; it's ok, we'll need at most 24
uint32_t r = xorShift.next();
for(; final_ptr!=final_end; ++final_ptr) {
// take the lower 8 bits
*final_ptr = r;
// throw away the bits we used, get in the upper ones
r = r>>8;
}
display.refresh();
}