0

好的,我有一个类允许用户在 100x64 LCD 上设置像素。

// (U8 = unsigned char)
inline void pixelOn(const U8 X, const U8 Y) {
   *(disp + ((Y / LCD_DEPTH) * LCD_WIDTH + X)) |= (1 << (Y % LCD_DEPTH));
}


inline void pixelOff(const U8 X, const U8 Y) {
   *(disp + ((Y / LCD_DEPTH) * LCD_WIDTH + X)) &= !(1 << (Y % LCD_DEPTH));
}

现在我有一个在液晶显示器上画线的课程。有 3 个函数使用不同的像素“设置函数”:显示、擦除、反转

void NLine::show(bool update) const {
    if(lcd == 0) return;

    if (!NLcd::pixelInLcd(x, y) && !NLcd::pixelInLcd(endX, endY))
        return;
    if ((x == endX) || (y == endY)) {
        straight(&NLcd::pixelOn);
    } else {
        bresenham(&NLcd::pixelOn);
    }
    visible = true;
    if (update) {
        display_update();
    }
}

目前调用一个私有函数来使用函数指针设置像素。

void NLine::bresenham(void (NLcd::*fpPixelState)(const U8, const U8)) const {
    // predetermine function to avoid ifs during calculation!
    // low level pixel functions use U8!
    S8 ix = x;
    S8 iy = y;
    S8 sx = ix < endX ? 1 : -1;
    S8 sy = iy < endY ? 1 : -1;
    S16 err = width + (-height), e2;

    for (;;) {
        // how to get the compiler to inline this (template?)!
        (lcd->*fpPixelState)(static_cast<U8>(ix), static_cast<U8>(iy));
        if (ix == endX && iy == endY)
            break;
        e2 = 2 * err;
        if (e2 > (-height)) {
            err += (-height);
            ix += sx;
        }
        if (e2 < width) {
            err += width;
            iy += sy;
        }
    }
}

我认为我希望编译器在 for 循环中内联这个函数是可以理解的。我试图用模板解决这个问题,但同样的问题是我不知道编译器是否使用内联。我应该使用完全不同的设计还是如何解决这个问题?下一个问题是如果我调用 show erase 和 invert 因为不同的内联,编译器会生成很多代码,所以我认为我应该使用不同的代码设计?

编辑:

首先,感谢 Dietmar Kühl 的设计建议!所以这是结论:

这是测试代码:

NLine line(lcd, 0, 0, 99, 0);
t0 = timer.now();
for(S8 i=0; i<NLcd::LCD_HEIGHT; ++i) {
    // x0, y0, xend, yend
    line.setPosition(NLine::keep, i, NLine::keep, i);
    // call only straight not the bigger bresenham function
    line.show();
    line.erase();
    line.invert();
}
t1 = timer.now();

方法一:使用函数指针(第一)
内存:26384
RT:51 ms

方法 2:使用函数对象 (Dietmar Kühl)
内存:26592
RT:27 ms

方法三:在for循环中使用switch判断像素操作函数
内存:26416
RT:36 ms

方法 2:最佳 RT 但事实证明,如果围绕绘图方法的实现变得非常大,程序就会变得非常大,尤其是对于 bresenham。发生这种情况是因为模板实现为所有 3 个像素函数生成了完整代码。

方法3:似乎最简单的方法是一个很好的权衡。

欢迎进一步的建议。

4

2 回答 2

1

最有效的方法不是传入函数指针,而是使用指定函数对象的模板参数自定义函数。然后可以通过内联函数调用运算符传入一个函数对象(显然,成员函数需要在NLineclas 中相应声明:

template <typename PixelState>
    // PixelState is a function object taking
    // - a reference to an `NLcd`
    // - two U8 parameters and returns nothing
void NLine::bresenham(PixelState pixelState) const {
    // ...
    pixelState(lcd,static_cast<U8>(ix), static_cast<U8>(iy));
    // ...
}

然后,相应的函数对象可以是具有合适的函数调用运算符的类,例如:

struct PixelOn
{
    void operator()(NLcd& lcd, U8 x, U8 y) const {
        lcd.pixelOn(x, y);
    }
};
// ...
bresenham(PixelOn());
// with C++ as of the 2011 revision:
bresenham([](Nlcd& lcd, U8 x, U8 y){ lcd.pixelOn(x, y); });

像这样的函数对象通常可以由编译器内联,并且编译器也倾向于内联它们,因为这种技术经常使用,例如标准 C++ 算法。您的特定函数是否实际上被内联取决于编译器是否决定这样做,但它通常会做出最有效的选择(当然,假设一个合理的优化级别)。如果NLcd::pixelOn可以内联,此函数也将被内联。

于 2013-11-09T11:02:58.417 回答
1

您不能将函数作为指向另一个函数的指针传递,并期望编译器内联它,除非代码真的很简单(这样编译器可以内联函数指针传递给的函数)。编译器(通常)不会知道您不会在其他上下文中(例如,在不同的编译单元中)传递其他函数指针,因此,它仍然必须在函数指针到位的情况下“工作”。

可能有几种不同的方法可以“解决”这个问题,如果它真的那么重要的话 - 但是调用函数的开销[通过指针或不通过指针]可能比你正在做的数学非常小(除以 LCD_DEPTH 是昂贵的)。您是否真的进行了测量以确定是通话时间产生了影响?

另外,您的pixelOff代码可能是错误的,我希望:

*(disp + ((Y / LCD_DEPTH) * LCD_WIDTH + X)) &= !(1 << (Y % LCD_DEPTH));

应该:

*(disp + ((Y / LCD_DEPTH) * LCD_WIDTH + X)) &= ~(1 << (Y % LCD_DEPTH));

因为那会与

于 2013-11-09T10:52:03.280 回答