可以优化 std::visit 吗?

2024-04-06

使用时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<...>花费最多的时间。

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

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

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

构建选项:

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

我刚刚看了一个更简单的example https://gcc.godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(j:1,lang:c%2B%2B,source:'%23include+%3Cvariant%3E%0A%0Adouble+test(std::variant%3Cint,+double%3E+v1,+std::variant%3Cint,+double%3E+v2)+%7B%0A++++return+std::visit(%5B%5D(auto+a,+auto+b)+-%3E+double+%7B%0A++++++++return+a+%2B+b%3B%0A++++++++%7D,+v1,+v2)%3B%0A%7D%0A'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:33.333333333333336,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:g73,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'0',trim:'0'),lang:c%2B%2B,libs:!(),options:'-std%3Dc%2B%2B17+-O3+-Wall',source:1),l:'5',n:'0',o:'x86-64+gcc+7.3+(Editor+%231,+Compiler+%231)+C%2B%2B',t:'0')),k:33.333333333333336,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:output,i:(compiler:1,editor:1),l:'5',n:'0',o:'%231+with+x86-64+gcc+7.3',t:'0')),k:33.33333333333333,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4。该表是在编译时生成的。时间可能花在生成的 lambda 上std::__detail::__variant::__gen_vtable_impl<...>。由于某种原因,这些基本上调用访问者的 lambda 不会忽略对变体实际类型的检查。

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

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]]

这是为<double, double>游客:

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<...>,而不是为您提供深度嵌套 lambda 的完整 800 多个字符名称。

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

我看了一下使用 clang 和 libc++ 编译的相同示例 https://gcc.godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(j:1,lang:c%2B%2B,source:'%23include+%3Cvariant%3E%0A%0Adouble+test(std::variant%3Cint,+double%3E+v1,+std::variant%3Cint,+double%3E+v2)+%7B%0A++++return+std::visit(%5B%5D(auto+a,+auto+b)+-%3E+double+%7B%0A++++++++return+a+%2B+b%3B%0A++++++++%7D,+v1,+v2)%3B%0A%7D%0A'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:33.333333333333336,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:clang600,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'0',trim:'0'),lang:c%2B%2B,libs:!(),options:'-std%3Dc%2B%2B17+-O3+-Wall+-stdlib%3Dlibc%2B%2B',source:1),l:'5',n:'0',o:'x86-64+clang+6.0.0+(Editor+%231,+Compiler+%231)+C%2B%2B',t:'0')),k:33.333333333333336,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:output,i:(compiler:1,editor:1),l:'5',n:'0',o:'%231+with+x86-64+clang+6.0.0',t:'0')),k:33.33333333333333,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4。在 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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

可以优化 std::visit 吗? 的相关文章

随机推荐