【问题标题】:optimizing of std::visit possible?可以优化 std::visit 吗?
【发布时间】:2018-03-07 07:46:01
【问题描述】:

在使用std::visit / std::variant 时,我在分析器输出中看到std::__detail::__variant::__gen_vtable_impl 函数花费的时间最多。

我做了这样的测试:

// 3 class families, all like this
class ElementDerivedN: public ElementBase
{
    ...
        std::variant<ElementDerived1*, ElementDerived2*,... > GetVariant() override { return this; }
}

std::vector<Element*> elements;
std::vector<Visitor*> visitors;
std::vector<Third*>   thirds;

// prepare a hack to get dynamic function object:
template<class... Ts> struct funcs : Ts... { using Ts::operator()...; };
template<class... Ts> funcs(Ts...) -> funcs<Ts...>;

// demo functions:
struct Actions { template < typename R, typename S, typename T> void operator()( R*, S*, T* ) {} };
struct SpecialActionForElement1{ template < typename S, typename T > void operator()( Element1*, S*, T* ) {} };


for ( auto el: elements )
{
    for ( auto vis: visitors )
    {
        for ( auto th: thirds )
        {
            std::visit( funcs{ Actions(), SpecialActionForElement1Derived1()}, el->GetVariant(), vis->GetVariant(), th->GetVariant() );
        }
    }
}

如前所述,std::__detail::__variant::__gen_vtable_impl&lt;...&gt; 花费的时间最多。

问: 由于每次访问调用时生成的 n 维函数数组从调用到调用都相同,因此最好将其保留在 std::visit 的调用之间。这可能吗?

也许我走错了路,如果是,请告诉我!

编辑: 使用标准 Fedora 安装中的编译器 gcc7.3。 std-lib 在 g++ 中被用作标准(这是什么)

构建选项:

g++ --std=c++17 -fno-rtti main.cpp -O3 -g -o go

【问题讨论】:

  • 你在 std::variant 中使用多态对象吗?也许您可以通过完全避免 std::variant 来简化数据。变体实际上是昂贵的东西,也不是最佳的。如果你使用简单的继承,你给编译器一个机会来优化你的代码,通过去虚拟化等。
  • @VictorGubin:是的,这是众所周知的。原因是标准的访问者模式不能用模板(多分派)实现,因为虚拟模板在 C++ 中是不可能的。因此,使用变体/访问是解决方法。正如所见,当 N=2 时,该解决方案需要多花大约 60% 的时间来调度。这对我来说是可以接受的,但也许可以优化(该问题的原因)。与经典的访问者模式实现一样,我的具体 sw 的设计更简单,更容易阅读变体/访问。示例中的简化并没有反映我真正的软件需求!
  • 从我的(不仅仅是我的,比如Thomas Kyte)的角度来看,如果你考虑优化,你应该从设计开始,然后再转向底层。我不认为低水平
  • @VictorGubin:您可以向 XY 提出任何问题。但是这个问题询问了具体功能的潜在优化选项。发现的行为让 me 假设一个对象将生成一个临时对象,该对象可以在调用之间保留。那么这与我的申请有什么关系呢?我进行了测量,发现正是在这一点上消耗了时间。所以我认为要求改进是一个重点。也许这是其他人也可以用来使他们的代码更快的想法。而且这个问题不是“我的程序很慢,请帮忙”。
  • @PaulR:每个变体包含 3 种类型,应该给出一个 3x3 矩阵。这也是我想知道的!

标签: c++ optimization variant visitor-pattern


【解决方案1】:

我刚刚看了一个更简单的example。该表是在编译时生成的。时间可能花在std::__detail::__variant::__gen_vtable_impl&lt;...&gt; 中生成的 lambdas 上。出于某种原因,这些基本上调用访问者的 lambdas 不会忽略对变体实际类型的检查。

此函数允许编译器为四个不同版本的访问 lambda 内联到在 std::visit 中创建的 lambdas 中创建代码,并将指向这些 lambdas 的指针存储在静态数组中:

double test(std::variant<int, double> v1, std::variant<int, double> v2) {
    return std::visit([](auto a, auto b) -> double {
        return a + b;
        }, v1, v2);
}

这是在测试中创建的:

  (...) ; load variant tags and check for bad variant
  lea rax, [rcx+rax*2] ; compute index in array
  mov rdx, rsi
  mov rsi, rdi
  lea rdi, [rsp+15]
  ; index into vtable with rax
  call [QWORD PTR std::__detail::__variant::(... bla lambda bla ...)::S_vtable[0+rax*8]]

这是为&lt;double, double&gt; 访客生成的:

std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<double (*)(test(std::variant<int, double>, std::variant<int, double>)::{lambda(auto:1, auto:2)#1}&&, std::variant<int, double>&, test(std::variant<int, double>, std::variant<int, double>)::{lambda(auto:1, auto:2)#1}&&)>, std::tuple<test(std::variant<int, double>, std::variant<int, double>)::{lambda(auto:1, auto:2)#1}&&, test(std::variant<int, double>, std::variant<int, double>)::{lambda(auto:1, auto:2)#1}&&>, std::integer_sequence<unsigned long, 1ul, 1ul> >::__visit_invoke(test(std::variant<int, double>, std::variant<int, double>)::{lambda(auto:1, auto:2)#1}, test(std::variant<int, double>, std::variant<int, double>)::{lambda(auto:1, auto:2)#1}&&, test(std::variant<int, double>, std::variant<int, double>)::{lambda(auto:1, auto:2)#1}&&):
; whew, that is a long name :-)
; redundant checks are performed whether we are accessing variants of the correct type:
      cmp BYTE PTR [rdx+8], 1
      jne .L15
      cmp BYTE PTR [rsi+8], 1
      jne .L15
; the actual computation:
      movsd xmm0, QWORD PTR [rsi]
      addsd xmm0, QWORD PTR [rdx]
      ret

如果分析器将这些类型检查的时间和内联访问者的时间都归因于 std::__detail::__variant::__gen_vtable_impl&lt;...&gt;,而不是为您提供深度嵌套的 lambda 的完整 800 多个字符名称,我不会感到惊讶。

我在这里看到的唯一通用优化潜力是省略对 lambda 中的坏变体的检查。由于 lambda 仅通过函数指针调用匹配变量,因此编译器将很难静态地发现检查是多余的。

我查看了same example compiled with clang and libc++。在 libc++ 中,多余的类型检查被消除了,所以 libstdc++ 还不是很理想。

decltype(auto) std::__1::__variant_detail::__visitation::__base::__dispatcher<1ul, 1ul>::__dispatch<std::__1::__variant_detail::__visitation::__variant::__value_visitor<test(std::__1::variant<int, double>, std::__1::variant<int, double>)::$_0>&&, std::__1::__variant_detail::__base<(std::__1::__variant_detail::_Trait)0, int, double>&, std::__1::__variant_detail::__base<(std::__1::__variant_detail::_Trait)0, int, double>&>(std::__1::__variant_detail::__visitation::__variant::__value_visitor<test(std::__1::variant<int, double>, std::__1::variant<int, double>)::$_0>&&, std::__1::__variant_detail::__base<(std::__1::__variant_detail::_Trait)0, int, double>&, std::__1::__variant_detail::__base<(std::__1::__variant_detail::_Trait)0, int, double>&): # @"decltype(auto) std::__1::__variant_detail::__visitation::__base::__dispatcher<1ul, 1ul>::__dispatch<std::__1::__variant_detail::__visitation::__variant::__value_visitor<test(std::__1::variant<int, double>, std::__1::variant<int, double>)::$_0>&&, std::__1::__variant_detail::__base<(std::__1::__variant_detail::_Trait)0, int, double>&, std::__1::__variant_detail::__base<(std::__1::__variant_detail::_Trait)0, int, double>&>(std::__1::__variant_detail::__visitation::__variant::__value_visitor<test(std::__1::variant<int, double>, std::__1::variant<int, double>)::$_0>&&, std::__1::__variant_detail::__base<(std::__1::__variant_detail::_Trait)0, int, double>&, std::__1::__variant_detail::__base<(std::__1::__variant_detail::_Trait)0, int, double>&)"
  ; no redundant check here
  movsd xmm0, qword ptr [rsi] # xmm0 = mem[0],zero
  addsd xmm0, qword ptr [rdx]
  ret

也许您可以检查您的生产软件中实际生成的代码,以防万一它与我在示例中找到的不相似。

【讨论】:

  • 感谢您的工作!正如我在我的程序集中发现的那样,没有静态生成的表,而是生成器函数。所以我会把我的示例代码分解到编译器生成静态解决方案的地步。也许我找到了阻碍优化的关键。
  • 这可能与 constexpr 计算的大小或时间限制有关,请参阅answer。也许你可以调整-fconstexpr-depth 选项。
猜你喜欢
  • 1970-01-01
  • 2016-07-21
  • 1970-01-01
  • 2012-01-01
  • 2013-03-05
相关资源
最近更新 更多