Đây là một lớp đơn giản để lặp lại trên một phạm vi số đa chiều:Tại sao phiên bản đệ quy của hàm này nhanh hơn?
#include <array>
#include <limits>
template <int N>
class NumericRange
{
public:
// typedef std::vector<double>::const_iterator const_iterator;
NumericRange() {
_lower.fill(std::numeric_limits<double>::quiet_NaN());
_upper.fill(std::numeric_limits<double>::quiet_NaN());
_delta.fill(std::numeric_limits<double>::quiet_NaN());
}
NumericRange(const std::array<double, N> & lower, const std::array<double, N> & upper, const std::array<double, N> & delta):
_lower(lower), _upper(upper), _delta(delta) {
_state.fill(std::numeric_limits<double>::quiet_NaN());
_next_index_to_advance = 0;
}
const std::array<double, N> & get_state() const {
return _state;
}
void start() {
_state = _lower;
}
bool in_range(int index_to_advance = N-1) const {
return (_state[ index_to_advance ] - _upper[ index_to_advance ]) < _delta[ index_to_advance ];
}
void advance(int index_to_advance = 0) {
_state[ index_to_advance ] += _delta[ index_to_advance ];
if (! in_range(index_to_advance)) {
if (index_to_advance < N-1) {
// restart index_to_advance
_state[index_to_advance] = _lower[index_to_advance];
// carry
index_to_advance;
advance(index_to_advance+1);
}
}
}
private:
std::array<double, N> _lower, _upper, _delta, _state;
int _next_index_to_advance;
};
int main() {
std::array<double, 7> lower{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
std::array<double, 7> upper{1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0};
std::array<double, 7> delta{0.03, 0.06, 0.03, 0.06, 0.03, 0.06, 0.03};
NumericRange<7> nr(lower, upper, delta);
int c = 0;
for (nr.start(); nr.in_range(); nr.advance()) {
const std::array<double, 7> & st = nr.get_state();
++c;
}
std::cout << "took " << c << " steps" << std::endl;
return 0;
}
Khi tôi thay thế advance
chức năng với một biến thể không đệ quy, thời gian chạy tăng:
void advance(int index_to_advance = 0) {
bool carry;
do {
carry = false;
_state[ index_to_advance ] += _delta[ index_to_advance ];
if (! in_range(index_to_advance)) {
if (index_to_advance < N-1) {
// restart index_to_advance
_state[index_to_advance] = _lower[index_to_advance];
// carry
++index_to_advance;
carry = true;
// advance(index_to_advance);
}
}
} while (carry);
}
Runtimes là sử dụng thời gian người dùng unix thông qua lệnh time
. Mã được biên dịch bằng gcc-4.7 với các tùy chọn -std=c++11 -O3
(nhưng tôi nghĩ nó nên hoạt động với c++0x
trên gcc-4.6). Phiên bản đệ quy mất 13 giây và phiên bản lặp lại mất 30 giây. Cả hai yêu cầu cùng một số cuộc gọi advance
để chấm dứt (và nếu bạn in mảng nr.get_state()
bên trong vòng lặp for(ns.start()...)
, cả hai đều thực hiện tương tự).
Đây là một câu đố vui nhộn! Giúp tôi tìm ra lý do tại sao đệ quy sẽ hiệu quả hơn/tối ưu hơn.
Thử định hình. 'valgrind --callgrind' là một profiler tốt. –
Cá nhân tôi thích gdb. Break + backtrace –
Hiệu suất tôi thấy là không nhất quán. Đối với phiên bản lặp lại, tôi có thể nhận được 30 hoặc 100 giây trên các lần chạy khác nhau. Có lẽ có một vấn đề bộ nhớ đệm tinh tế. –