10.6. 局部缓冲区优化的局限性

局部缓冲区优化并非没有缺点。最明显的一点是,所有带有局部缓冲区的对象都比没有缓冲区时更大。如果通常存储在缓冲区中的数据小于所选的缓冲区大小,每个对象都会浪费一些内存,但至少这种优化还是有效的。更糟糕的是,如果选择的缓冲区大小不合适,而实际上大多数数据都大于局部缓冲区,那么数据将存储在外部,但每个对象内部仍然会创建局部缓冲区,导致所有这些内存都被浪费。

我们愿意浪费的内存量与优化有效的数据大小范围之间,存在着明显的平衡。局部缓冲区的大小应根据具体应用仔细选择。

另一个更微妙的复杂性是:原本位于对象外部的数据现在存储在对象内部。这除了我们一直关注的性能优势外,还带来了一些后果。首先,只要数据能放入局部缓冲区,对象的每次复制都会包含数据的一个独立副本。这使得诸如数据引用计数之类的设计无法实现;例如,写时复制(Copy-On-Write, COW)字符串,即在所有字符串副本保持不变时数据不会复制,就无法使用小字符串优化。

其次,如果对象本身移动,其数据也必须移动。这与 std::vector 形成对比,std::vector 的移动或交换本质上就像指针操作一样 —— 移动的是指向数据的指针,而数据本身保持在原地。对于 std::any 内部包含的对象也存在类似的考虑。可能会认为这种担忧微不足道;毕竟,局部缓冲区优化主要用于少量数据,移动成本应该与复制指针的成本相当。然而,这里涉及的不仅是性能问题 —— 移动 std::vector(或 std::any)的实例可以保证不会抛出异常。然而,在移动任意对象时,并没有这样的保证。只有当 std::any 包含的对象满足 std::is_nothrow_move_constructible 时,才能使用局部缓冲区优化来实现 std::any。

即使有这样的保证,对于 std::vector 来说仍不够充分;标准明确指出,移动或交换一个vector不会使指向该vector元素的迭代器失效。显然,这一要求与局部缓冲区优化不兼容,因为移动一个小型vector会将其所有元素重新定位到内存中的不同区域,许多高效能库提供了支持小vector优化的自定义类vector容器,但这以牺牲标准的迭代器失效保证为代价。