漫漫开发路|Build Insights对模板代码进行性能分析,使用C++( 二 )


4.在WPA中打开信息跟踪文件 。
我们打开[构建资源管理器]和[模板实例化]视图 。 [构建资源管理器]视图指示编译持续了大约13.5秒 , 这可以通过查看视图底部的时间轴(标记为A)来看出来 。 [模板实例化]视图显示了在时间8到10.5(标记为B)之间某个位置的模板实例化活动 。
漫漫开发路|Build Insights对模板代码进行性能分析,使用C++
文章图片
默认情况下 , 所有模板的特化都按主模板的名称进行分组 。 例如 , std::vector和std::vector的特化都将归类到std::vector主模板下 。
在我们的案例研究中 , 我们想知道是否存在一种可能出问题的模板特化 , 因此我们重新组织了视图的列 , 以便按[特化名称]对列表进行分组 。 具体操作如下所示:
漫漫开发路|Build Insights对模板代码进行性能分析,使用C++
文章图片
我们注意到 , sprout::tpp::all_of的模板实例化大约需要2.15秒 。 另外 , 还发现sprout::tpp::detail::all_of_impl这个模板有多达511次实例化 。 因此 , 我们推测:sprout::tpp::all_of是sprout::tpp::detail::all_of_impl递归调用的根本原因 。 分析结果如下图所示:
漫漫开发路|Build Insights对模板代码进行性能分析,使用C++
文章图片
代码分析
通过对代码的分析 , 我们发现在源文件sproutrandomshuffle_order.hpp中 , 对以下类型的operator()调用触发了对sprout::tpp::all_of的实例化 。 typedefsprout::random::shuffle_order_engine<sprout::random::minstd_rand0,256>knuth_b;
这个类型在内部包含一个256个元素的数组 , 最终将其传递到sproutcontainercontainer_construct_traits.hpp头文件中的default_remake_container函数 。
该函数具有以下三个模板定义 。 为了简单起见 , 模板定义代码已替换为简单的注释 。
漫漫开发路|Build Insights对模板代码进行性能分析,使用C++
文章图片
这些定义全部使用std::enable_if标准类型traits类来实现在特定条件启用或禁用 。 你可以看出来在第二个定义的std::enable_if条件中发现对sprout::tpp::all_of的调用吗?下面我们来复制它:
!(sizeof…(Args)==2&&sprout::tpp::all_of<sprout::is_input_iterator<typenamestd::remove_reference::type>…>::value)
从总体上看 , 如果调用default_remake_container时使用的参数数量不是2 , 则无需对sprout::tpp::all_of进行求值 。 在我们的例子中 , 我们有256个参数 , 并且知道无论sprout::tpp::all_of返回什么条件 , 该条件都将为false 。
在编译器看来 , 这无关紧要 。 它在解析对default_remake_container的调用时 , 还是会尝试在我们的256个参数上对sprout::tpp::all_of进行求值 , 从而导致非常耗时的递归模板实例化 。
寻找解决方法
我们通过在default_remake_container和sprout::tpp::all_of调用之间添加一个间接级别来解决这种情况 。 我们首先讨论参数的数量:
漫漫开发路|Build Insights对模板代码进行性能分析,使用C++
文章图片
仅当确认参数计数为2时 , 我们才通过名为default_remake_container_two_args的新函数来对sprout::tpp::all_of进行求值:
漫漫开发路|Build Insights对模板代码进行性能分析,使用C++
文章图片
评估最终结果
经过上面的代码修改之后 , 我们再次编译工程并收集编译信息 。 我们注意到编译时间减少了约25% , 总计约9.7秒 。 [模板实例化视图]也消失了 , 这说明模板实例化是影响编译时间的主要因素 , 简而言之:我们胜利了!


推荐阅读