我正在尝试为标准库中的排序功能添加一些遥测功能。我想计算交换次数,移动次数(包括交换调用的次数),以及std::sort()
和std::stable_sort()
函数中的比较操作。
为了做到这一点,我采取了以下步骤:
std::swap()
函数并增加交换计数std::sort()
和std::stable_sort()
并收集统计信息我得到以下输出,这可能是正确的,因为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,然后是第二个/破案的摘录),看看你是否可以回答这些问题:
基本代码(第一种情况):
#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);
}
}
答案 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;
}