为什么分配const&循环前const的标量值?

时间:2016-10-15 09:38:27

标签: c++ gcc temporary stl-algorithm gcc5

在GCC 5.4.0' stl_algobase.h中我们有:

  template<typename _ForwardIterator, typename _Tp>
    inline typename
    __gnu_cxx::__enable_if<!__is_scalar<_Tp>::__value, void>::__type
    __fill_a(_ForwardIterator __first, _ForwardIterator __last,
         const _Tp& __value)
    {
      for (; __first != __last; ++__first)
    *__first = __value;
    }

  template<typename _ForwardIterator, typename _Tp>
    inline typename
    __gnu_cxx::__enable_if<__is_scalar<_Tp>::__value, void>::__type
    __fill_a(_ForwardIterator __first, _ForwardIterator __last,
         const _Tp& __value)
    {
      const _Tp __tmp = __value;
      for (; __first != __last; ++__first)
    *__first = __tmp;
    }

我不明白为什么标量变体比一般变体有任何优势。我的意思是,他们不会被编译成完全相同的东西吗?将堆栈中的__value加载到寄存器中并在整个循环中使用该寄存器?

1 个答案:

答案 0 :(得分:5)

这起源于2004年的SVN rev 83645(git commit 8ba26e53),当两个__fill_a变体实现为辅助结构时:

template<typename>
struct __fill
{
  template<typename _ForwardIterator, typename _Tp>
    static void
    fill(_ForwardIterator __first, _ForwardIterator __last,
     const _Tp& __value)
    {
  for (; __first != __last; ++__first)
    *__first = __value;
}
};

template<>
struct __fill<__true_type>
{
  template<typename _ForwardIterator, typename _Tp>
    static void
    fill(_ForwardIterator __first, _ForwardIterator __last,
     const _Tp& __value)
    {
  const _Tp __tmp = __value;
  for (; __first != __last; ++__first)
    *__first = __tmp;
}
};

关于此主题的文档很少,但Dan Nicolaescu和Paolo Carlini的原始提交在提交消息中包含提示:

  
      
  • include / bits / stl_algobase.h(__fill,__ fill_n):新助手   对于fill和fill_n,分别为: 当复制便宜时,使用a   临时避免每次迭代读取内存。
  •   

鉴于那些是标准库的维护者,我认为他们知道自己在做什么:他们解决了引用通常被实现为指针的问题。毕竟,它们只是已存在的内存位置的新别名。这就是为什么最初有两个变种的原因。请注意,__true_type已在fill电话中决定:

  typedef typename __type_traits<_Tp>::has_trivial_copy_constructor
    _Trivial;
  std::__fill<_Trivial>::fill(__first, __last, __value);

使用std::enable_if,或者更确切地说是其GCC变体,Carlini删除了这些帮助程序,并用您已经提供的版本替换它们。逻辑仍然存在:对于标量类型,您希望拥有一些本地值。如果您的范围位于另一个内存区域而不是您的值并且跨越多个页面并溢出您的L1缓存,那么您不希望保留一些缓存锁定该值。并且局部变量很简单。

然而,语义很重要。 std::fill生成完全std::distance(first, last)个副本。使用标量值,我们知道额外的副本不会产生副作用。用户定义的类型?好吧,我们不知道。这就是为什么在第一种情况下你不能使用const auto tmp = __value;变种的原因。

这就是为什么你最终选择了两个,实际上是三个变体:

  • 标量值之一是你可以保持价值&#34;关闭&#34;并帮助优化器
  • 一个类似于字节的值,您可以使用memset
  • 所有其他类型的一个,你不能干涉语义。