5

在 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到寄存器中并在整个循环中使用该寄存器?

4

1 回答 1

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 的新助手:当复制成本低时,使用临时避免在每次迭代中读取内存。

鉴于这些是/曾经是标准库的维护者,我认为他们知道他们在做什么:他们解决了引用通常实现为指针的问题。毕竟,它们只是已经存在的内存位置的新别名。这就是为什么最初有两种变体。请注意,这__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;在第一种情况下使用该变体。

这就是为什么你最终会得到两个,嗯,实际上是三个变体:

  • 一个标量值是你可以保持值“关闭”并帮助优化器
  • 一个用于类似字节的值,您可以在其中使用 memset
  • 一种适用于所有其他类型,您不能干预语义。
于 2016-10-15T14:44:15.283 回答