【问题标题】:Generating random visual noise using For loop使用 For 循环生成随机视觉噪声
【发布时间】:2017-07-29 16:18:52
【问题描述】:

我开始深入了解 C 语言,使用 arduinos 之类的工具,我只是想要一些关于如何使用 For 循环生成随机噪声的建议。 重要的一点:

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,就向下移动到下一行。像素是亮(黑)还是灭(白)由M决定。

代码运行良好,但我觉得它可以做得更好,或者至少更整洁,也许更有效。

非常感谢您的意见和批评。

【问题讨论】:

  • 您确实经常调用 refresh()。生成一整行像素然后画一条线怎么样?
  • 这与 Arduino 有什么关系?好奇。
  • 您无需以肉眼可见的速度刷新屏幕。因此,大约每 1/30 秒左右调用一次refresh [您可以通过跟踪当前时间增量来做到这一点]
  • 是的,我经常调用刷新,大约每秒 15 次。我认为问题只是我已经达到了这个 arduino 可以处理的最大值。它不会再快了:(
  • @MatteoItalia:它可能是一个带驱动程序的串行连接面板。

标签: c++ for-loop random arduino draw


【解决方案1】:

首先,您的循环永远不会结束,并且会无限地递增j,因此,在您填充一次屏幕后,您会继续在屏幕高度之外循环;尽管您的库does bounds checking,但在j 溢出并回到零之前,在没有实际做有用工作的情况下继续循环肯定不是对CPU 的有效使用。

此外,有符号溢出在 C++ 中是未定义的行为,因此从技术上讲,您的理由是不稳定的(我最初认为 Arduino 总是使用 -fwrapv 编译,这保证了有符号整数溢出 but apparently I was mistaken 的环绕)。

鉴于您正在使用的库将整个帧缓冲区保存在内存中并在 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

还应注意,当您在代码中请求一个随机位时,PRNG 会生成一个完整的 31 位随机数并只取低位。为什么要浪费所有其他完美的随机位?

同时,当您调用drawPixel 时,库对内存中的相应字节执行一系列布尔运算,以设置您要求的位,而不会触及其余位。相当愚蠢,因为无论如何你都会用 random 覆盖其他的。

所以,把这两个事实放在一起,我们可以做这样的事情:

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——我们仍然只使用 31 位中的 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 位完整的随机性。

xorshift 是一个体面且非常快速的 PRNG,它提供 32 个随机位和最小状态;我们将直接使用 Wikipedia 中的 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();
}

【讨论】:

  • 真的很棒!我只是要理解后面的部分,但第一部分完全有道理,让我想知道为什么我最初以我的方式做事。谢谢!
  • 只是为了让您了解造成的差异:它从每秒写入大约 6 像素,到 不到一毫秒的整个屏幕。 同样来自您的代码我可以看到我在之前的一次尝试中出错的地方。再次感谢您。
  • 很高兴它有帮助,您是否设法尝试了我们的第二种方法?是明显更快,还是一旦你将refresh 移出循环,一切都太快了
  • 是的,一旦refresh() 被移出循环,它就会加速处理过程。现在尝试和改进这一点将是徒劳的。就像我上面所说的,它已经从每秒写入大约 6 个像素,在不到 10 秒的时间内在屏幕上显示了全部 24,192 个像素。
  • 哈哈,很高兴看到它真正起作用 :-) 不客气,这是一个有趣的问题。
猜你喜欢
  • 1970-01-01
  • 2011-03-20
  • 1970-01-01
  • 2012-01-08
  • 2013-10-11
  • 2014-02-14
  • 1970-01-01
  • 2011-12-06
  • 1970-01-01
相关资源
最近更新 更多