Краткое предисловие
Я понимаю, что существует много нюансов и требований для совместимого со стандартами распределителя.Здесь есть ряд вопросов, охватывающих ряд тем, связанных с распределителями.Я понимаю, что требования, изложенные в стандарте, имеют решающее значение для обеспечения того, чтобы распределитель функционировал правильно во всех случаях, не пропускал память, не вызывал undefined-поведения и т. Д. Это особенно верно там, гдераспределитель предназначен для использования (или, по крайней мере, может использоваться) в широком диапазоне вариантов использования с различными базовыми типами и различными стандартными контейнерами, размерами объектов и т. д.
В отличие от этого, Iу меня есть очень специфический случай использования, когда я лично строго контролирую все условия, связанные с его использованием, как я подробно опишу ниже.Следовательно, я считаю, что то, что я сделал, вполне приемлемо, учитывая весьма специфический характер того, что я пытаюсь реализовать.
Я надеюсь, что кто-то с гораздо большим опытом и пониманием, чем я, может либо подтвердить, что приведенное ниже описание является приемлемым, либо указать на проблемы (и, в идеале, как их исправить).
Обзор / Особые требования
В двух словах, я пытаюсь написать распределитель, который будет использоваться в моем собственном коде и для одного конкретногоЦель:
- Мне нужно «несколько»
std::vector
(вероятно, uint16_t
) с фиксированным (во время выполнения) количеством элементов.Я бенчмаркинг, чтобы определить лучший компромисс производительности / пространства для точного целочисленного типа [1] - Как уже отмечалось, количество элементов всегда одинаково, но это зависит от некоторых данных конфигурации времени выполнения, передаваемых вapplication
- Количество векторов также является либо фиксированным, либо, по крайней мере, ограниченным.Точное число обрабатывается библиотекой, обеспечивающей реализацию
parallel::for(execution::par_unseq, ...)
- . Векторы построены мной (т.е. я точно знаю, что они всегда будут построены с N элементами)
[1] Значение векторов используется для условного копирования числа с плавающей точкой из одного из 2 векторов в цель: c[i] = rand_vec[i] < threshold ? a[i] : b[i]
, где a, b, c
- это непрерывные массивы float
, rand_vec
- это std::vector
Я пытаюсь выяснить здесь, и threshold
является единственной переменной типа integer_tbd
.Код компилируется как операции SSE SIMD.Я не помню деталей этого, но я полагаю, что это требует дополнительных инструкций сдвига, если целые числа меньше, чем числа с плавающей запятой.
На этой основе я написал очень простой распределитель, с одной статическойboost :: lockfree :: queue в качестве свободного списка.Учитывая, что я сам создам векторы, и когда я с ними покончу, они выйдут из области видимости, я с уверенностью знаю, что все вызовы alloc :: deallocate (T *, size_t) всегда будут возвращать векторы одинакового размера,поэтому я считаю, что могу просто отправить их обратно в очередь, не беспокоясь о указателе на выделение другого размера, помещаемом в свободный список.
Как отмечено в приведенном ниже коде, я добавил впока что выполняю тесты для функций allocate и deallocate, в то время как я сам подтверждаю, что таких ситуаций не может и не будет.Опять же, я считаю, что, безусловно, безопасно удалить эти тесты во время выполнения.Хотя некоторые советы и здесь были бы оценены - учитывая окружающий код, я думаю, что они должны быть адекватно обработаны предиктором ветвления, чтобы они не требовали значительных затрат времени выполнения (хотя и без инструментов, трудно сказать на 100%).
В двух словах - насколько я могу судить, все здесь полностью находится под моим контролем, полностью детерминировано в поведении и, таким образом, совершенно безопасно.Это также рекомендуется при запуске кода в типичных условиях - нет ошибок по умолчанию и т. Д. Я еще не пробовал работать с дезинфицирующими средствами - я надеялся получить некоторую обратную связь и рекомендации, прежде чем сделать это.
Я должен отметить, что мой код работает в 2 раза быстрее по сравнению с использованием std::allocator
, что, по крайней мере, качественно следует ожидать.
CR_Vector_Allocator.hpp
class CR_Vector_Allocator {
using T = CR_Range_t; // probably uint16_t or uint32_t, set elsewhere.
private:
using free_list_type = boost::lockfree::queue>;
static free_list_type free_list;
public:
T* allocate(size_t);
void deallocate(T* p, size_t) noexcept;
using value_type = T;
using pointer = T*;
using reference = T&;
template struct rebind { using other = CR_Vector_Allocator;};
};
CR_Vector_Allocator.cc
CR_Vector_Allocator::T* CR_Vector_Allocator::allocate(size_t n) {
if (n <= 1)
throw std::runtime_error("Unexpected number of elements to initialize: " +
std::to_string(n));
T* addr_;
if (free_list.pop(addr_)) return addr_;
addr_ = reinterpret_cast<T*>(std::malloc(n * sizeof(T)));
return addr_;
}
void CR_Vector_Allocator::deallocate(T* p, size_t n) noexcept {
if (n <= 1) // should never happen. but just in case, I don't want to leak
free(p);
else
free_list.push(p);
}
CR_Vector_Allocator::free_list_type CR_Vector_Allocator::free_list;
Используется следующим образом:
using CR_Vector_t = std::vector<uint16_t, CR_Vector_Allocator>;
CR_Vector_t Generate_CR_Vector(){
/* total_parameters is a member of the same class
as this member function and is defined elsewhere */
CR_Vector_t cr_vec (total_parameters);
std::uniform_int_distribution<uint16_t> dist_;
/* urng_ is a member variable of type std::mt19937_64 in the class */
std::generate(cr_vec.begin(), cr_vec.end(), [this, &dist_](){
return dist_(this->urng_);});
return cr_vec;
}
void Prepare_Next_Generation(...){
/*
...
*/
using hpx::parallel::execution::par_unseq;
hpx::parallel::for_loop_n(par_unseq, 0l, pop_size, [this](int64_t idx){
auto crossovers = Generate_CR_Vector();
auto new_parameters = Generate_New_Parameters(/* ... */, std::move(crossovers));
}
}
Буду признателен за любые отзывы, рекомендации или замечания.
Спасибо !!