为什么在循环之前为const指定const和标量值有帮助?

ein*_*ica 5 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;
    }
Run Code Online (Sandbox Code Playgroud)

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

Zet*_*eta 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;
}
};
Run Code Online (Sandbox Code Playgroud)

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

  • include/bits/stl_algobase.h(__ fill,__ fill_n):fill和fill_n的新助手:当复制很便宜时,使用临时来避免每次迭代中读取内存.

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

  typedef typename __type_traits<_Tp>::has_trivial_copy_constructor
    _Trivial;
  std::__fill<_Trivial>::fill(__first, __last, __value);
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 一个标量值是你可以保持值"关闭"并帮助优化器
  • 一个类似于字节的值,您可以使用memset
  • 一个用于所有其他类型,您无法干涉语义.