Ответ 1
Я бы не попытался сделать объединение segmentsLarge
и segmentsSmall
. Да, это отвлекает еще один указатель. Тогда указатель, позволяет называть его просто segments
, может первоначально указывать на сегментыSmall.
С другой стороны, другие методы всегда могут использовать один и тот же указатель, который упрощает их.
И переключение с малого на большое может быть достигнуто путем сравнения обменного указателя.
Я не уверен, как это можно сделать безопасно с помощью объединения.
Идея будет выглядеть примерно так (обратите внимание, что я использовал С++ 11, который предшествовал библиотеке Intel, поэтому они, вероятно, сделали это с их атомными внутренними функциями). Это, вероятно, пропустит немало деталей, которые, я уверен, люди Intel думали больше, поэтому вам, скорее всего, придется это проверить против реализаций всех других методов.
#include <atomic>
#include <array>
#include <cstddef>
#include <climits>
template<typename T>
class concurrent_vector
{
private:
std::atomic<size_t> size;
std::atomic<T**> segments;
std::array<T*, 3> segmentsSmall;
unsigned lastSegmentIndex = 0;
void switch_to_large()
{
T** segmentsOld = segments;
if( segmentsOld == segmentsSmall.data()) {
// not yet switched
T** segmentsLarge = new T*[sizeof(size_t) * CHAR_BIT];
// note that we leave the original segment allocations alone and just copy the pointers
std::copy(segmentsSmall.begin(), segmentsSmall.end(), segmentsLarge);
for(unsigned i = segmentsSmall.size(); i < numSegments; ++i) {
segmentsLarge = nullptr;
}
// now both the old and the new segments array are valid
if( segments.compare_exchange_strong(segmentsOld, segmentsLarge)) {
// success!
return;
} else {
// already switched, just clean up
delete[] segmentsLarge;
}
}
}
public:
concurrent_vector() : size(0), segments(segmentsSmall.data())
{
//The initial array is contiguous just for the sake of cache optimization
T* initialContiguousBlock = new T[2 + 4 + 8]; //2^1 + 2^2 + 2^3
segmentsSmall[0] = initialContiguousBlock;
segmentsSmall[1] = initialContiguousBlock + 2;
segmentsSmall[2] = initialContiguousBlock + 2 + 4;
}
void push_back(T& item)
{
if(size > 2 + 4 + 8) {
switch_to_large();
}
// here we may have to allocate more segments atomically
++size;
//afterwards adds the item to the appropriate slot in the appropriate segment
}
};