Написание специального, узкоспециализированного, специализированного, совместимого со стандартами распределителя C ++ - PullRequest
1 голос
/ 20 марта 2019

Краткое предисловие

Я понимаю, что существует много нюансов и требований для совместимого со стандартами распределителя.Здесь есть ряд вопросов, охватывающих ряд тем, связанных с распределителями.Я понимаю, что требования, изложенные в стандарте, имеют решающее значение для обеспечения того, чтобы распределитель функционировал правильно во всех случаях, не пропускал память, не вызывал 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));
  }
}

Буду признателен за любые отзывы, рекомендации или замечания.
Спасибо !!

...