针对编译时常数进行了优化的功能

时间:2019-03-19 19:34:46

标签: c++ c++17 c++20

假设我有一个向量长度计算函数,它具有一个附加的inc参数(这表明相邻元素之间的距离)。一个简单的实现是:

float calcLength(const float *v, int size, int inc) {
    float l = 0;

    for (int i=0; i<size*inc; i += inc) {
        l += v[i]*v[i];
    }
    return sqrt(l);
}

现在,可以使用两种calcLength参数来调用inc:何时在编译时知道inc,何时不知道。我想为calcLength(如1)的常见编译时值提供一个优化的inc版本。

所以,我会有这样的东西:

template <int C>
struct Constant {
    static constexpr int value() {
        return C;
    }
};

struct Var {
    int v;

    constexpr Var(int p_v) : v(p_v) { }

    constexpr int value() const {
        return v;
    }
};

template <typename INC>
float calcLength(const float *v, int size, INC inc) {
        float l = 0;

        for (int i=0; i<size*inc.value(); i += inc.value()) {
            l += v[i]*v[i];
        }
        return sqrt(l);
    }
}

因此,可以使用:

calcLength(v, size, Constant<1>()); // inc is a compile-time constant 1 here, calcLength can be vectorized

int inc = <some_value>;
calcLength(v, size, Var(inc)); // inc is a non-compile-time constant here, less possibilities of compiler optimization

我的问题是,是否可能以某种方式保留原始接口,并根据{{1的类型(是否为编译时常数)自动插入Constant / Var }}?

inc

注意:这是一个简单的示例。在我的实际问题中,我有几个类似calcLength(v, size, 1); // this should end up calcLength(v, size, Constant<1>()); calcLength(v, size, inc); // this should end up calcLength(v, size, Var(int)); 的函数,它们很大,我不希望编译器内联它们。


注2:我也接受不同的方法。基本上,我想有一个解决方案,可以满足以下要求:

  • 算法仅指定一次(最有可能在模板函数中指定)
  • 如果我将calcLength指定为1,则会实例化一个特殊函数,并且代码很可能被矢量化
  • 如果inc不是编译时常量,则调用通用函数
  • 否则(非1的编译时常数):调用哪个函数都没关系

3 个答案:

答案 0 :(得分:2)

如果此处的目标只是优化而不是在编译时上下文中使用,则可以向编译器提示您的意图:

static float calcLength_inner(const float *v, int size, int inc) {
    float l = 0;

    for (int i=0; i<size*inc; i += inc) {
        l += v[i]*v[i];
    }
    return sqrt(l);
}

float calcLength(const float *v, int size, int inc) {
    if (inc == 1) {
        return calcLength_inner(v, size, inc);  // compiler knows inc == 1 here, and will optimize
    }
    else {
        return calcLength_inner(v, size, inc);
    }
}

From godbolt,您可以看到calcLength_inner实例化了两次,无论是否具有恒定的传播。

这是一个C技巧(在numpy中广泛使用),但是您可以编写一个简单的包装程序,使其更易于在c ++中使用:

// give the compiler a hint that it can optimize `f` with knowledge of `cond`
template<typename Func>
auto optimize_for(bool cond, Func&& f) {
    if (cond) {
        return std::forward<Func>(f)();
    }
    else {
        return std::forward<Func>(f)();
    }
}

float calcLength(const float *v, int size, int inc) {
    return optimize_for(inc == 1, [&]{
        float l = 0;
        for (int i=0; i<size*inc; i += inc) {
            l += v[i]*v[i];
        }
        return sqrt(l);
    });
}

答案 1 :(得分:0)

C ++没有提供检测提供的函数参数是否为常量表达式的方法,因此您无法自动区分提供的文字和运行时值。

如果参数必须是一个函数参数,并且您不愿意在两种情况下更改其调用方式,那么这里唯一的杠杆就是参数的类型:在这方面,您对Constant<1>()Var(inc)的建议非常好。

答案 2 :(得分:0)

选项1:信任您的编译器(不执行任何操作)

编译器可以在不费吹灰之力的情况下完成您想做的事情吗(嗯,您需要启用优化的构建,但这不用多说)。

编译器可以创建所谓的“函数克隆”,它们可以执行您想要的操作。克隆函数是用于常量传播的函数的副本,也就是使用常量参数调用的函数的结果汇编。我很少找到有关此功能的文档,因此如果您要依靠它,完全取决于您。

编译器可以完全内联该函数,可能使您的问题不成问题(您可以通过使用lto和/或使用编译器特定的属性,例如__attribute__((always_inline))在标头中内联定义它来解决此问题)

现在,我不是在鼓吹让编译器完成其工作。尽管这些时候编译器的优化令人惊奇,并且经验法则是信任优化器,但是在某些情况下,您需要手动进行干预。我只是说要意识到并考虑到它。哦,一如既往地衡量,衡量,衡量,在性能方面,请不要使用您的“我觉得我需要优化”的直觉。

选项2:两个重载

float calcLength(const float *v, int size, int inc) {
    float l = 0;

    for (int i=0; i<size*inc; i += inc) {
        l += v[i]*v[i];
    }
    return sqrt(l);
}

template <int Inc>
float calcLength(const float *v, int size) {
    float l = 0;

    for (int i=0; i<size*inc; i += inc) {
        l += v[i]*v[i];
    }
    return sqrt(l);
}

这里的缺点是代码重复,ofc。同样,在呼叫站点也无需多加注意:

calcLength(v, size, inc); // ok
calcLength<1>(v, size);   // ok
calcLength(v, size, 1);   // nope

选项3:您的版本

您的版本还可以。

相关问题