正确测量分拣中的组成基本操作

时间:2017-05-09 03:19:40

标签: c++ sorting c++11 stl

我正在尝试为标准库中的排序功能添加一些遥测功能。我想计算交换次数,移动次数(包括交换调用的次数),以及std::sort()std::stable_sort()函数中的比较操作。

为了做到这一点,我采取了以下步骤:

  1. 创建一个结构来封装我想要衡量的操作的计数
  2. 为比较函数对象创建一个包装器以增加比较计数
  3. 专门化std::swap()函数并增加交换计数
  4. 创建自定义移动函数并从步骤3中创建的专用std :: swap()函数调用该函数
  5. 最后,运行std::sort()std::stable_sort()并收集统计信息
  6. 我得到以下输出,这可能是正确的,因为std::stable_sort()不太可能调用任何交换:

    std::sort(): Moves: 68691, Swaps: 22897, Comparisons: 156959
    std::stable_sort(): Moves: 0, Swaps: 0, Comparisons: 183710
    

    但是,如果我用以下内容替换#4步骤:

    • 专门化std::move()功能并增加移动次数

    std::sort()的输出有0次移动,这绝对不对:

    std::sort(): Moves: 0, Swaps: 22897, Comparisons: 156959
    std::stable_sort(): Moves: 0, Swaps: 0, Comparisons: 183710
    

    此外,std::stable_sort()的编译器(LLVM 8.1.0 on Darwin 16.5.0)实现与move()调用一起使用。所以,我也期待输出中的非零移动计数。

    请看下面我的代码(第一个案例的MVCE,然后是第二个/破案的摘录),看看你是否可以回答这些问题:

    1. 我对统计数据收集的总体方法是否合理?
    2. 为什么第二个案例被破坏了,我该如何解决?
    3. 有没有办法简化(减少冗长)比较仿函数的包装器,至少是std :: ref()部分?
    4. 基本代码(第一种情况):

      #include<iostream>
      #include<sstream>
      #include<vector>
      #include<cassert>
      #include<numeric>
      #include<algorithm>
      
      namespace test {
      
      // struct to store the count of swaps, moves, and comparisons
      struct sort_stats {
          long long int __count_swaps;
          long long int __count_moves;
          long long int __count_comps;
      
          void reset_stats() { __count_swaps = 0; __count_moves = 0; __count_comps = 0; }
      
          void incr_count_swaps() { ++__count_swaps; }
          void incr_count_moves() { ++__count_moves; }
          void incr_count_comps() { ++__count_comps; }
      
          std::string to_str() {
              std::stringstream ss;
              ss << "Moves: " << __count_moves
                 << ", Swaps: " << __count_swaps
                 << ", Comparisons: " << __count_comps;
              return ss.str();
          }
      };
      
      // static instance of stats
      static sort_stats __sort_stats;
      
      // my custom move template
      template<class T>
      inline
      typename std::remove_reference<T>::type&&
      move(T&& x) noexcept
      {
          test::__sort_stats.incr_count_moves();
          return static_cast<typename std::remove_reference<T>::type&&>(x);
      }
      
      // Wrapper for comparison functor
      template <class _Comp>
      class _WrapperComp
      {
          public:
          typedef typename _Comp::result_type result_type;
          typedef typename _Comp::first_argument_type arg_type;
      
          _WrapperComp(_Comp comp) : __comp(comp) { }
          result_type operator()(const arg_type& __x, const arg_type& __y)
          {
              test::__sort_stats.incr_count_comps();
              return __comp(__x, __y);
          }
      
          private:
          _Comp __comp;
      };
      }
      
      // Specialization of std::swap (for int type)
      namespace std {
      template<>
      inline
      void
      swap<int>(int& a, int&b) noexcept(is_nothrow_move_constructible<int>::value
                                        && is_nothrow_move_assignable<int>::value)
      {
          test::__sort_stats.incr_count_swaps();
          using test::move;
          int temp(move(a));
          a = move(b);
          b = move(temp);
      }
      }
      
      using namespace test;
      
      int main()
      {
          const size_t SIZE = 10000;
          auto v = std::vector<int>(SIZE);
          std::iota(v.begin(), v.end(), 0);
      
          auto wrapper_less = _WrapperComp<std::less<int>>(std::less<int>());
          auto ref_wrapper_less = std::ref(wrapper_less);
      
          // Run std::sort() and gather stats
          std::random_shuffle(v.begin(), v.end());
          std::cout << "std::sort(): ";
          __sort_stats.reset_stats();
          std::sort(v.begin(), v.end(), ref_wrapper_less);
          assert(std::is_sorted(v.begin(), v.end()));
          std::cout << __sort_stats.to_str() << "\n";
      
          // Run std::stable_sort() and gather stats    
          std::random_shuffle(v.begin(), v.end());
          std::cout << "std::stable_sort(): ";
          __sort_stats.reset_stats();
          std::stable_sort(v.begin(), v.end(), ref_wrapper_less);
          assert(std::is_sorted(v.begin(), v.end()));
          std::cout << __sort_stats.to_str() << "\n";
      
          return 0;
      }
      

      以下是第二种情况的差异摘录:

      namespace std {
      // Add specialization for std::move()
      template<>
      inline
      std::remove_reference<int>::type&&
      move<int>(int&& x) noexcept
      {
          test::__sort_stats.incr_count_moves();
          return static_cast<std::remove_reference<int>::type&&>(x);
      }
      
      // Invoke std::move() instead of test::move from the specialized std::swap() function
      template<>
      inline
      void
      swap<int>(int& a, int&b) noexcept(is_nothrow_move_constructible<int>::value
                                        && is_nothrow_move_assignable<int>::value)
      {
          test::__sort_stats.incr_count_swaps();
          using std::move;
          int temp(move(a));
          a = move(b);
          b = move(temp);
      }
      }
      

1 个答案:

答案 0 :(得分:0)

我能够根据Igor的建议解决这个问题关于使用自定义类型(而不是我之前使用的int)并检测自定义副本/移动构造函数/赋值运算符。我还为该类型添加了一个自定义的自定义swap()函数,简化了std::swap()函数模板的特化。

现在,我得到以下输出:

std::sort(): Moves: 101606, Copies: 0, Swaps: 32821, Compares: 140451
std::stable_sort(): Moves: 129687, Copies: 0, Swaps: 0, Compares: 121474

如果未定义移动构造函数/赋值运算符,则上面的Moves计数将显示在Copies下。

以下代码有效:

#include<iostream>
#include<vector>
#include<cassert>
#include<numeric>
#include<sstream>
#include<algorithm>

namespace test {

struct sort_stats {
    long long int __count_swaps;
    long long int __count_moves;
    long long int __count_copies;
    long long int __count_comps;

    void reset_stats()
    { __count_swaps = __count_moves = __count_copies = __count_comps = 0; }

    void incr_count_swaps()  { ++__count_swaps; }
    void incr_count_moves()  { ++__count_moves; }
    void incr_count_copies() { ++__count_copies; }
    void incr_count_comps()  { ++__count_comps; }

    std::string to_str() {
        std::stringstream ss;
        ss << "Moves: " << __count_moves
           << ", Copies: " << __count_copies
           << ", Swaps: " << __count_swaps
           << ", Compares: " << __count_comps;
        return ss.str();
    }
};

static sort_stats __sort_stats;

struct my_type
{
    int x;

    // default constructor
    my_type() = default;

    // copy constructor
    my_type(const my_type& other) : x (other.x)
    {
        __sort_stats.incr_count_copies();
    }

    // move constructor
    my_type(my_type&& other) noexcept : x (std::move(other.x))
    {
        __sort_stats.incr_count_moves();
    }

    // copy assignment operator
    my_type& operator=(const my_type& other)
    {
        __sort_stats.incr_count_copies();
        x = other.x;
        return *this;
    }

    // move assignment operator
    my_type& operator=(my_type&& other)
    {
        __sort_stats.incr_count_moves();
        x = std::move(other.x);
        return *this;
    }

    // '<' operator
    bool operator<(const my_type& other) const
    {
        __sort_stats.incr_count_comps();
        return x < other.x;
    }

    // swap 
    void swap(my_type& other)
    {
        __sort_stats.incr_count_swaps();
        my_type temp(std::move(*this));
        *this = std::move(other);
        other = std::move(temp);
    }

    // overload assignment operator for std::iota
    my_type& operator=(const int& val)
    {
        x = val;
        return *this;
    }

};

} // namespace test

namespace std {

using test::my_type;

// std::swap specialized for test::my_type
template<>
inline
void
swap<my_type>(my_type& a, my_type& b) noexcept(is_nothrow_move_constructible<my_type>::value
                                              && is_nothrow_move_assignable<my_type>::value)
{
    a.swap(b);
}
} // namespace std

using namespace test;

int main()
{
  const size_t SIZE = 10000;

  auto v = std::vector<my_type>(SIZE);
  std::iota(v.begin(), v.end(), 0);

  std::random_shuffle(v.begin(), v.end());
  std::cout << "std::sort(): ";
  __sort_stats.reset_stats();
  std::sort(v.begin(), v.end());
  std::cout << __sort_stats.to_str() << "\n";
  assert(std::is_sorted(v.begin(), v.end()));

  std::random_shuffle(v.begin(), v.end());
  std::cout << "std::stable_sort(): ";
  __sort_stats.reset_stats();
  std::stable_sort(v.begin(), v.end());
  std::cout << __sort_stats.to_str() << "\n";
  assert(std::is_sorted(v.begin(), v.end()));

  return 0;
}