首页 文章

优化可变与不可变矢量数学

提问于
浏览
5

哪种编码风格更适合编译器优化?特别是,我感兴趣1)最小化立即丢弃的临时值的数量和2)自动矢量化,即生成用于算术的SIMD指令 .

假设我有这个结构:

#define FOR_EACH for (int i = 0; i < N; ++i)

template<typename T, unsigned N>
struct Vector {
    void scale(T scalar) {
        FOR_EACH v[i] *= scalar;
    }

    void add(const Vector<T, N>& other) {
        FOR_EACH v[i] += other.v[i];
    }

    void mul(const Vector<T, N>& other) {
        FOR_EACH v[i] *= other.v[i];
    }

    T v[N];
};

此结构的示例用法:

Vector<int, 3> v1 = ...;
Vector<int, 3> v2 = ...;
v1.scale(10);
v1.add(v2);
v1.mul(v2);

这是一种可变的方法 .

另一种不可变的方法可能如下所示:

template<typename T, unsigned N>
struct Vector {
    Vector(const Vector<T, N>& other) {
        memcpy(v, other.v, sizeof(v));
    }

    Vector<T, N> operator+(const Vector<T, N>& other) const {
        Vector<T, N> result(*this);
        FOR_EACH result.v[i] += other.v[i];
        return result;
    }

    Vector<T, N> operator*(T scalar) const {
        Vector<T, N> result(*this);
        FOR_EACH result.v[i] *= scalar;
        return result;
    }

    Vector<T, N> operator*(const Vector<T, N>& other) const {
        Vector<T, N> result(*this);
        FOR_EACH result.v[i] *= other.v[i];
        return result;
    }

    T v[N];
};

用法示例:

Vector<int, 3> v1 = ...;
Vector<int, 3> v2 = ...;
auto result = (v1 * 10 + v2) * v2;

现在,我不关心这个问题中的API设计 . 假设两种解决方案在这方面都是可行的 .

此外,代替示例代码中的 int ,它也可以是 floatdouble .

我感兴趣的是:现代C编译器可以更容易地分析哪种设计?我并没有特别针对任何一个编译器 . 如果您有任何编译器的经验并知道它如何处理我所询问的优化,请分享您的经验 .

  • 第二个版本产生了很多临时值 . 如果编译器最终内联所有的操作符调用并且看到所有的算术表达式,那么编译器可以摆脱它们吗? (我假设没有内联,没有编译器可以消除临时因为可能的副作用)

  • 第一个版本最小化临时数量,但构造严格顺序计算 . 编译器是否仍然能够以最小化操作数量并允许并行化(在CPU指令级别)的方式推断出意图并重新排序操作?

  • 现代编译器对上面的循环进行矢量化有多难?

1 回答

  • 0

    据我所知,只要目标架构中有支持,第一个示例就很容易进行矢量化 . 这是因为在连续迭代中元素之间没有数据依赖性 .

    如果你有循环,在连续迭代中元素之间存在数据依赖关系,在某些情况下,它们可以通过软件流水线删除 . 软件流水线有助于矢量化 .

    在某些体系结构中,由于有限的浮点执行单元,浮点计算不易于矢量化 .

    在第二个例子中,可以通过内联消除临时性 .

    有用的链接:

相关问题