【问题标题】:Simple loop optimized by clang, how?由clang优化的简单循环,如何?
【发布时间】:2020-09-16 18:45:24
【问题描述】:

来自CppCon 2014: Mike Acton "Data-Oriented Design and C++",他展示了这个简单的循环

int Foo::Bar(int count)
{
    int value = 0;
    for (int i=0;i<count;i++)
    {
        if ( m_NeedParentUpdate )
        {
            value++;
        }
    }
    return (value);
}

被clang优化

我不明白这里发生了什么。为什么这段代码不好,为什么它会被 clang 优化,为什么会这样?

关于循环,他还说“我相信你可以在脑海中优化它”。我不明白。我该如何优化?

【问题讨论】:

  • 请不要张贴图片、复制或重新输入文字并作为代码片段发布。

标签: loops optimization clang


【解决方案1】:

如果m_NeedParentUpdate 不是false,则循环增加value count 倍。

从生成的代码来看,m_NeedParentUpdate 似乎是一个布尔值,存储为无符号字节,位于this 的偏移量0。优化器可能检测到m_NeedParentUpdate 是循环中的常量,因此可以将测试移到循环外。程序员应该已经用这种方式编写了代码,这可能是 Mike Acton 所指的我相信你可以在脑海中优化它

这是一个重写的版本:

class Foo {
    bool m_NeedParentUpdate;
    int Bar(int count);
};

int Foo::Bar(int count) {
    int value = 0;
    if (m_NeedParentUpdate) {
        for (int i = 0; i < count; i++) {
            value++;
        }
    }
    return value;
}

但是请注意,进一步优化代码在你的头脑中可能会导致循环减少到value += count;,但这对于count的负值是不正确的,这在第一眼。

优化器可以检测循环模式并优化为:

int Foo::Bar(int count) {
    int value = 0;
    if (m_NeedParentUpdate) {
        if (count >= 0) {
            value += count;
        }
    }
    return value;
}

或等效:

int Foo::Bar(int count) {
    int value = 0;
    if (count >= 0) {
        if (m_NeedParentUpdate) {
            value = count;
        }
    }
    return value;
}

m_ParentNeedUpdate 转换为unsigned 并将其取反为false 生成0,为true 生成所有位。使用此值屏蔽count 将产生0count

int Foo::Bar(int count) {
    int value = 0;
    if (count >= 0) {
        value = -(unsigned)m_NeedParentUpdate & count;
    }
    return value;
}

但是请注意,代码仍然有一个测试和一个分支指令。可以进一步优化为:

int Foo::Bar(int count) {
    // equivalent code, but definitely not readable
    return -(unsigned)m_NeedParentUpdate & -(unsigned)(count >= 0) & count;
}

gccclang 编译它会产生无分支代码,如GodBolt's Compiler Explorer 所示。但是这两个编译器都没有将原始代码简化为这一行。

【讨论】:

    【解决方案2】:

    它被认为不好的原因是您必须在每次迭代时读取成员变量m_NeedParentUpdate。如果这是一个大的for 循环,并且您还试图读取一堆其他成员变量,那么读取此m_NeedParentUpdate 可能会占用您的缓存空间,从而可能会降低有效的缓存使用率。这将导致性能损失。

    性能损失的细节取决于运行此代码的类的数据布局以及在for 循环中访问的其他成员变量。

    我们可以优化它的方法是将布尔检查移到外面,因为我们真的只需要这样做一次。

    int Foo::Bar(int count)
    {
        int value = 0;
        if ( m_NeedParentUpdate )
        {
            for (int i=0;i<count;i++)
            {
    
                value++;
            }
        }
        return (value);
    }
    

    【讨论】:

      猜你喜欢
      • 2019-08-27
      • 2020-12-14
      • 2018-11-01
      • 2021-04-23
      • 1970-01-01
      • 2016-11-20
      • 2014-09-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多