I have relatively-small algorithm that takes up ~60% of the total run-time of my scientific code (57 lines of 3600), so I would like to find a way to optimize what I'm doing and make the code order-independent so that I can apply a cilk_for
parallel strcture.
Here's what it does, verbally: I have an std::vector
of pointers to custom objects called Segment
(vector<Segment*> newSegment
). Each Segment
contains a std::vector
of integers (mesh indices). In this function, I would like to find any Segment
that overlaps with any another, with overlap being defined as the member indices
overlapping on the number line. If they do overlap, I would like to join them together (insert the A.indices
into B.indices
) and delete one (delete A
).
ex. 1:
A.indices
={1,2,3} B.indices
={4,5,6} do not overlap; do nothing
ex. 2:
A.indices
={1,2,4} B.indices
={3,5,6} do overlap; A
= deleted B.indices
={1,2,3,4,5,6}
The overlaps are sparse, but existent.
Here's the current code:
main algorithm:
//make sure segments don't overlap
for (unsigned i = 0; i < newSegment.size(); ++i) {
if (newSegment[i]->size() == 0) continue;
for (unsigned j = i + 1; j < newSegment.size(); ++j) {
if (newSegment[i]->size() == 0) continue;
if (newSegment[j]->size() == 0) continue;
int i1 = newSegment[i]->begin();
int i2 = static_cast<int>(newSegment[i]->end());
int j1 = newSegment[j]->begin();
int j2 = static_cast<int>(newSegment[j]->end());
int L1 = abs(i1 - i2);
int L2 = abs(j1 - j2);
int dist = max(i1,i2,j1,j2) - min(i1,i2,j1,j2);
//if overlap, fold segments together
//copy indices from shorter segment to taller segment
if (dist <= L1 + L2) {
unsigned more, less;
if (newSegment[i]->slope == newSegment[j]->slope) {
if (value_max[i] > value_max[j]) {
more = i;
less = j;
} else {
more = j;
less = i;
}
} else if (newSegment[i]->size() == 1) {
more = j; less = i;
} else if (newSegment[j]->size() == 1) {
more = i; less = j;
} else assert(1 == 0);
while(!newSegment[less]->indices.empty()) {
unsigned index = newSegment[less]->indices.back();
newSegment[less]->indices.pop_back();
newSegment[more]->indices.push_back(index);
}
}
}
}//end overlap check
//delete empty segments
vector<unsigned> delList;
for (unsigned i = 0; i < newSegment.size(); ++i) {
if (newSegment[i]->size() == 0) { //delete empty
delList.push_back(i);
continue;
}
}
while (delList.size() > 0) {
unsigned index = delList.back();
delete newSegment.at(index);
newSegment.erase(newSegment.begin() + index);
delList.pop_back();
}
Relevant Segment
object class definition and member functions:
class Segment{
public:
Segment();
~Segment();
unsigned size();
int begin();
unsigned end();
std::vector<int> indices;
double slope;
};
int Segment::begin() {
if (!is_sorted(indices.begin(),indices.end())) std::sort(indices.begin(),indices.end());
if (indices.size() == 0) return -1;
return indices[0];
}
unsigned Segment::end() {
if (!is_sorted(indices.begin(),indices.end())) std::sort(indices.begin(),indices.end());
return indices.back();
}
unsigned Segment::size() {
unsigned indSize = indices.size();
if (indSize == 1) {
if (indices[0] == -1) return 0;
}
return indSize;
}
Ideas:
- Since I don't care about the order of the
Segment
objects, they could be in an orderless container? - In my algorithm, I find overlap by looking at the first and last
indices
of each segment. I do anstd::is_sorted
(and then maybe astd::sort
) when I fetch theindices
because the list can change when more indices are inserted. Maybe I could put theindices
in astd::set
rather thanstd::vector
to save the explicit sort-checking/sorting? I'm pretty sure that by editing the
indices
as I go, this makes it order-dependent. Perhaps, I could break the code into the following organization using the concept of an undirected graph to make it order-independent:- edge discovery (without modifying
indices
) - join clusters of connected nodes (
Segment
objects that overlap) using a graph traversal - delete empty
Segment
objects
- edge discovery (without modifying
Questions
- Are either of the ideas above worthwhile or negligible to performance?
- How else can I optimize it?
- How (if not the above) can I make the algorithm order-independent?
std::is_sorted
( link ). I don't have any mutator methods per se, I justpush_back
directly to the publicindices
vector (e.g.newSegment[j]->indices.push_back(i)
). I do onlypush_back
toindices
in only two other places in the code, so I couldstd::sort
after each one, as well as sort when I combine them in this algorithm, and then delete theis_sorted
/sort
when accessing.