Итак, этот вопрос возник в основном благодаря сообщениям, подобным этим:
Почему new () / delete () медленнее, чем malloc () / free ()?
РассмотримВы оказались в ситуации, когда:
- вы в основном работаете с простыми старыми типами данных, и вам приходится выполнять много операций выделения / освобождения
- вы работаете с непрерывными блоками памяти
- по какой-то причине производительность действительно имеет значение ...
Идея состоит в том, чтобы создать новый класс распределителя, который может "выбирать" междудва метода, а именно new
и malloc()
.
Допустим, вы будете использовать std::allocator
, если ваш тип не является типом POD,
и использовать такой распределитель, как описано здесь: https://en.cppreference.com/w/cpp/named_req/Allocator (Mallocator)
, если ваш тип равен типу POD
А теперь, чтобы выбрать свой класс распределителя, вы будете использовать что-то вроде этого:
namespace meta
{
template<bool, typename T>
struct Allocator_Impl;
template<typename T>
struct Allocator_Impl<true, T>
{
using type = Mallocator<T>;
};
template<typename T>
struct Allocator_Impl<false, T>
{
using type = std::allocator<T>;
};
template<typename T>
struct is_trivially_allocateable
{
using type = typename
std::conjunction<std::is_trivial<T>,
std::is_standard_layout<T>>::type;
constexpr static inline bool value = type::value;
};
}
template<typename T>
struct Allocator : public meta::Allocator_Impl<meta::is_trivially_allocateable<T>::value, T>
{};
После этого реализовать наш "умный?"Контейнер мы можем просто унаследовать от старого доброго vector
класса, например. Допустим, этот вектор находится где-то в другом пространстве имен, например, sna
:
namespace sna
{
template<typename T> using allocTy = typename Allocator<T>::type;
template<typename T, class Alloc = allocTy<T>>
class vector : public std::vector<T, Alloc>
{
public:
typedef typename std::vector<T, Alloc>::size_type size_type;
typedef T value_type;
typedef Alloc allocator;
typedef std::vector<value_type, allocator> parent;
vector() : parent() {}
vector(const size_type n) :
parent(n)
... implement other constructors if needed ...
};
}
После этого - просто чтобы убедиться - я тоже сравнил распределениераз и придумал:
Распределение 100000000 double:
Продолжительность с вектором нормалей: 281528400 нс.
Продолжительность с вектором заказчиков: 1990600 нс.
Для теста я использовал этот фрагмент кода:
std::chrono::system_clock::time_point start, stop;
const size_t size = 100000000;
std::cout << "Allocating " << size << " doubles:" << std::endl;
start = std::chrono::high_resolution_clock::now();
std::vector<double> new_data(size);
stop = std::chrono::high_resolution_clock::now();
std::cout << "Duration with normal vector: ";
std::cout << std::chrono::duration_cast<std::chrono::nanoseconds>(stop - start).count() << " ns." << std::endl;
start = std::chrono::high_resolution_clock::now();
sna::vector<double> malloc_data(size);
stop = std::chrono::high_resolution_clock::now();
std::cout << "Duration with custom vector: ";
std::cout << std::chrono::duration_cast<std::chrono::nanoseconds>(stop - start).count() << " ns." << std::endl;
Для этого теста я использовал флаг оптимизации -O2.
Итакдля меня vector
, использующий класс Mallocator
, более чем в сто раз быстрее, чем класс std::allocator
, что, на мой взгляд, является значительным приростом производительности.
Но все это в стороне:
Считается ли что-то вроде этого "трюка" плохой практикой, или, если ситуация описана выше, можете ли вы использовать что-то подобное безопасно? Было бы другое лучшее решение, или это просто не стоило бы усилий, и я должен вместо этого придерживаться типа std::vector
?