I have a case where I need to remove elements from a vector if they meet some criteria, which is determined by a function. The layout of my code looks like this:
auto firstToErase = std::remove_if( myVec.begin(), myVec.end(), [&]( const Obj &obj) {
return isEntryInvalid( obj );
} );
myVec.erase( firstToErase, myVec.end() );
This performs the erasure in two steps. One to identify which entries must be removed, another to actually remove them. The erasure cannot be done in parallel, but the identification of entries to be removed can. Conveniently, C++17 offers execution policies to do this. That looks like this:
auto firstToErase = std::remove_if( std::execution::par_unseq, myVec.begin(), myVec.end(), [&]( const Obj &obj) {
return isEntryInvalid( obj );
} );
myVec.erase( firstToErase, myVec.end() );
I have this already written, and they work fantastic. And par_unseq provides a roughly 5x performance improvement. However:
How does that remove_if operation work when std::execution::par_unseq is passed?
A naive approach I can think of would be to hand each std::thread its own std::unordered_set<int> that indicate the indices which need to be erased. Then, once all threads finish, join them together into one large set. (Though I'm not sure if that's compatible with what erase() takes.)
But the problem with that approach is that if many removals need to happen or if you're on a system with many threads, that final merge looks pretty ugly and would eat at the performance benefit. (At least in my experience with implementing a similar approach with std::unordered_map)
So clearly, that must not be what remove_if is doing!
How is this handled now? Is there a more clever solution GCC/Clang are using?
To clarify, I don't need a solution to a problem per-se. I already have working code. I just want to know how remove_if is working under the hood.