用户空间内存碎片

时间:2017-08-07 19:02:47

标签: c++ c++11

让我们假设一个非常基本的c ++程序,它分配了大量的小std :: vector' s。 我真的不知道编译器和操作系统如何将这些向量放在进程内存空间中,但如果数量很大,我认为某些向量可能靠近(接近)。

现在,让我假设我删除了一些内存中的向量,并保留了其他一些向量。 想象一下,我想在第一个向量中添加10000个项目。

如果第二个向量在内存中太近会发生什么? 你认为我会得到一个低记忆"错误,还是OS应该移动第一个向量?

2 个答案:

答案 0 :(得分:3)

不,矢量彼此接近并不重要。只有当向量达到没有连续内存块来保存其内存的大小时,才会出现错误(对于默认分配器,将抛出std::bad_alloc异常)。

内部发生的事情类似于移动的意思,但在C ++ 11中,该术语具有不同的含义,因此我将尽量避免这种情况,而宁愿将其重新分配。另请注意,操作系统与它无关。

让我们看一下细节:

std::vector是连续的是正确的,但(与std::array相反)其元素不会直接存储在std::vector实例本身内。相反,它将底层数组存储在堆上,并且只保留指向它的指针。

出于效率原因,允许实现使其内部数组大于存储在数组中的元素数。例如:

std::vector<int> v;
assert(v.size() == 0);
assert(v.capacity() >= 0); // at least v.size(), but can be higher

当您向向量添加新元素时(例如,通过v.push_back),将会发生以下两件事:

  • 如果剩余足够的空间(即v.size() < v.capacity()),则可以添加新元素而无需任何额外的内存分配
  • 否则,必须增加底层数组,其中包括以下步骤:

    1. 将分配一个新的(更大的)数组。
    2. 旧数组中的所有元素都必须复制到新数组中。
    3. 新数组替换旧数组(将被解除分配),您可以插入新元素。

重要的是要注意std::vector实例本身将保留在相同的内存地址,只有它的内部指针现在将指向新创建的(更大的)数组。在这方面,数据已被移动到不同的存储位置。 (这也会产生影响,例如,您保留给元素的所有迭代现在都会失效。)

关键操作是重新分配内存。在这里,内存碎片发挥作用。可能会发生因为碎片,即使有足够的空间而没有碎片,也无法分配新的数组。

与其他语言相比,C ++中不可能以压缩垃圾收集器的方式避免碎片(例如,Java中的某些GC实现正在压缩)。同样,操作系统无法避免C ++中的内存碎片。至少在理论上。实际上,在今天的64位系统(带有虚拟内存)中,内存碎片不像以前那么令人担忧。

如果您不需要容器中元素必须是连续的属性,则可以使用std::dequeue而不是std::vector。它对内存碎片更强大,因为它不会保留一个大数组而是几个较小的块。另一方面,std::vector通常效率更高,因此默认情况下我仍然会使用向量,但这里有一篇来自Herb Sutter的旧文章,它涉及到主题:Using Vector and Deque

答案 1 :(得分:2)

当您的std::vector资源耗尽时,它会重新分配空间(通常为2 * required_size,请参阅分摊的复杂程度)并移动已在向量中的元素。它会将数据指针移动到第一个向量中,它不会移动向量本身(你的向量和你的向量数据是不同的位置)。

您的std::vector和“内部”元素通常不在同一位置。出于多种原因,这种不完整的伪实现是错误的,但可能会说明push_back如何在内部扩展:

namespace std {

template<typename T>
class vector<T>
  size_t size_;
  size_t capacity_;
  T* data_;  // Stored elsewhere on the heap.
  void push_back(const T& foo) {
    if (size_ == capacity_) {
      capacity_ *= 2;  // assuming capacity_ > 0, and non-wrapping size
      data_ = realloc(data_, capacity_ * sizeof(T));  // assumes POD types and no realloc failures.
    }
    data_[++size_] = foo;
  }
}
}

realloc这里将移动向量中的数据,因此&vector[0]重新分配向量后,对push_back的任何旧引用都是垃圾。 realloc负责查找一个足够大的连续段来存储N个新元素(可能需要mmap更多内存)。

另一个解释分离的例子:

int main() {
  std::vector<float> numbers;  // the vector is on the stack and never moves.

  numbers.push_back(5.0f);
  // 5.0f is stored inside vector data, which may be on the heap. 
  // Adding more items may allocate heap memory and move all previous items.

  return 0;
}