如果通过限制指针发生,实现是否可以优化对非原子访问的原子访问?
May an implementation optimize an atomic access to a non-atomic access, if it happens through a restrict pointer?
考虑以下函数。
void incr(_Atomic int *restrict ptr) {
*ptr += 1;
}
我会考虑 x86,但我的问题是关于语言,而不是任何特定原子实现的语义。 GCC 和 Clang 都发出以下内容:
incr:
lock add DWORD PTR [rdi], 1
ret
符合标准的实现是否可以简单地发出
incr:
add DWORD PTR [rdi], 1
ret
(如果删除 _Atomic
,您会得到同样的结果。)
如果没有 restrict
,这将是一个错误编译,因为 add
不是原子的,所以(例如)在两个线程中同时调用 incr
会竞争。但是,由于指针是 restrict
限定的,我认为在 incr
和任何其他对 *ptr
的访问之间不可能发生竞争(无论如何都不会导致未定义的行为)。
我有信心手动进行此优化,但据我所知,没有编译器会自动进行此操作。这是正确的吗?还是我理解错了restrict
?
编译器无法进行优化,因为 restrict
的行为仅针对通过 incr
.
中的其他指针访问同一对象进行定义
来自this reference,我自己强调:
During each execution of a block in which a restricted pointer P is declared (typically each execution of a function body in which P is a function parameter), if some object that is accessible through P (directly or indirectly) is modified, by any means, then all accesses to that object (both reads and writes) in that block must occur through P (directly or indirectly), otherwise the behavior is undefined...
对指针的限制仅适用于从 incr
进行的访问,这意味着符合标准的编译器不应该对其他线程或其他具有自己的指针指向的函数进行的访问做出任何结论到相同的整数(这很好,incr
的调用者可能只有指向它的非限制指针)。
开放标准第 6.7.3.1 节还定义了 restrict
关于正在执行的块的正式行为,不声明任何指针的生命周期。但是,我认为该标准的定义在数学上过于笨拙,不值得在这里完整介绍。
Or have I misunderstood restrict?
是的 - 它不能保证在您使用它的函数之外发生的事情。所以它不能用于同步或线程安全目的。
关于 restrict
的 TL;DR 是 *restrict ptr
保证没有其他指针访问 ptr
指向的对象将从 [=13= 所在的块完成] 被宣布。如果存在其他指针或对象引用(“左值访问”),编译器可能会假定它们没有修改指向的受限对象。
这几乎只是程序员和编译器之间的契约:“亲爱的编译器,我保证我不会在你背后用这个指针指向的对象做傻事”。但是编译器无法真正检查程序员是否违反了这个契约,因为为了这样做,它可能必须同时检查几个不同的翻译单元。
示例代码:
#include <stdio.h>
int a=0;
int b=1;
void copy_stuff (int* restrict pa, int* restrict pb)
{
*pa = *pb; // assign the value 1 to a
a=2; // undefined behavior, the programmer broke the restrict contract
printf("%d\n", *pa);
}
int main()
{
copy_stuff(&a, &b);
printf("%d\n", a);
}
这会在所有主流 x86 编译器上打印 1
然后 2
并优化。因为在函数内部的 printf 中,他们可以自由假设 *pa
自 *pa = *pb;
以来未被修改。删除 restrict
,他们将打印 2
然后 2
,因为编译器必须在机器代码中添加额外的读取指令。
因此,要使 restrict
有意义,需要将其与对对象的另一个引用进行比较。在您的示例中情况并非如此,因此 restrict
没有明显的目的。
至于_Atomic
,它总是必须保证机器指令本身是原子的,无论程序中发生了什么。这不是一些半高级的“无论如何似乎没有其他人在使用这个变量”。但是要使关键字对多线程和多核都有意义,它还必须充当低级内存屏障以防止流水线、指令重新排序和数据缓存——如果我没记错的话这正是 x86 lock
确实如此。
考虑以下函数。
void incr(_Atomic int *restrict ptr) {
*ptr += 1;
}
我会考虑 x86,但我的问题是关于语言,而不是任何特定原子实现的语义。 GCC 和 Clang 都发出以下内容:
incr:
lock add DWORD PTR [rdi], 1
ret
符合标准的实现是否可以简单地发出
incr:
add DWORD PTR [rdi], 1
ret
(如果删除 _Atomic
,您会得到同样的结果。)
如果没有 restrict
,这将是一个错误编译,因为 add
不是原子的,所以(例如)在两个线程中同时调用 incr
会竞争。但是,由于指针是 restrict
限定的,我认为在 incr
和任何其他对 *ptr
的访问之间不可能发生竞争(无论如何都不会导致未定义的行为)。
我有信心手动进行此优化,但据我所知,没有编译器会自动进行此操作。这是正确的吗?还是我理解错了restrict
?
编译器无法进行优化,因为 restrict
的行为仅针对通过 incr
.
来自this reference,我自己强调:
During each execution of a block in which a restricted pointer P is declared (typically each execution of a function body in which P is a function parameter), if some object that is accessible through P (directly or indirectly) is modified, by any means, then all accesses to that object (both reads and writes) in that block must occur through P (directly or indirectly), otherwise the behavior is undefined...
对指针的限制仅适用于从 incr
进行的访问,这意味着符合标准的编译器不应该对其他线程或其他具有自己的指针指向的函数进行的访问做出任何结论到相同的整数(这很好,incr
的调用者可能只有指向它的非限制指针)。
开放标准第 6.7.3.1 节还定义了 restrict
关于正在执行的块的正式行为,不声明任何指针的生命周期。但是,我认为该标准的定义在数学上过于笨拙,不值得在这里完整介绍。
Or have I misunderstood restrict?
是的 - 它不能保证在您使用它的函数之外发生的事情。所以它不能用于同步或线程安全目的。
关于 restrict
的 TL;DR 是 *restrict ptr
保证没有其他指针访问 ptr
指向的对象将从 [=13= 所在的块完成] 被宣布。如果存在其他指针或对象引用(“左值访问”),编译器可能会假定它们没有修改指向的受限对象。
这几乎只是程序员和编译器之间的契约:“亲爱的编译器,我保证我不会在你背后用这个指针指向的对象做傻事”。但是编译器无法真正检查程序员是否违反了这个契约,因为为了这样做,它可能必须同时检查几个不同的翻译单元。
示例代码:
#include <stdio.h>
int a=0;
int b=1;
void copy_stuff (int* restrict pa, int* restrict pb)
{
*pa = *pb; // assign the value 1 to a
a=2; // undefined behavior, the programmer broke the restrict contract
printf("%d\n", *pa);
}
int main()
{
copy_stuff(&a, &b);
printf("%d\n", a);
}
这会在所有主流 x86 编译器上打印 1
然后 2
并优化。因为在函数内部的 printf 中,他们可以自由假设 *pa
自 *pa = *pb;
以来未被修改。删除 restrict
,他们将打印 2
然后 2
,因为编译器必须在机器代码中添加额外的读取指令。
因此,要使 restrict
有意义,需要将其与对对象的另一个引用进行比较。在您的示例中情况并非如此,因此 restrict
没有明显的目的。
至于_Atomic
,它总是必须保证机器指令本身是原子的,无论程序中发生了什么。这不是一些半高级的“无论如何似乎没有其他人在使用这个变量”。但是要使关键字对多线程和多核都有意义,它还必须充当低级内存屏障以防止流水线、指令重新排序和数据缓存——如果我没记错的话这正是 x86 lock
确实如此。