我一直在绞尽脑汁想要完成这项任务一周,我希望有人能带领我走向正确的道路.让我从教师的指示开始:
您的作业与我们的第一个实验作业相反,即优化素数计划.你在这个任务中的目的是使程序失望,即让它运行得更慢.这两个都是CPU密集型程序.他们需要几秒钟才能在我们的实验室电脑上运行.您可能无法更改算法.
要取消优化程序,请使用您对英特尔i7管道如何运行的了解.想象一下重新排序指令路径以引入WAR,RAW和其他危险的方法.想一想最小化缓存有效性的方法.恶魔无能.
该作业选择了Whetstone或Monte-Carlo程序.缓存有效性评论大多只适用于Whetstone,但我选择了Monte-Carlo模拟程序:
// Un-modified baseline for pessimization, as given in the assignment
#include <algorithm> // Needed for the "max" function
#include <cmath>
#include <iostream>
// A simple implementation of the Box-Muller algorithm, used to generate
// gaussian random numbers - necessary for the Monte Carlo method below
// Note that C++11 actually provides std::normal_distribution<> in
// the <random> library, which can be used instead of this function
double gaussian_box_muller() {
double x = 0.0;
double y = 0.0; …Run Code Online (Sandbox Code Playgroud) 我知道关于这个主题的多个问题,但是,我没有看到任何明确的答案或任何基准测量.因此,我创建了一个简单的程序,它使用两个整数数组.第一个数组a非常大(64 MB),第二个数组b很小,适合L1缓存.程序迭代a并将其元素添加到b模块化意义上的相应元素中(当到达结束时b,程序从其开始再次开始).测量的不同大小的L1缓存未命中数b如下:
测量是在具有32 kiB L1数据高速缓存的Xeon E5 2680v3 Haswell型CPU上进行的.因此,在所有情况下,都b适合L1缓存.然而,大约16 kiB的b内存占用量大大增加了未命中数.这可能因为两者的负载预期a并b导致缓存线失效从一开始b在这一点上.
绝对没有理由保留a缓存中的元素,它们只使用一次.因此,我运行一个具有非时间负载a数据的程序变体,但未命中数没有改变.我还运行了一个非暂时预取a数据的变体,但仍然有相同的结果.
我的基准代码如下(没有显示非时间预取的变体):
int main(int argc, char* argv[])
{
uint64_t* a;
const uint64_t a_bytes = 64 * 1024 * 1024;
const uint64_t a_count = a_bytes / sizeof(uint64_t);
posix_memalign((void**)(&a), 64, a_bytes);
uint64_t* b;
const uint64_t b_bytes = atol(argv[1]) * 1024;
const uint64_t b_count = b_bytes …Run Code Online (Sandbox Code Playgroud) 在C++ 11标准,部分1.10/5提到,但不正式定义的术语acquire operation,release operation和consume operation.然后在第29节继续使用这些术语来描述某些内存排序,原子操作和内存栅栏的操作.例如,关于"顺序和一致性"的29.3/1表示:
memory_order_release,memory_order_acq_rel和memory_order_seq_cst:存储操作在受影响的内存位置上执行释放操作 [强调添加].
这种类型的语言在第29节中重复出现,但令我烦恼的是,memory_order枚举的所有含义都基于操作类型,这些类型本身似乎没有被标准正式化,但必须对它们有一些共同的意义.作为定义有效.
换句话说,如果我说"一个酒吧是一个翻转的foo",bar和foo的具体含义是模棱两可的,因为这两个术语都没有正式定义.只定义了它们的相对性质.
请问C++的11个标准,或其他一些C++ 11标准委员会文件正式准确定义的是什么acquire operation,release operation等等是,或者是这些简单的通常理解的术语?如果是后者,是否有一个很好的参考被认为是这些操作含义的行业标准?我特别要求,因为硬件内存一致性模型不是相同的,因此我认为必须有一些共同商定的引用,允许那些实现编译器等的人正确地将这些操作的语义转换为本机程序集命令.
英特尔内存模型保证:
http://bartoszmilewski.com/2008/11/05/who-ordered-memory-fences-on-an-x86/
我已经看到声称由于Intel内存模型,SFENCE在x86-64上是多余的,但从来没有LFENCE.上述内存模型规则是否使指令冗余?
Linux的同步原语(自旋锁,互斥锁,RCU)使用内存屏障指令强制重新排序内存访问指令.这种重新排序可以由CPU本身或编译器完成.
有人可以展示一些GCC生成的代码示例吗?我主要对x86感兴趣.我之所以问这个问题,是为了理解GCC如何决定可以重新排序的指令.不同的x86 mirco架构(例如:沙桥与常春藤桥)使用不同的缓存架构.因此,我想知道GCC如何进行有效的重新排序,无论缓存架构如何,都有助于执行性能.一些示例C代码和重新排序的GCC生成的代码将非常有用.谢谢!
当从连续的内存位置执行一系列_mm_stream_load_si128()调用(MOVNTDQA)时,硬件预取器是否仍会启动,或者我应该使用显式软件预取(使用NTA提示)以获得预取的好处,同时仍然避免缓存污染?
我问这个的原因是因为他们的目标似乎与我相矛盾.流加载将获取绕过缓存的数据,而预取器尝试主动将数据提取到缓存中.
当顺序迭代一个大型数据结构(处理过的数据不会在很长一段时间内被修饰)时,我有必要避免污染chache层次结构,但我不想因频繁出现频繁的~100次循环处罚-fetcher闲置.
目标架构是Intel SandyBridge
我正在生成sse/avx指令,目前我必须使用未对齐的加载和存储.我在浮点/双数组上操作,我永远不知道它是否会对齐.所以在矢量化它之前,我希望有一个pre和可能的post循环,它关注未对齐的部分.然后,主矢量化循环在对齐的部分上操作.
但是我如何确定阵列何时对齐?我可以查看指针值吗?应该何时预循环停止和循环后启动?
这是我的简单代码示例:
void func(double * in, double * out, unsigned int size){
for( as long as in unaligned part ){
out[i] = do_something_with_array(in[i])
}
for( as long as aligned ){
awesome avx code that loads operates and stores 4 doubles
}
for( remaining part of array ){
out[i] = do_something_with_array(in[i])
}
}
Run Code Online (Sandbox Code Playgroud)
编辑:我一直在考虑它.从理论上讲,指向第i个元素的指针应该是可分的(类似于&a [i]%16 == 0)2,4,16,32(取决于它是否是双倍以及它是sse还是avx).所以第一个循环应该掩盖不可分割的元素.
实际上我将尝试编译器编译指示和标志输出,以查看编译器产生了什么.如果没有人给出一个好的答案,我会在周末发布我的解决方案(如果有的话).
我正在阅读Ulrich Drepper的"每个程序员应该了解的关于记忆的内容".在第6部分的开头,theres是一个代码片段:
#include <emmintrin.h>
void setbytes(char *p, int c)
{
__m128i i = _mm_set_epi8(c, c, c, c,
c, c, c, c,
c, c, c, c,
c, c, c, c);
_mm_stream_si128((__m128i *)&p[0], i);
_mm_stream_si128((__m128i *)&p[16], i);
_mm_stream_si128((__m128i *)&p[32], i);
_mm_stream_si128((__m128i *)&p[48], i);
}
Run Code Online (Sandbox Code Playgroud)
在它下面有这样的评论:
假设指针
p已正确对齐,则对此函数的调用将设置所寻址的高速缓存行的所有字节c.写组合逻辑将看到四个生成的movntdq指令,并且只有在执行完最后一条指令后才发出内存的写命令.总而言之,这个代码序列不仅避免了在写入之前读取高速缓存行,还避免了使用可能不需要的数据来缓存高速缓存.
什么错误我是在它被写,它"将设置针对高速缓存行的所有字节到C",但是从我的理解流的intrisics他们绕过缓存功能评论 - 既没有高速缓存的读,也不缓存写入.这段代码如何访问任何缓存行?第二个粗体片段表示相似,即函数"避免在写入之前读取缓存行".如上所述,我没有看到如何以及何时写入缓存.此外,是否需要在缓存写入之前写入缓存?有人可以向我澄清这个问题吗?
当你使用非临时存储,例如movntq,并且数据已经在缓存中时,存储会更新缓存而不是写入内存吗?或者它会更新缓存行并写出来,驱逐它吗?或者是什么?
这是一个有趣的困境.假设线程A正在加载包含x和y的缓存行.线程B使用NT存储写入x.线程A写入y.这里有数据竞争,如果B的存储到x可以在A的负载发生时传输到内存.如果A看到x的旧值,但是X的写入已经发生,那么稍后写入y并最终写回高速缓存行将破坏不相关的值x.我假设处理器以某种方式防止这种情况发生?我不知道如果有可能的行为,任何人都可以使用NT商店构建一个可靠的系统.
我的测试代码如下,我发现只有memory_order_seq_cstforbade编译器重新排序.
#include <atomic>
using namespace std;
int A, B = 1;
void func(void) {
A = B + 1;
atomic_thread_fence(memory_order_seq_cst);
B = 0;
}
Run Code Online (Sandbox Code Playgroud)
而其他选择memory_order_release,memory_order_acq_rel根本没有产生任何编译屏障.
我认为他们必须使用原子变量,如下所示.
#include <atomic>
using namespace std;
atomic<int> A(0);
int B = 1;
void func(void) {
A.store(B+1, memory_order_release);
B = 0;
}
Run Code Online (Sandbox Code Playgroud)
但我不想使用原子变量.与此同时,我认为"asm("":::"记忆")"太低了.
还有更好的选择吗?
你能描述一下x86_64上WC和WB内存的含义和区别吗?为了完整起见,请在x86_64上描述其他类型的内存,如果有的话.