I'm trying to accomplish the following:
Start out with 1 interval on the real line. This interval is processed in some function and out comes an array of errors (size < 10 usually). The maximum element of this array is also stored and the interval is put onto a queue sorted by this maximum error.
Then we proceed by taking the interval with the largest error from the queue and split this interval into 2 equal parts. We then do the same operations as above on both these intervals and then put them on the queue as well.
I will demonstrate how I solved this below. But high performance is essential in this application, so I would very much appreciate your input on how I can speed things up here. Is std::vector a bad choice for example?
Here is my Interval class.
Interval.h
#include <queue>
class Interval
{
public:
Interval(size_t dim, double a, double b);
void UpdateInterval(double, double);
std::vector<double> errors; // vector of length dim
double maxError, xmin, xmax, center;
private:
friend bool operator<(const Interval& lhs, const Interval& rhs) { return lhs.maxError < rhs.maxError; }
};
Interval.cpp
#include "Interval.h"
Interval::Interval(size_t fdim, double a, double b)
{
xmin = a;
xmax = b;
center = (xmax + xmin) / 2;
errors.resize(fdim);
maxError = std::numeric_limits<double>::max();
}
void Interval::UpdateInterval(double a, double b)
{
xmin = a;
xmax = b;
center = (xmax + xmin) / 2;
}
In this simplified example I simply populate the vectors with random doubles. I don't care about the performance of this step since I will populate these vectors in a more sophisticated way.
Source.cpp
#include "Interval.h"
typedef std::priority_queue < Interval, std::vector<Interval>, std::less<Interval>> PriorityQueue;
static void SplitInterval(Interval &i1, Interval&i2)
{
size_t dim = i1.errors.size();
double a = i1.xmin;
double b = i1.xmax;
double center = (a + b) / 2.0;
i1.UpdateInterval(a, center);
i2 = Interval(dim, center, b);
}
int main()
{
{
PriorityQueue queue;
Interval i1(5, 0.0, 100.0);
Interval i2(5, 0.0, 100.0);
std::generate(i1.errors.begin(), i1.errors.end(), std::rand);
queue.push(std::move(i1));
for (int counter = 0; counter < 1000; counter++)
{
// A trick because top() returns a const T&
Interval i1 = std::move(const_cast<Interval&>(queue.top()));
queue.pop();
// Splits i1 in half and put the left halft in i1 and the right half in i2
SplitInterval(i1, i2);
// Populate the error arrays with random data
std::generate(i1.errors.begin(), i1.errors.end(), std::rand);
std::generate(i2.errors.begin(), i2.errors.end(), std::rand);
// Calculate the maximum error of each interval
i1.maxError = *max_element(i1.errors.begin(), i1.errors.end());
i2.maxError = *max_element(i2.errors.begin(), i2.errors.end());
// Push the interval onto the queue and it will be sorted according to it's maxError value
queue.push(std::move(i1));
queue.push(std::move(i2));
}
}
return 0;
}
I decided to push( value_type&& value )
instead of push( const value_type &value )
since it's much more efficient (~30% faster). We avoid doing a lot of copies. The drawback is the little trick we have to do for each top()
operation since top()
returns const value_type &
. You have to be really delicate here not to ruin the order of the queue.
You might wonder why I need to have the error array as a class member since I never do anything with it? In my more realistic application I need to subtract the errors from a global error before splitting the intervals in two. So that is the reason.
Really appreciate you feedback on how I can optimize the performance of this in terms of CPU time. Tweak it? Rewrite it from the ground?
errors
? \$\endgroup\$std::array<double, 5>
or a small vector if you don't want a size upper bound (more recent versions of boost has one). \$\endgroup\$