与C++ 11相比,OpenMP从内存操作的角度来看是原子性的,而不是变量.这允许例如对在编译时存储在具有未知大小的向量中的整数使用原子读/写:
std::vector<int> v;
// non-atomic access (e.g., in a sequential region):
v.resize(n);
...
v.push_back(i);
...
// atomic access in a multi-threaded region:
#pragma omp atomic write // seq_cst
v[k] = ...;
#pragma omp atomic read // seq_cst
... = v[k];
Run Code Online (Sandbox Code Playgroud)
在C++ 11中,这是不可能实现的.我们可以通过放松内存模型来将原子变量作为非原子变量进行访问,但是我们无法调整原子元素的向量.
我理解为什么C++不允许通过原子内存操作访问非原子变量.但我想知道,为什么这些原因也不适用于OpenMP.
例如,在N4013中,据说"没有合理的方法将原子操作完全可移植地应用于未声明为原子的数据." OpenMP如何能够保证这种可移植性和C++不是这样?
假设我想创建一个函数,通过引用获取左值和右值字符串参数,将它们转换为大写,并将它们打印到标准输出:
void upper_print(std::string& s);
void upper_print(std::string&& s);
Run Code Online (Sandbox Code Playgroud)
这工作正常如下:
std::string s("Hello world");
upper_print(s);
upper_print(std::string("Hello world"));
upper_print("Hello world"); // converting ctor used
Run Code Online (Sandbox Code Playgroud)
但是,为避免冗余,我想使用转发引用:
template <typename T> upper_print(T&& s);
Run Code Online (Sandbox Code Playgroud)
不幸的是,我无法upper_print
使用字符串文字参数调用:
std::string s("Hello world"); // OK
upper_print(s); // OK
upper_print(std::string("Hello world")); // OK
upper_print("Hello world"); // ERROR
Run Code Online (Sandbox Code Playgroud)
我知道可以限制std::string
对象的参数,例如,使用std::enable_if
或static_assert
.但这并没有帮助.
在这个意义上,是否有任何选项可以结合转发引用和转换构造函数的功能?
我创建了以下类来理解以下行为std::sort
:
class X {
public:
X(int i) : i_(i) { }
X(X&& rhs) noexcept : i_(std::move(rhs.i_)) { mc_++; }
X& operator=(X&& rhs) noexcept {
i_ = std::move(rhs.i_); ao_++; return *this;
}
void swap(X& rhs) noexcept { std::swap(i_, rhs.i_); sw_++; }
friend bool operator<(const X& lhs, const X& rhs) {
return lhs.i_ < rhs.i_;
}
static void reset() { mc_ = ao_ = sw_ = 0; }
private:
int i_;
static size_t mc_, ao_, sw_; // function-call counters …
Run Code Online (Sandbox Code Playgroud) 简单的MWE:
int* ptr = (int*)malloc(64 * sizeof(int));
_mm_prefetch((const char*)(ptr + 64), _MM_HINT_0);
Run Code Online (Sandbox Code Playgroud)
我问,因为我可以在编译器生成的代码中看到这样的预取,其中在循环预取内部完成而不检查地址(存储在rbx
)中:
400e73: 49 83 c5 40 add r13,0x40
400e77: 62 f1 f9 08 28 03 vmovapd zmm0,ZMMWORD PTR [rbx]
400e7d: 4d 3b ec cmp r13,r12
400e80: 62 d1 f9 08 eb 4d ff vporq zmm1,zmm0,ZMMWORD PTR [r13-0x40]
400e87: 90 nop
400e88: 62 d1 78 08 29 4d ff vmovaps ZMMWORD PTR [r13-0x40],zmm1
400e8f: 72 03 jb 400e94 <main+0x244>
400e91: 49 89 c5 mov r13,rax …
Run Code Online (Sandbox Code Playgroud) 假设我们想要设计一个C
类似的容器std::vector
.push_back
通过调用实现是一个好主意emplace_back
,例如:
template <typename T>
class C {
public:
...
template <typename Args...>
void emplace_back(Args&&... args) {
... // uses T(std::forward<Args>(args)...) internally
}
void push_back(T value) {
emplace_back(std::move(value));
}
...
};
Run Code Online (Sandbox Code Playgroud)
或者,或者:
template <typename U>
void push_back(U&& value) {
emplace_back(std::forward(value));
}
Run Code Online (Sandbox Code Playgroud)
?
我找不到标准中指针/引用失效的任何定义.我问,因为我发现C++ 11禁止字符串的写时复制(COW).据我所知,如果应用了COW,那么在以下命令之后p
仍然是一个有效的指针和r
一个有效的引用:
std::string s("abc");
std::string s2(s);
char * p = &(s2[0]);
char & r = s2[0];
s2[1] = "B";
Run Code Online (Sandbox Code Playgroud)
只是他们不再指向/引用第一个字符s2
,而只是指向第一个字符s
.
在C++ 11标准中,据说非常量std::basic_string::operator[]
可能不会使字符串元素的指针/引用(以及迭代器)无效.
哪些规则说上面显示的例子实际上会失效p
,r
如果实施了COW?
我想知道为什么std::launder
是一个constexpr
功能.是否有任何用例可以在编译时使用?
对于以下函数,带有优化的代码被向量化,并且计算在寄存器中执行(返回值返回eax
).生成的机器代码例如是:https://godbolt.org/z/VQEBV4.
int sum(int *arr, int n) {
int ret = 0;
for (int i = 0; i < n; i++)
ret += arr[i];
return ret;
}
Run Code Online (Sandbox Code Playgroud)
但是,如果我创建ret
变量global(或类型的参数int&
),则不使用向量化,并且编译器将ret
每次迭代中更新的内容存储到内存中.机器代码:https://godbolt.org/z/NAmX4t.
int ret = 0;
int sum(int *arr, int n) {
for (int i = 0; i < n; i++)
ret += arr[i];
return ret;
}
Run Code Online (Sandbox Code Playgroud)
我不明白为什么在后一种情况下会阻止优化(寄存器中的矢量化/计算).没有线程,即使增量不是原子地执行的.此外,这种行为似乎在编译器(GCC,Clang,Intel)之间是一致的,所以我认为必须有一些原因.
我创建了一个简单的演示来展示未对齐的内存存储/加载在 x86_64 和 ARM64 架构上通常不是原子的。该演示由一个 C++ 程序组成,该程序创建两个线程 \xe2\x80\x94,第一个线程 10 亿次调用名为 的函数store
,第二个线程对名为 的函数执行相同的操作load
。该程序的源代码在这里:
#include <cstdint>\n#include <cstdlib>\n#include <iostream>\n#include <thread>\n\nextern "C" void store(void*);\nextern "C" uint16_t load(void*);\n\nalignas(64) char buf[65];\nchar* ptr;\n\nstatic long n = 1\'000\'000\'000L;\n\nvoid f1()\n{\n for (long i = 0; i < n; i++)\n store(ptr);\n}\n\nvoid f2()\n{\n long v0x0000 = 0;\n long v0x0101 = 0;\n long v0x0100 = 0;\n long v0x0001 = 0;\n long other = 0;\n\n for (long i = 0; i < n; i++)\n {\n uint16_t …
Run Code Online (Sandbox Code Playgroud) 我注意到内部实现的 std::find 中的一些东西让我感到困惑;他们为什么这么做?
\nstd::find(begin,end,...)
假设这一行,那么在文件的内部实现中stl_algobase.h
,Line:2064是:
\xc2\xa0 \xc2\xa0 \xc2\xa0 typename iterator_traits<_RandomAccessIterator>::difference_type\n\xc2\xa0 \xc2\xa0 __trip_count = (__last - __first) >> 2;\n
Run Code Online (Sandbox Code Playgroud)\n这里发生了什么?为什么他们一起做减法然后使用移位运算符?我不明白他们为什么这样做?(抱歉,如果这是初学者的问题。)
\n