c – 如果构造和销毁了许多向量,自定义分配器是否会提高性能?

前端之家收集整理的这篇文章主要介绍了c – 如果构造和销毁了许多向量,自定义分配器是否会提高性能?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
在下面的代码中,每10个整数的许多向量构造有60%的几率,或者现有的向量被删除,有40%的几率.因此,会有很多调用new / malloc和delete. @H_502_2@由于所有这些向量都是vector< int>类型,自定义分配器可以帮助减少对new和delete的调用,从而提高性能吗?这个想法是删除的矢量的空间可以由新构造的空间重用.这样的分配器怎么样?

注意:这个问题是关于分配器,它减少了对new和delete的调用.

  1. #include <iostream>
  2. #include <vector>
  3. #include <random>
  4.  
  5. using namespace std;
  6.  
  7. int main()
  8. {
  9. // Random generator and distribution
  10. mt19937 gen(123456);
  11. uniform_real_distribution<> dis01(0.,1.);
  12.  
  13. // Make or delete 10E6 vectors.
  14. vector< vector<int> > v; //the inner vectors will make many calls to new and delete
  15.  
  16. v.reserve(10E5); //assume some size.
  17.  
  18. for(int i=0; i<10E6; ++i)
  19. {
  20. if(dis01(gen)<0.6) // if true: make new sub-vector
  21. {
  22. v.emplace_back(); //new sub-vector
  23. v.back().reserve(10);
  24.  
  25. for(int k=0; k<10; ++k)
  26. v.back().emplace_back(k); //fill it with some numbers
  27. }
  28. else // else,delete the last entry if there is one.
  29. if(!v.empty())
  30. v.pop_back();
  31. }
  32.  
  33. cout<<"v.size()= "<<v.size();
  34. return 0;
  35. }

解决方法

这适用于C 11.较旧的标准需要额外的东西 @H_502_2@在分配器[1]中实现.

这是概念验证代码.它运行并解决了这个例子@H_502_2@问题但受到一些限制.它仍然@H_502_2@演示了如何使用自定义分配器来改进@H_502_2@在很多std :: vectors的场景中的性能@H_502_2@创造和破坏.

PoolAlloc.hh:

  1. template<typename T>
  2. struct MemChunk
  3. {
  4. std::size_t buf_size=0;
  5. T* buf=nullptr;
  6. T* top=nullptr;
  7. std::size_t used=0;
  8. };
  9.  
  10. template<typename T>
  11. class PoolAllocator
  12. {
  13. public:
  14. using value_type=T;
  15.  
  16. PoolAllocator();
  17. explicit PoolAllocator(std::size_t);
  18. PoolAllocator(PoolAllocator const&) noexcept;
  19. template<typename U>
  20. PoolAllocator(PoolAllocator<U> const&) noexcept;
  21. PoolAllocator(PoolAllocator&&) noexcept;
  22. PoolAllocator& operator=(PoolAllocator const&)=delete;
  23. PoolAllocator& operator=(PoolAllocator&&)=delete;
  24. ~PoolAllocator();
  25.  
  26. template <typename U>
  27. struct rebind
  28. {
  29. using other=PoolAllocator<U>;
  30. };
  31.  
  32. T* allocate(std::size_t);
  33. void deallocate(T*,std::size_t) noexcept;
  34.  
  35. template<typename U1,typename U2>
  36. friend bool operator==(PoolAllocator<U1> const&,PoolAllocator<U2> const&) noexcept;
  37.  
  38. private:
  39. std::vector<MemChunk<T>>* memory_=nullptr;
  40. int* ref_count_=nullptr;
  41. std::size_t default_buf_size_=0;
  42. };
  43.  
  44. template<typename T>
  45. PoolAllocator<T>::PoolAllocator():
  46. PoolAllocator{100000} {}
  47.  
  48. template<typename T>
  49. PoolAllocator<T>::PoolAllocator(std::size_t buf_size):
  50. memory_{new std::vector<MemChunk<T>>},ref_count_{new int(0)},default_buf_size_{buf_size}
  51. {
  52. memory_->emplace_back();
  53. memory_->back().buf_size=buf_size;
  54. memory_->back().buf=new T[buf_size];
  55. memory_->back().top=memory_->back().buf;
  56. ++(*ref_count_);
  57. }
  58.  
  59. template<typename T>
  60. PoolAllocator<T>::PoolAllocator(PoolAllocator const& src) noexcept:
  61. memory_{src.memory_},ref_count_{src.ref_count_},default_buf_size_{src.default_buf_size_}
  62. {
  63. ++(*ref_count_);
  64. }
  65.  
  66. template<typename T>
  67. PoolAllocator<T>::PoolAllocator(PoolAllocator&& src) noexcept:
  68. memory_{src.memory_},default_buf_size_{src.default_buf_size_}
  69. {
  70. src.memory_=nullptr;
  71. src.ref_count_=nullptr;
  72. }
  73.  
  74. template<typename T>
  75. template<typename U>
  76. PoolAllocator<T>::PoolAllocator(PoolAllocator<U> const& src) noexcept:
  77. memory_{src.memory_},default_buf_size_{src.default_buf_size_}
  78. {
  79. ++(*ref_count_);
  80. }
  81.  
  82. template<typename T>
  83. PoolAllocator<T>::~PoolAllocator()
  84. {
  85. if (ref_count_!=nullptr)
  86. {
  87. --(*ref_count_);
  88. if (*ref_count_==0)
  89. {
  90. if (memory_!=nullptr)
  91. {
  92. for (auto& it : *memory_)
  93. {
  94. delete[] it.buf;
  95. }
  96. delete memory_;
  97. }
  98. delete ref_count_;
  99. }
  100. }
  101. }
  102.  
  103. template<typename T>
  104. T*
  105. PoolAllocator<T>::allocate(std::size_t n)
  106. {
  107. MemChunk<T>* mem_chunk=&memory_->back();
  108. if ((mem_chunk->used+n)>mem_chunk->buf_size)
  109. {
  110. default_buf_size_*=2;
  111. memory_->emplace_back();
  112. mem_chunk=&memory_->back();
  113. std::size_t buf_size=default_buf_size_;
  114. if (n>default_buf_size_)
  115. {
  116. buf_size=n;
  117. }
  118. mem_chunk->buf_size=buf_size;
  119. mem_chunk->buf=new T[mem_chunk->buf_size];
  120. mem_chunk->top=mem_chunk->buf;
  121. }
  122. T* r=mem_chunk->top;
  123. mem_chunk->top+=n;
  124. mem_chunk->used+=n;
  125. return r;
  126. }
  127.  
  128. template<typename T>
  129. void
  130. PoolAllocator<T>::deallocate(T* addr,std::size_t n) noexcept
  131. {
  132. MemChunk<T>* mem_chunk=&memory_->back();
  133. if (mem_chunk->used>n and (mem_chunk->top-n)==addr)
  134. {
  135. mem_chunk->used-=n;
  136. mem_chunk->top-=n;
  137. }
  138. }
  139.  
  140. template<typename U1,typename U2>
  141. bool operator==(PoolAllocator<U1> const& lhs,PoolAllocator<U2> const& rhs) noexcept
  142. {
  143. return (std::is_same<U1,U2>::value and lhs.memory_==rhs.memory_);
  144. }

使用以下方式修改的示例:

  1. #include <iostream>
  2. #include <vector>
  3. #include <random>
  4. #include "PoolAlloc.hh"
  5.  
  6. using namespace std;
  7.  
  8. int main()
  9. {
  10. // Random generator and distribution
  11. mt19937 gen(123456);
  12. uniform_real_distribution<> dis01(0.,1.);
  13. PoolAllocator<int> palloc{1000000};
  14.  
  15. // Make or delete 10E6 vectors.
  16. vector< vector<int,PoolAllocator<int>> > v; //the inner vectors will make many calls to new and delete
  17.  
  18. v.reserve(10E5); //assume some size.
  19.  
  20. for(int i=0; i<10E6; ++i)
  21. {
  22. if(dis01(gen)<0.6) // if true: make new sub-vector
  23. {
  24. v.emplace_back(palloc); //new sub-vector
  25. v.back().reserve(10);
  26.  
  27. for(int k=0; k<10; ++k)
  28. v.back().emplace_back(k); //fill it with some numbers
  29. }
  30. else // else,delete the last entry if there is one.
  31. if(!v.empty())
  32. v.pop_back();
  33. }
  34.  
  35. cout<<"v.size()= "<<v.size();
  36. return 0;
  37. }

对malloc的调用次数从~6e6下降到21@H_502_2@指令数从3.7e9下降到2.5e9(使用-O3,@H_502_2@用valgrind测量–tool = callgrind).

有一些实施细节会影响到@H_502_2@在不同的使用情况下的表现.

目前使用多个缓冲区.如果一个满了,另一个满了@H_502_2@被建造.这种方式永远不必重新分配@H_502_2@操作会让你进入一个受伤的世界(见@H_502_2@评论).

最大的问题是,如何处理解除分配的内存.@H_502_2@目前使用的是一种简单的方法,只能进行解除分配@H_502_2@内存可用于稍后在它结束时分配@H_502_2@缓冲.对于你的例子就足够了,就像你一样@H_502_2@在缓冲区的末尾释放内存.

对于更复杂的场景,您需要更复杂的场景@H_502_2@机制.存储地址需要一些数据结构@H_502_2@和可用内存块的大小.多个概念是可能的@H_502_2@在这里,他们的表现会因具体情况而有所不同@H_502_2@它们被用于.我怀疑有一个很好的一刀切@H_502_2@解决方案在这

[1] http://howardhinnant.github.io/stack_alloc.html

猜你在找的C&C++相关文章