当操作正在写入单个常量值时,代码中存在竞争条件是否存在问题?例如,如果有一个并行循环为另一个数组seen
中的每个值填充一个arr
数组(假定越界索引没有问题)。关键部分可能是以下代码:
//parallel body with index i
int val = arr[i];
seen[val] = true;
由于唯一要写入的值是true
,这是否不需要互斥量,并且可能对性能有害?即使线程彼此脚,它们也只是用相同的值填充地址,对吗?
答案 0 :(得分:3)
C ++内存模型不允许您免费写入相同的值。
如果两个线程在不同步的情况下写入一个非原子对象,那只是竞争条件。竞争条件意味着您的程序执行未定义的行为。而且,在程序执行的任何地方都会发生未定义的行为,这意味着在未定义行为的前后,程序的行为都不受C ++标准的限制。
给定的编译器可以自由提供更多自由的内存模型。我不知道有什么事。
您必须了解的一件事是C ++不是汇编程序宏语言。它不必产生您想象中的幼稚汇编程序。相反,C ++试图使编译器更容易生成汇编器,这是完全不同的事情。
编译器可以并且确实确定“如果发生X,我们将获得未定义的行为;因此,我将围绕X不会发生的事实进行优化”。在这种情况下,编译器可以证明,具有定义行为的程序在两个不同的未同步线程中可能具有相同的val
。
所有这些都可能在生成任何程序集之前发生。
在组装级别上,某些硬件可能会对未对齐的多字节值分配进行有趣的操作。当声称是单线程写的指令出现在相同字节上的两个不同内核中时,某些硬件可能会(理论上;我实际上没有意识到)会引发陷阱。
所以这是C ++中的UB。一旦有了UB,就必须在接触该编译器的任何地方都可以审计程序产生的汇编代码。如果执行LTO,则意味着在整个程序中,至少在调用或与执行UB的代码进行交互的任何地方,距离都不明确。
只需编写定义的行为。而且只有当这成为关键任务性能瓶颈时,您才应该花更多的精力来对其进行优化(首先定义更快的行为,并且只有在失败时才考虑使用UB)。
答案 1 :(得分:-1)
可能存在与体系结构有关的约束,要求您将可见的数组元素分开一定数量,以防止竞争线程破坏在同一机器字(或高速缓存行,甚至是缓存行)中冲突的值。
也就是说,如果将seen
定义为bool seen[N];
,则seen
的长度为N个字节,并且每个元素都直接与其相邻元素相邻。如果一个线程更改了元素0,而另一个线程更改了元素2,则这两个更改都在同一64位机器字中发生。如果这两个更改是由不同的内核(甚至在多CPU系统的不同CPU上)同时进行的,则它们将尝试将冲突解决为整个64位机器字(在某些情况下甚至更大)。这样的结果将是,由于获胜线程对相邻元素的更新,已写入的true
之一将返回到其先前的状态(可能为false
)。
如果相反,您将看到的结构定义为一个数组,每个结构都与一个高速缓存行一样大,那么您可能让竞争线程将该结构中的bool值混为一谈……但这是有风险的,因为并非所有CPU将共享相同的缓存冲突验证策略,行大小等,并且不可避免地,将有一个CPU发生故障。