跳转到内容

优化 C++/代码优化/分配和释放

来自维基教科书,开放书籍,开放世界

即使使用非常高效的分配器,分配和释放操作也需要相当的时间,而且分配器通常效率不高。

在本节中,将描述一些减少内存分配总数及其相应的释放的技术。这些技术仅应用于瓶颈,即在测量到大量分配对性能有重大影响之后。

移动分配和释放

[编辑 | 编辑源代码]

在瓶颈内存分配之前进行移动,并在瓶颈之后进行匹配的释放。

可变长度动态内存管理比自动内存管理慢得多。

类似的优化也应针对间接导致分配的操作,例如直接或间接拥有动态内存的对象的副本。

reserve 函数

[编辑 | 编辑源代码]

在向向量或字符串对象添加元素之前,调用其成员函数 reserve,并指定足够大的大小以满足大多数情况。

如果反复向向量或字符串对象添加对象,则会执行多次代价高昂的重新分配。为了避免此类重新分配,只需预先分配所需的内存即可。

保留向量的容量

[编辑 | 编辑源代码]

要清空向量对象而不释放其内存,请使用语句 x.resize(0); 要清空它并释放其内存,请使用语句 vector<T>().swap(x);

要清空向量对象,还可以使用 clear() 成员函数,但 C++ 标准没有指定此函数是否保留向量的分配容量。

虽然标准没有指定容量是否会改变,但进一步的 测试 表明容量保持不变。此外,C++11 为此行为提供了 "shrink_to_fit()" 函数。

如果反复填充和清空向量对象,因此要避免频繁的重新分配,请通过调用 resize 成员函数来执行清空操作,根据标准,该函数保留对象的容量。相反,如果你已经完成对大型向量对象的访问,并且可能不再使用它,或者将使用它来存储数量明显更少的元素,则应通过对新的空临时向量对象调用 swap 函数来释放对象的内存。

swap 函数重载

[编辑 | 编辑源代码]

对于每个可复制的具体类 T,它直接或间接地拥有某些动态内存,重新定义相应的 swap 函数。

具体来说,向类添加具有以下签名的公共成员函数

void swap(T&) throw();

并在包含类 T 的相同命名空间中添加以下非成员函数

void swap(T& lhs, T& rhs) { lhs.swap(rhs); }

而且,如果该类不是类模板,请在包含类 T 定义的相同文件中添加以下非成员函数

namespace std { template<> swap(T& lhs, T& rhs) { lhs.swap(rhs); } }

在标准库中,swap 函数被许多算法频繁调用。该函数具有通用实现,以及针对标准库中各种类型的专门实现。

如果在调用 swap 的标准库算法中使用了非标准类的对象,并且没有重载 swap 函数,则将使用通用实现。

swap 函数的通用实现会导致创建和销毁临时对象,以及执行两个对象赋值。如果应用于拥有动态内存的对象,此类操作会花费大量时间,因为此类内存将重新分配三次。

动态内存的拥有甚至可能是间接的。例如,如果成员变量是字符串或向量,或者是一个包含字符串或向量对象的类,那么每次包含这些对象的类被复制时,这些对象拥有的内存都会被重新分配。因此,即使在这些情况下,也应该重载 swap 函数。

如果对象不拥有动态内存,则对象的复制速度快得多,而且不会明显慢于使用其他技术,因此无需重载 swap 函数。

如果该类不可复制或抽象,则绝不应该对该类型的对象调用 swap 函数,因此在这些情况下也不应该重新定义 swap 函数。

要加快 swap 函数的速度,你必须为你的类专门化它。有两种可能的实现方式:在类的相同命名空间(可能是全局命名空间)中作为重载,或者在 std 命名空间中作为标准模板的专门化。建议两种方式都定义它,因为首先,如果它是一个类模板,则只有第一种方式是可行的,然后一些编译器不接受或在只以第一种方式定义时发出警告。

此类函数的实现必须访问对象的全部成员,因此需要调用一个成员函数,该函数按照惯例也被称为 swap,它执行实际的工作。

此项工作包括交换两个对象的全部非静态成员,通常通过对它们调用 swap 函数来完成,无需限定其命名空间。

要将 std::swap 函数放入当前作用域,该函数必须以以下语句开头

using std::swap;
华夏公益教科书