考虑在x86 CPU上进行单个内存访问(单个读取或单个写入,而不是读取或写入)SSE指令.该指令访问16字节(128位)的存储器,访问的存储器位置对齐为16字节.
文档"英特尔®64架构内存订购白皮书"指出,对于"读取或写入地址在8字节边界上对齐的四字(8字节)的指令",内存操作似乎作为单个内存访问执行,而不管记忆类型.
问题:是否存在Intel/AMD/etc x86 CPU,它们保证读取或写入与16字节边界对齐的16字节(128位)作为单个内存访问执行?是这样,它是哪种特定类型的CPU(Core2/Atom/K8/Phenom/...)?如果您对此问题提供答案(是/否),请同时指定用于确定答案的方法 - PDF文档查找,强力测试,数学证明或您用于确定答案的任何其他方法.
此问题涉及http://research.swtch.com/2010/02/off-to-races.html等问题
更新:
我在C中创建了一个可以在您的计算机上运行的简单测试程序.请在您的Phenom,Athlon,Bobcat,Core2,Atom,Sandy Bridge或您碰巧拥有的任何支持SSE2的CPU上编译并运行它.谢谢.
// Compile with:
// gcc -o a a.c -pthread -msse2 -std=c99 -Wall -O2
//
// Make sure you have at least two physical CPU cores or hyper-threading.
#include <pthread.h>
#include <emmintrin.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
typedef int v4si __attribute__ ((vector_size (16)));
volatile v4si x;
unsigned n1[16] __attribute__((aligned(64)));
unsigned n2[16] __attribute__((aligned(64)));
void* thread1(void *arg) {
for (int i=0; i<100*1000*1000; i++) { …Run Code Online (Sandbox Code Playgroud) 我正在编写一些AVX代码,我需要从可能未对齐的内存中加载.我目前正在加载4个双打,因此我将使用内部指令_mm256_loadu_pd ; 我写的代码是:
__m256d d1 = _mm256_loadu_pd(vInOut + i*4);
Run Code Online (Sandbox Code Playgroud)
然后,我使用选项进行编译,-O3 -mavx -g然后使用objdump获取汇编代码以及带注释的代码和line(objdump -S -M intel -l avx.obj).
当我查看底层汇编程序代码时,我发现以下内容:
vmovupd xmm0,XMMWORD PTR [rsi+rax*1]
vinsertf128 ymm0,ymm0,XMMWORD PTR [rsi+rax*1+0x10],0x1
Run Code Online (Sandbox Code Playgroud)
我期待看到这个:
vmovupd ymm0,XMMWORD PTR [rsi+rax*1]
Run Code Online (Sandbox Code Playgroud)
并充分利用256位寄存器(YMM0),而不是它看起来像海湾合作委员会已决定在128位部分(填写XMM0),然后再次加载另一半vinsertf128.
有人能够解释这个吗?在MSVC VS 2012中
使用单个vmovupd编译等效代码.
我运行gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0在Ubuntu的18.04 X86-64.
正如我们从C11-memory_order所知道的那样:http://en.cppreference.com/w/c/atomic/memory_order
从C++ 11-std :: memory_order开始:http://en.cppreference.com/w/cpp/atomic/memory_order
在强排序系统(x86,SPARC,IBM大型机)上, 发布 - 获取顺序是自动的.没有为此同步模式发出额外的CPU指令,只会影响某些编译器优化(例如,禁止编译器在原子存储释放之前移动非原子存储或在原子载荷获取之前执行非原子加载)
但这对于x86-SSE指令是否正确(除了[NT] - 非时间,我们总是必须使用L/S/MFENCE)?
这里说,"sse指令......不要求向后兼容性,并且内存顺序未定义".据信,当需要时,严格的可订购性与旧版本的处理器x86兼容,但是新的命令,即SSE(除了[NT]) - 被剥夺了自动释放 - 获取订单,是吗?
我正在开发一个多核,多线程软件库,我想在其中提供可能跨越多个缓存行的更新顺序保留无锁共享内存对象.
具体来说,假设我有一些高速缓存行大小的对象的向量X:X [0],... X [K]每个占用恰好一个高速缓存行.我按索引顺序写入它们:首先是X [0],然后是X [1],等等.如果线程2读取X [K],它还会看到X [0]的状态是"至少是当前的"正如它看到的X [K]?
从同一个线程,显然我会看到尊重更新顺序的内存语义.但是现在如果某个第二个线程读取X [K]则会出现问题:是否会观察到对X [0] ...... X [K-1]的相应更新?
通过锁定,我们可以获得此保证.但是由于memcpy用于将某些东西复制到向量中,我们失去了这个属性:memcpy有一个POSIX语义,它不保证索引顺序更新或内存顺序更新或任何其他排序.您可以确保在memcpy完成后,已执行整个更新.
我的问题:是否已经有一个保持订单的memcpy具有相似的速度但具有所需的保证?如果没有,可以在没有锁定的情况下实现这样的原语吗?
假设我的目标平台是x86和ARM.
(编者注:最初称英特尔,所以OP可能不关心AMD.)
我编写了一个简单的“信封”类,以确保我正确理解C ++ 11原子语义。我有一个标头和一个有效负载,编写器清除该标头,填充有效负载,然后用递增的整数填充标头。这样的想法是,读取器然后可以读取标头,将有效负载换出,再次读取标头,如果标头相同,则读取器可以假定他们成功复制了有效负载。读者可能会错过一些更新是可以的,但是让他们获得更新的撕裂(其中来自不同更新的字节混合在一起)也不是可以的。永远只有一个读者和一个作家。
编写者使用释放内存顺序,而读者使用获取内存顺序。
是否存在通过原子存储/加载调用对memcpy重新排序的风险?还是可以将负载彼此重新排序?这永远不会让我流产,但也许我很幸运。
#include <iostream>
#include <atomic>
#include <thread>
#include <cstring>
struct envelope {
alignas(64) uint64_t writer_sequence_number = 1;
std::atomic<uint64_t> sequence_number;
char payload[5000];
void start_writing()
{
sequence_number.store(0, std::memory_order::memory_order_release);
}
void publish()
{
sequence_number.store(++writer_sequence_number, std::memory_order::memory_order_release);
}
bool try_copy(char* copy)
{
auto before = sequence_number.load(std::memory_order::memory_order_acquire);
if(!before) {
return false;
}
::memcpy(copy, payload, 5000);
auto after = sequence_number.load(std::memory_order::memory_order_acquire);
return before == after;
}
};
envelope g_envelope;
void reader_thread()
{
char local_copy[5000];
unsigned messages_received = 0;
while(true) {
if(g_envelope.try_copy(local_copy)) {
for(int i …Run Code Online (Sandbox Code Playgroud) https://www.gnu.org/software/libc/manual/html_node/Atomic-Types.html#Atomic-Types说 -在实践中,你可以假设 int 是原子的。你也可以假设指针类型是原子的;那很方便。这两个假设在 GNU C 库支持的所有机器上和我们知道的所有 POSIX 系统上都是正确的。
我的问题是,对于使用 gcc m64 标志编译的 C 程序,是否可以将指针分配视为 x86_64 架构上的原子分配。操作系统为 64 位 Linux,CPU 为 Intel(R) Xeon(R) CPU D-1548。一个线程将设置一个指针,另一个线程将访问该指针。只有一个写入线程和一个读取线程。Reader 应该获取指针的先前值或最新值,并且两者之间没有垃圾值。
如果它不被认为是原子的,请让我知道我如何使用 gcc 原子内置函数或者像 __sync_synchronize 这样的内存屏障来实现相同的效果而不使用锁。只对 C 解决方案感兴趣,对 C++ 不感兴趣。谢谢!
array< atomic_size_t, 10 > A;
Run Code Online (Sandbox Code Playgroud)
无论是atomic_init(A,{0})也A = {ATOMIC_VAR_INIT(0)}似乎工作,返回一个难以理解的错误.你如何将一个原子数组初始化为0?
即使是循环,在每一步更新数组的一个元素也不起作用.如果我们不能初始化原子数组的目的是什么?
我还想补充一点,我的数组的实际大小是巨大的(在示例中不是10),所以我需要直接初始化.
在这里(以及一些SO问题)我看到C++不支持像无锁的东西,std::atomic<double>并且还不能支持像原子AVX/SSE向量这样的东西,因为它依赖于CPU(虽然现在我知道CPU,ARM, AArch64和x86_64有矢量).
但是double在x86_64中对s或向量的原子操作是否有汇编级支持?如果是这样,支持哪些操作(如加载,存储,添加,减去,可能相乘)?MSVC++ 2017实现哪些操作无锁atomic<double>?
假设从一个竞争激烈的高速缓存行中需要三段数据,是否有一种方法可以“原子地”加载所有这三件事,从而避免到任何其他内核的多次往返?
实际上,对于所有3个成员的快照,我实际上都不需要原子性的正确性保证,只是在正常情况下,所有3个项目都是在同一时钟周期中读取的。我想避免高速缓存行到达的情况,但是在读取所有3个对象之前会出现无效请求。这将导致第三次访问需要发送另一个请求以共享线路,从而使争用更加严重。
例如,
class alignas(std::hardware_destructive_interference_size) Something {
std::atomic<uint64_t> one;
std::uint64_t two;
std::uint64_t three;
};
void bar(std::uint64_t, std::uint64_t, std::uint64_t);
void f1(Something& something) {
auto one = something.one.load(std::memory_order_relaxed);
auto two = something.two;
if (one == 0) {
bar(one, two, something.three);
} else {
bar(one, two, 0);
}
}
void f2(Something& something) {
while (true) {
baz(something.a.exchange(...));
}
}
Run Code Online (Sandbox Code Playgroud)
我能否以某种方式确保one,two并且three所有组件都可以在没有大量RFO的情况下(f1而f2不是同时运行)一起加载到一起?
用于此问题的目标体系结构/平台是Intel x86 Broadwell,但是如果有某种技术或编译器内在函数可以允许某些可移植的工作尽力而为,那也很好。
struct Data {
double a;
double b;
double c;
};
Run Code Online (Sandbox Code Playgroud)
如果在不同的线程上读取,但只有一个其他线程正在写入 a、b、c 中的每一个,那么读取每个双精度值是否正常?
如果我确保Data对齐,会出现什么情况?
struct Data {double a,b,c; } __attribute__((aligned(64));
Run Code Online (Sandbox Code Playgroud)
这将确保 a,b,c 中的每一个都对齐到 64,64+8, 64+16... 所以总是对齐到 8*8=64 位边界。
这个问题对原子 x86 指令的对齐要求及其答案让我认为Data::a/b/c从另一个线程写入并同时读取它们而不使用std::atomic.
是的,我知道std::atomic会解决这个问题,但这不是问题。