x86 放松排序性能?
x86 relaxed ordering performance?
由于英特尔提供了强大的硬件内存模型,在 C++11 程序中使用 "memory_order_relaxed" 是否有任何优势?或者只是将其保留为默认值 "sequential consistent" 因为它没有任何区别?
像计算机科学中的大多数答案一样,答案是 "that depends."
首先,顺序一致的排序永远不会带来任何惩罚的想法是不正确的。根据您的代码(可能还有编译器),它可能并且将会受到惩罚。
其次,要对内存排序约束做出明智的决策,您需要考虑(并理解)您如何使用所涉及的数据。
memory_order_relaxed 对于需要原子性的独立计数器之类的东西很有用,但与其他东西没有直接关系,因此它不需要与任何 "something else" 保持一致。典型的例子是引用计数,例如 shared_ptr
或 std::string
的一些旧实现。在这种情况下,我们只需要确保计数器以原子方式递增和递减,并且对它的修改对所有线程都是可见的。但是,特别是,没有任何相关数据需要保持一致,所以我们不太关心它相对于其他任何东西的排序。
顺序一致排序几乎处于相反的极端。它可能是最容易应用的——你编写代码就像它是单线程的一样,并且实现确保它正常工作(这并不是说你根本不必考虑线程,而是顺序一致的顺序通常需要最少的思考,但通常也是最慢的模型)。
Acquire/release 一致性通常用于当您有两个或多个相关信息时,您需要确保一个只对另一个可见 before/after。对于我最近处理的一个示例,假设您正在构建大致类似于内存数据库的东西。你有一些数据,你有一些元数据(你或多或少地分开存储)。
元数据(除其他外)用于搜索数据库。我们想确保如果有人找到一些特定数据,他们发现的数据将实际存在于数据库中。
为确保这一点,我们要确保数据始终先于元数据存在,并且至少与元数据存在的时间一样长。如果有人可以使用元数据搜索数据库并找到它想要使用的一些数据,但实际上并不存在该数据,那么数据库将不一致。
所以在这种情况下,当我们添加记录时,我们需要确保先添加数据,然后添加元数据——编译器不得重新排列两者。同样,当我们删除记录时,我们需要删除元数据(因此没有人会 找到 数据),然后删除数据本身。对于数据本身,我们很可能有一个引用计数来跟踪当前有多少客户端正在访问该数据,以确保我们不会在有人尝试使用它时删除它。
所以在这种情况下,我们可以对元数据和数据使用 acquire/release 语义,并对引用计数放宽排序。或者,如果我们想让我们的代码尽可能简单,我们可以在整个过程中使用顺序一致性——即使它可能(并且可能会)至少带来一些损失。
始终使用使代码正确所需的最低保证。
不多也不少。
这样一来,您就可以避免对实现的任何不必要的依赖,从而降低任何移植成本,并且仍然可以获得最快的程序。
当然,如果您确定自己永远不会关心移植任何代码,那么在您知道它在您的平台上无关紧要的地方采取更强有力的保证可能会更容易证明它的正确性。
更难被滥用、更容易推理或更短也是使用性能较低的结构的完全可以接受的原因。
由于英特尔提供了强大的硬件内存模型,在 C++11 程序中使用 "memory_order_relaxed" 是否有任何优势?或者只是将其保留为默认值 "sequential consistent" 因为它没有任何区别?
像计算机科学中的大多数答案一样,答案是 "that depends."
首先,顺序一致的排序永远不会带来任何惩罚的想法是不正确的。根据您的代码(可能还有编译器),它可能并且将会受到惩罚。
其次,要对内存排序约束做出明智的决策,您需要考虑(并理解)您如何使用所涉及的数据。
memory_order_relaxed 对于需要原子性的独立计数器之类的东西很有用,但与其他东西没有直接关系,因此它不需要与任何 "something else" 保持一致。典型的例子是引用计数,例如 shared_ptr
或 std::string
的一些旧实现。在这种情况下,我们只需要确保计数器以原子方式递增和递减,并且对它的修改对所有线程都是可见的。但是,特别是,没有任何相关数据需要保持一致,所以我们不太关心它相对于其他任何东西的排序。
顺序一致排序几乎处于相反的极端。它可能是最容易应用的——你编写代码就像它是单线程的一样,并且实现确保它正常工作(这并不是说你根本不必考虑线程,而是顺序一致的顺序通常需要最少的思考,但通常也是最慢的模型)。
Acquire/release 一致性通常用于当您有两个或多个相关信息时,您需要确保一个只对另一个可见 before/after。对于我最近处理的一个示例,假设您正在构建大致类似于内存数据库的东西。你有一些数据,你有一些元数据(你或多或少地分开存储)。
元数据(除其他外)用于搜索数据库。我们想确保如果有人找到一些特定数据,他们发现的数据将实际存在于数据库中。
为确保这一点,我们要确保数据始终先于元数据存在,并且至少与元数据存在的时间一样长。如果有人可以使用元数据搜索数据库并找到它想要使用的一些数据,但实际上并不存在该数据,那么数据库将不一致。
所以在这种情况下,当我们添加记录时,我们需要确保先添加数据,然后添加元数据——编译器不得重新排列两者。同样,当我们删除记录时,我们需要删除元数据(因此没有人会 找到 数据),然后删除数据本身。对于数据本身,我们很可能有一个引用计数来跟踪当前有多少客户端正在访问该数据,以确保我们不会在有人尝试使用它时删除它。
所以在这种情况下,我们可以对元数据和数据使用 acquire/release 语义,并对引用计数放宽排序。或者,如果我们想让我们的代码尽可能简单,我们可以在整个过程中使用顺序一致性——即使它可能(并且可能会)至少带来一些损失。
始终使用使代码正确所需的最低保证。
不多也不少。
这样一来,您就可以避免对实现的任何不必要的依赖,从而降低任何移植成本,并且仍然可以获得最快的程序。
当然,如果您确定自己永远不会关心移植任何代码,那么在您知道它在您的平台上无关紧要的地方采取更强有力的保证可能会更容易证明它的正确性。
更难被滥用、更容易推理或更短也是使用性能较低的结构的完全可以接受的原因。