我在使用clang和float
级别优化的-O3
值类型constexpr
enhanced boost.units的克隆时看到了10%的运行时间开销。这是我正在研究的一些更精细的图书馆应用程序。鉴于这种情况,我有两个问题,我真的很想解决,并希望得到帮助:
详细...
我一直在研究用C ++编写的interactive physics engine 14。由于它使用了许多不同的物理量和单位,我喜欢使用boost.units提供的编译时强制单位和数量。不幸的是,启用增压单元似乎会带来这种运行时成本。该引擎附带一个基准测试应用程序,该应用程序使用谷歌的基准库来提供这种洞察力,并且需要一些更精细的模拟来查看开销。
目前,由于开销,发动机默认情况下不使用增压装置。通过定义正确的预处理器宏名称,可以使用增强单元构建引擎。我使用如下代码实现了这种切换:
// #define USE_BOOST_UNITS
#if defined(USE_BOOST_UNITS)
...
#include <boost/units/systems/si/time.hpp>
...
#endif // defined(USE_BOOST_UNITS)
#if defined(USE_BOOST_UNITS)
#define QUANTITY(BoostDimension) boost::units::quantity<BoostDimension, float>
#define UNIT(Quantity, BoostUnit) Quantity{BoostUnit * float{1}}
#define DERIVED_UNIT(Quantity, BoostUnit, Ratio) Quantity{BoostUnit * float{Ratio}}
#else // defined(USE_BOOST_UNITS)
#define QUANTITY(BoostDimension) float
#define UNIT(Quantity, BoostUnit) float{1}
#define DERIVED_UNIT(Quantity, BoostUnit, Ratio) float{Ratio}}
#endif // defined(USE_BOOST_UNITS)
using Time = QUANTITY(boost::units::si::time);
constexpr auto Second = UNIT(Time, boost::units::si::second);
我对UNIT
宏所做的事情让我感到有点怀疑,因为它采用了升压单位类型并将其转换为值。这使得在使用或不使用增强单元之间切换更容易,因为像3.0f * Second
之类的任何一种方式都可以在没有警告的情检查clang和gcc对这些表达式做了什么似乎确认它们足够聪明,可以避免运行时倍增3.0f * 1.0f
并且只是将表达式识别为3.0f
。无论如何,我想知道这是否是导致开销的原因,或者是否是我做过的其他事情。
我也想知道问题是否可能源于我使用的constexpr
增强代码,或者该代码的作者是否对此开销有所了解。在互联网上搜索时,我发现提到了overhead with the normal boost units library,因此可以安全地假设增强单元没有错。我的查询中出现的suggestion(我感谢GitHub用户muggenhor):
我希望这可能是由编译器完成的内联量引起的。由于运算符的包装函数,这增加了至少一个需要在每个操作中内联的函数调用。对于取决于子表达式结果的表达式,这需要首先内联子表达式。因此,我希望最小量的内联传递能够正确地优化您的代码,使其等于生成的表达式树的深度...
这听起来对我来说是一个非常可行的理论。不幸的是,我不知道如何测试它,并且不可否认我现在更喜欢挖掘自己的代码而不是clang / LLVM代码。我尝试过使用-inline-threshold=10000
,但似乎并没有让开销消失。至少我对clang的理解,我不相信这会特别增加内联通行证的数量。有另一个命令行参数吗?或者在clang的源代码中是否有参数可以指示我将其视为可能重新编译clang并尝试修改后的编译器的起点?
我的另一个理论是使用float
是否是问题。我可以重建我的物理引擎以使用double
代替并在启用和不启用增强单元支持的情况下比较建筑物之间的基准测试结果。我在使用double
时发现的是,开销至少似乎减少了。即使我在其double
模板中使用float
并且可能导致开销,我还是想知道使用quantity
的升级单元是否在某个地方。
最后,我使用performance
增强功能构建了boost单元的constexpr
示例,并使用double
和float
进行了运行。没有任何开销的可靠迹象似乎消除了我float
问题的理论。
使用数据更新&amp;代码
在此处获得了一些更加孤立的数据和代码,看起来我的开销明显超过了10%......
Length
基本上boost::units::si::length
的一些基准数据:
LesserLength/1000 953 ns 953 ns 724870
LesserFloat/1000 590 ns 590 ns 1093647
LesserDouble/1000 619 ns 618 ns 1198938
相关代码的含义如下:
static void LesserLength(benchmark::State& state)
{
const auto vals = RandPairs(static_cast<unsigned>(state.range()),
-100.0f * playrho::Meter, 100.0f * playrho::Meter);
auto c = 0.0f * playrho::Meter;
for (auto _: state)
{
for (const auto& val: vals)
{
const auto a = std::get<0>(val);
const auto b = std::get<1>(val);
static_assert(std::is_same<decltype(b), const playrho::Length>::value, "not Length");
const auto v = (a < b)? a: b;
benchmark::DoNotOptimize(c = v);
}
}
}
static void LesserFloat(benchmark::State& state)
{
const auto vals = RandPairs(static_cast<unsigned>(state.range()),
-100.0f, 100.0f);
auto c = 0.0f;
for (auto _: state)
{
for (const auto& val: vals)
{
const auto a = std::get<0>(val);
const auto b = std::get<1>(val);
const auto v = (a < b)? a: b;
static_assert(std::is_same<decltype(v), const float>::value, "not float");
benchmark::DoNotOptimize(c = v);
}
}
}
static void LesserDouble(benchmark::State& state)
{
const auto vals = RandPairs(static_cast<unsigned>(state.range()),
-100.0, 100.0);
auto c = 0.0;
for (auto _: state)
{
for (const auto& val: vals)
{
const auto a = std::get<0>(val);
const auto b = std::get<1>(val);
const auto v = (a < b)? a: b;
static_assert(std::is_same<decltype(v), const double>::value, "not double");
benchmark::DoNotOptimize(c = v);
}
}
}
以此作为对我的提示,我使用以下代码检查Godbolt以查看clang 5.0.0和gcc 7.2将生成的内容:
#include <algorithm>
#include <boost/units/systems/si/length.hpp>
#include <boost/units/cmath.hpp>
using length = boost::units::quantity<boost::units::si::length, float>;
float f(float a, float b)
{
return a < b? a: b;
}
length f(length a, length b)
{
return a < b? a: b;
}
我看到生成的程序集在两个函数之间以及clang和gcc之间看起来非常不同。这是来自clang的相关程序集的要点(这里的提升内容只显示为length
):
f(float, float): # @f(float, float)
minss xmm0, xmm1
ret
f(length, length)
movss xmm0, dword ptr [rdx] # xmm0 = mem[0],zero,zero,zero
ucomiss xmm0, dword ptr [rsi]
cmova rdx, rsi
mov eax, dword ptr [rdx]
mov dword ptr [rdi], eax
mov rax, rdi
ret
使用-O3
优化的这两个编译器是否应该为length
版本返回与float
版本相同的程序集?问题是他们并没有完全优化到与float
相同的代码吗?似乎这是问题,如果是这样的话,我仍然想弄清楚可以做些什么才能真正获得零开销。