有没有办法优化这个功能?
Is there a way to optimize this function?
对于我正在处理的应用程序,我需要取两个整数并使用特定的数学公式将它们相加。这最终看起来像这样:
int16_t add_special(int16_t a, int16_t b) {
float limit = std::numeric_limits<int16_t>::max();//32767 as a floating point value
float a_fl = a, b_fl = b;
float numerator = a_fl + b_fl;
float denominator = 1 + a_fl * b_fl / std::pow(limit, 2);
float final_value = numerator / denominator;
return static_cast<int16_t>(std::round(final_value));
}
任何对物理学有一定了解的读者都会认识到这个公式与用于计算近光速速度总和的公式相同,这里的计算有意反映了该计算。
所写的代码给出了我需要的结果:对于小数,它们 几乎 正常相加,但对于大数,它们收敛到最大值 32767,即
add_special(10, 15) == 25
add_special(100, 200) == 300
add_special(1000, 3000) == 3989
add_special(10000, 25000) == 28390
add_special(30000, 30000) == 32640
似乎都是正确的。
然而,问题在于所写的函数涉及首先将数字转换为浮点值,然后再将它们转换回整数。对于我所知道的数字来说,这似乎是一个不必要的弯路,作为其领域的原则,永远不会是整数。
是否有更快、更优化的方法来执行此计算?或者这是我可以创建的此功能的最优化版本?
我正在为 x86-64 构建,使用 MSVC 14.X,尽管也适用于 GCC 的方法将是有益的。另外,现阶段我对 SSE/SIMD 优化不感兴趣;我主要只是查看对数据执行的基本操作。
建议:
- 使用
32767.0*32767.0
(常量)代替 std::pow(limit, 2)
。
- 尽可能使用整数值,可能使用定点。只是这两个部门是一个问题。如有必要,仅使用浮点数(取决于输入数据范围)。
- 如果功能小,合适的话就
inline
类似于:
int16_t add_special(int16_t a, int16_t b) {
float numerator = int32_t(a) + int32_t(b); // Cannot overflow.
float denominator = 1 + (int32_t(a) * int32_t(b)) / (32767.0 * 32767.0); // Cannot overflow either.
return (numerator / denominator) + 0.5; // Relying on implementation defined rounding. Not good but potentially faster than std::round().
}
以上的唯一风险是省略了显式舍入,因此您会得到一些隐式舍入。
正如 Johannes Overmann 所指出的,通过避免 std::round
可以获得很大的性能提升,但代价是结果中存在一些(小)差异。
我尝试了一些其他的小改动 HERE,似乎以下是一种更快的方法(至少对于该架构而言)
constexpr int32_t i_max = std::numeric_limits<int16_t>::max();
constexpr int64_t i_max_2 = static_cast<int64_t>(i_max) * i_max;
int16_t my_add_special(int16_t a, int16_t b)
{
// integer multipication instead of floating point division
double numerator = (a + b) * i_max_2;
double denominator = i_max_2 + a * b;
// Approximated rounding instead of std::round
return 0.5 + numerator / denominator;
}
您可能会避免使用浮点数并以整数类型进行所有计算:
constexpr int16_t add_special(int16_t a, int16_t b) {
std::int64_t limit = std::numeric_limits<int16_t>::max();
std::int64_t a_fl = a;
std::int64_t b_fl = b;
return static_cast<int16_t>(((limit * limit) * (a_fl + b_fl)
+ ((limit * limit + a_fl * b_fl) / 2)) /* Handle round */
/ (limit * limit + a_fl * b_fl));
}
但根据 Benchmark,这些值并不快。
对于我正在处理的应用程序,我需要取两个整数并使用特定的数学公式将它们相加。这最终看起来像这样:
int16_t add_special(int16_t a, int16_t b) {
float limit = std::numeric_limits<int16_t>::max();//32767 as a floating point value
float a_fl = a, b_fl = b;
float numerator = a_fl + b_fl;
float denominator = 1 + a_fl * b_fl / std::pow(limit, 2);
float final_value = numerator / denominator;
return static_cast<int16_t>(std::round(final_value));
}
任何对物理学有一定了解的读者都会认识到这个公式与用于计算近光速速度总和的公式相同,这里的计算有意反映了该计算。
所写的代码给出了我需要的结果:对于小数,它们 几乎 正常相加,但对于大数,它们收敛到最大值 32767,即
add_special(10, 15) == 25
add_special(100, 200) == 300
add_special(1000, 3000) == 3989
add_special(10000, 25000) == 28390
add_special(30000, 30000) == 32640
似乎都是正确的。
然而,问题在于所写的函数涉及首先将数字转换为浮点值,然后再将它们转换回整数。对于我所知道的数字来说,这似乎是一个不必要的弯路,作为其领域的原则,永远不会是整数。
是否有更快、更优化的方法来执行此计算?或者这是我可以创建的此功能的最优化版本?
我正在为 x86-64 构建,使用 MSVC 14.X,尽管也适用于 GCC 的方法将是有益的。另外,现阶段我对 SSE/SIMD 优化不感兴趣;我主要只是查看对数据执行的基本操作。
建议:
- 使用
32767.0*32767.0
(常量)代替std::pow(limit, 2)
。 - 尽可能使用整数值,可能使用定点。只是这两个部门是一个问题。如有必要,仅使用浮点数(取决于输入数据范围)。
- 如果功能小,合适的话就
inline
类似于:
int16_t add_special(int16_t a, int16_t b) {
float numerator = int32_t(a) + int32_t(b); // Cannot overflow.
float denominator = 1 + (int32_t(a) * int32_t(b)) / (32767.0 * 32767.0); // Cannot overflow either.
return (numerator / denominator) + 0.5; // Relying on implementation defined rounding. Not good but potentially faster than std::round().
}
以上的唯一风险是省略了显式舍入,因此您会得到一些隐式舍入。
正如 Johannes Overmann 所指出的,通过避免 std::round
可以获得很大的性能提升,但代价是结果中存在一些(小)差异。
我尝试了一些其他的小改动 HERE,似乎以下是一种更快的方法(至少对于该架构而言)
constexpr int32_t i_max = std::numeric_limits<int16_t>::max();
constexpr int64_t i_max_2 = static_cast<int64_t>(i_max) * i_max;
int16_t my_add_special(int16_t a, int16_t b)
{
// integer multipication instead of floating point division
double numerator = (a + b) * i_max_2;
double denominator = i_max_2 + a * b;
// Approximated rounding instead of std::round
return 0.5 + numerator / denominator;
}
您可能会避免使用浮点数并以整数类型进行所有计算:
constexpr int16_t add_special(int16_t a, int16_t b) {
std::int64_t limit = std::numeric_limits<int16_t>::max();
std::int64_t a_fl = a;
std::int64_t b_fl = b;
return static_cast<int16_t>(((limit * limit) * (a_fl + b_fl)
+ ((limit * limit + a_fl * b_fl) / 2)) /* Handle round */
/ (limit * limit + a_fl * b_fl));
}
但根据 Benchmark,这些值并不快。