【问题标题】:How to improve the speed of the float lerp function? [closed]如何提高float lerp函数的速度? [关闭]
【发布时间】:2016-12-13 02:39:30
【问题描述】:

最近我在写一个软光栅渲染器,但是它的速度真的很慢。通过性能测试,我发现float lerp函数是瓶颈。如何提高此功能的速度?使用simd?有什么想法吗?

inline float MathUtil::Lerp(float x1, float x2, float t)
{
    return x1 + (x2 - x1)*t;
}

//lerp vector
ZCVector MathUtil::Lerp(const ZCVector& v1, const ZCVector& v2, float t)
{
    return ZCVector(
        Lerp(v1.x, v2.x, t),
        Lerp(v1.y, v2.y, t),
        Lerp(v1.z, v2.z, t),
        v1.w
    );
}

//lerp ZCFLOAT2
ZCFLOAT2 MathUtil::Lerp(const ZCFLOAT2& v1, const ZCFLOAT2& v2, float t)
{
    return ZCFLOAT2(
        Lerp(v1.u, v2.u, t),
        Lerp(v1.v, v2.v, t)
    );
}

//lerp ZCFLOAT3
ZCFLOAT3 MathUtil::Lerp(const ZCFLOAT3& v1, const ZCFLOAT3& v2, float t)
{
    return ZCFLOAT3(
        Lerp(v1.x, v2.x, t),
        Lerp(v1.y, v2.y, t),
        Lerp(v1.z, v2.z, t)
    );
}

//lerp VertexOut
VertexOut MathUtil::Lerp(const VertexOut& v1, const VertexOut& v2, float t)
{
    return VertexOut(
        Lerp(v1.posTrans, v2.posTrans, t),
        Lerp(v1.posH, v2.posH, t),
        Lerp(v1.tex, v2.tex, t),
        Lerp(v1.normal, v2.normal, t),
        Lerp(v1.color, v2.color, t),
        Lerp(v1.oneDivZ, v2.oneDivZ, t)
    );
}

VertexOut 的结构:

class VertexOut
{
public:

    ZCVector posTrans;

    ZCVector posH;

    ZCFLOAT2 tex;

    ZCVector normal;

    ZCFLOAT3 color;

    float oneDivZ;
}

scanlinefill函数填充三角形,每个顶点都需要使用lerp函数,所以会被调用这么多次。

void Tiny3DDeviceContext::ScanlineFill(const VertexOut& left, const VertexOut& right,  int yIndex)
{
    float dx = right.posH.x - left.posH.x;

    for (float x = left.posH.x; x <= right.posH.x; x += 0.5f)
    {
        int xIndex = static_cast<int>(x + .5f);
        if(xIndex >= 0 && xIndex < m_pDevice->GetClientWidth())
        {

            float lerpFactor = 0;
            if (dx != 0)
            {
                lerpFactor = (x - left.posH.x) / dx;
            }


            float oneDivZ = MathUtil::Lerp(left.oneDivZ, right.oneDivZ, lerpFactor);
            if (oneDivZ >= m_pDevice->GetZ(xIndex,yIndex))
            {
                m_pDevice->SetZ(xIndex, yIndex, oneDivZ);
                //lerp get vertex
                VertexOut out = MathUtil::Lerp(left, right, lerpFactor);
                out.posH.y = yIndex;

                m_pDevice->DrawPixel(xIndex, yIndex, m_pShader->PS(out));
            }           
        }   
    }
}

【问题讨论】:

  • 也许可以尝试按位摆弄。不要在错误的地方尝试过早的优化。
  • 你在优化什么平台? x86?您需要在旧 CPU 上运行的二进制文件,还是可以使用 AVX 和 FMA?什么编译器和选项?更重要的是,周围的代码是什么?它实际上是自动矢量化的吗?此函数本身应使用 SSE 或任何其他 SIMD ISA 进行简单矢量化。内联到的周围代码显然很关键。延迟是吞吐量的限制因素吗?
  • @πάνταῥεῖ:在现代 CPU 上,FP 指令之间的整数位摆弄通常不是一个好主意。在 x86 上,您需要使用 SIMD 整数指令(因为即使是标量浮点数也使用 XMM 寄存器),或者您必须在 XMM 和整数寄存器之间进行缓慢的往返。在 FP insns 之间使用整数向量指令会产生额外的绕过延迟,因此即使您可以使用一两个整数指令做一些有用的事情,它的延迟也会比仅使用 FP 更糟糕。此外,向量 FP 指令具有极高的吞吐量。例如Haswell 上每个时钟 2 个矢量 FMA。
  • @PeterCordes 我从来没有说过这是一个好主意:P
  • 环境:win10,vs2015。用于处理pos,tex,color等,如果使用SIMD如何重写?

标签: c++ optimization graphics simd


【解决方案1】:

此循环结构可能会根据需要运行 lerp 的两倍:

for (float x = left.posH.x; x <= right.posH.x; x += 0.5f) {
      int xIndex = static_cast<int>(x + .5f);
      ...
}

相反,(更准确地说),循环递增整数 xIndex,并为每个 xIndex 计算正确的 float x


这可能会自动矢量化,但您必须检查编译器输出以了解发生了什么。希望你用out.posH.y = yIndex; 覆盖的 Lerp 得到优化,因为你丢弃了结果。如果没有,您可能会通过制作不执行该 Lerp 的包装函数来获得加速。


您可以通过使用 Struct-of-Arrays 方法而不是让结构的所有内容保持连续的 AoS 方法来使其对 SIMD 更加友好。但是,您以相同的方式对多个元素进行 Lerping,因此它可能会使用两个标量和一个向量 Lerp 自动矢量化。

请参阅 标记 wiki 以获取有关 SIMD 内容的一些指南,包括指向此 very nice beginner / intermediate set of slides 的链接。


您可能还可以更改其他内容,尤其是。 对代码进行更大规模的重组以减少整体工作。与使用 SIMD 来有效地应用现代 CPU 的蛮力相比,这种优化通常可以为您带来更大的加速。

同时做这两件事来倍增加速是真正让事情变得更快的原因。

缓存未命中和内存带宽瓶颈通常是一个重要因素,因此优化您的访问模式会产生很大的不同。

如果您想了解更多底层细节,请参阅Agner Fog's optimization guide。他有一个 C++ 优化指南,但大部分好东西都是关于 x86 asm 的。 (另见 标签维基)。但请记住,这种低级优化只是在寻找高级优化之后的一个好主意。

【讨论】:

    猜你喜欢
    • 2012-06-30
    • 2017-01-17
    • 1970-01-01
    • 1970-01-01
    • 2020-11-24
    • 1970-01-01
    • 2011-05-28
    • 2014-05-05
    • 2022-07-13
    相关资源
    最近更新 更多