相关疑难解决方法(0)

取消优化英特尔Sandybridge系列CPU中管道的程序

我一直在绞尽脑汁想要完成这项任务一周,我希望有人能带领我走向正确的道路.让我从教师的指示开始:

您的作业与我们的第一个实验作业相反,即优化素数计划.你在这个任务中的目的是使程序失望,即让它运行得更慢.这两个都是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)

c++ optimization x86 intel cpu-architecture

313
推荐指数
4
解决办法
4万
查看次数

为什么编译器没有合并多余的std :: atomic写入?

我想知道为什么没有编译器准备将相同值的连续写入合并到单个原子变量,例如:

#include <atomic>
std::atomic<int> y(0);
void f() {
  auto order = std::memory_order_relaxed;
  y.store(1, order);
  y.store(1, order);
  y.store(1, order);
}
Run Code Online (Sandbox Code Playgroud)

我尝试过的每个编译器都会发出三次上面的编写.什么合法的,无种族的观察者可以看到上述代码与具有单次写入的优化版本之间的差异(即,不是"假设"规则适用)?

如果变量是易变的,那么显然不适用优化.在我的情况下有什么阻止它?

这是编译器资源管理器中的代码.

c++ multithreading compiler-optimization c++11 stdatomic

47
推荐指数
5
解决办法
4494
查看次数

函数指针强制指令管道清除吗?

现代CPU具有广泛的流水线操作,也就是说,它们在实际执行指令之前很久就会加载必要的指令和数据.

有时,加载到管道中的数据会失效,必须清除管道并重新加载新数据.重新填充管道所需的时间可能相当长,并导致性能下降.

如果我在C中调用一个函数指针,那么管道是否足够智能以实现管道中的指针是一个函数指针,并且它应该跟随该指针用于下一个指令?或者是否有一个函数指针导致管道清除并降低性能?

我在C中工作,但我想这在C++中更为重要,因为许多函数调用都是通过v-tables进行的.


编辑

@JensGustedt写道:

要成为函数调用的真正性能,您调用的函数必须非常简短.如果您通过测量代码来观察这一点,那么您最终应该重新审视您的设计以允许内联调用

不幸的是,这可能是我陷入的陷阱.

出于性能原因,我编写了小而快的目标函数.

但是它被函数指针引用,因此它可以很容易地被其他函数替换(只需使指针引用一个不同的函数!).因为我通过函数指针引用它,所以我认为它不能内联.

所以,我有一个非常简短,没有内联的功能.

c performance x86 pipeline function-pointers

23
推荐指数
2
解决办法
3249
查看次数

了解lfence对具有两个长依赖链的循环的影响,以增加长度

我正在玩这个答案的代码,稍微修改一下:

BITS 64

GLOBAL _start

SECTION .text

_start:
 mov ecx, 1000000

.loop:

 ;T is a symbol defined with the CLI (-DT=...)

 TIMES T imul eax, eax
 lfence
 TIMES T imul edx, edx


 dec ecx
jnz .loop

 mov eax, 60           ;sys_exit
 xor edi, edi
 syscall
Run Code Online (Sandbox Code Playgroud)

没有lfence我,我得到的结果与答案中的静态分析一致.

当我介绍一个单一 lfence我期望的CPU执行imul edx, edx的序列的第k个平行于迭代imul eax, eax的下一个(的序列K + 1个)迭代.
像这样的东西(调用一个imul eax, eax序列和dimul edx, edx一个): …

performance x86 assembly cpu-architecture perf

13
推荐指数
2
解决办法
472
查看次数

为什么商店装载障碍被认为是昂贵的?

大多数CPU架构都会重新订购存储加载操作,但我的问题是为什么?我对商店装载障碍的解释如下:

x = 50;
store_load_barrier;
y = z;
Run Code Online (Sandbox Code Playgroud)

此外,与发布和获取语义相比,我没有看到这种障碍如何在无锁编程中有多大用处.

concurrency multithreading cpu-architecture lock-free

12
推荐指数
1
解决办法
1732
查看次数

乱序执行和记忆围栏

我知道现代CPU可以无序执行,但是他们总是按顺序退出结果,如维基百科所述.

"Out of Oder处理器及时填写这些"插槽"并准备好其他指令,然后在结尾处重新排序结果,使其看起来正常处理指令. "

现在,当使用多核平台时,据说需要内存防护,因为由于乱序执行,可以在此处打印错误的x值.

Processor #1:
 while f == 0
  ;
 print x; // x might not be 42 here

Processor #2:
 x = 42;
 // Memory fence required here
 f = 1
Run Code Online (Sandbox Code Playgroud)

现在我的问题是,由于乱序处理器(我假设MultiCore处理器的情况下的核心)总是按顺序退出结果,那么内存栅栏的必要性是什么.难道多核处理器的核心不会看到仅从其他核心退役的结果,或者它们是否也会看到正在进行中的结果?

我的意思是在我上面给出的例子中,当处理器2最终退出结果时,x的结果应该在f之前,对吗?我知道在乱序执行期间它可能在x之前修改了f,但它必须在x之前没有退役,对吗?

现在有了按顺序退出结果和缓存一致性机制,为什么你需要在x86中使用内存栅栏?

c cpu x86 memory-fences memory-barriers

10
推荐指数
2
解决办法
3033
查看次数

指令顺序可以发生跨函数调用吗?

假设我有如下伪 C 代码:

int x = 0;
int y = 0;

int __attribute__ ((noinline)) func1(void)
{ 
  int prev = x;  (1)

   x |= FLAG;    (2)

   return prev;  (3)
}

int main(void)
{  
  int tmp;

   ...
   y = 5;   (4)
   compiler_mem_barrier();
   func1();
   compiler_mem_barrier();
   tmp = y;  (5)
   ...
}
Run Code Online (Sandbox Code Playgroud)

假设这是一个单线程进程,所以我们不需要担心锁。假设代码在 x86 系统上运行。我们还假设编译器不进行任何重新排序。

据我了解,x86 系统只能重新排序写入/读取指令(读取可能会与较旧的写入重新排序到不同的位置,但不能与较旧的写入重新排序到同一位置)。但我不清楚 call/ret 指令是否被视为 WRITE/READ 指令。这是我的问题:

  1. 在 x86 系统上,“call”是否被视为 WRITE 指令?我认为是这样,因为调用会将地址推入堆栈。但我没有找到官方文件正式这么说。所以请大家帮忙确认一下。

  2. 出于同样的原因,“ret”是否被视为 READ 指令(因为它从堆栈中弹出地址)?

  3. 实际上,“ret”指令可以在函数内重新排序吗?例如,下面的ASM代码中(3)可以在(2)之前执行吗?这对我来说没有意义,但“ret”不是序列化指令。我在英特尔手册中没有找到任何地方说“ret”不能重新排序。

  4. 上面的代码中,(1)可以先于(4)执行吗?据推测,读指令 (1) 可以在写指令 (4) 之前重新排序。“call”指令可能有“jmp”部分,但是具有推测执行......所以我觉得它可能会发生,但我希望更熟悉这个问题的人可以证实这一点。

  5. 上面的代码中,(5)可以先于(2)执行吗?如果“ret”被认为是一个READ指令,那么我认为它不会发生。但我再次希望有人能证实这一点。

如果需要 func1() 的汇编代码,它应该类似于:

mov    %gs:0x24,%eax          (1) 
orl    $0x8,%gs:0x24 …
Run Code Online (Sandbox Code Playgroud)

x86 assembly cpu-architecture speculative-execution

6
推荐指数
1
解决办法
1128
查看次数

L1 缓存控制器处理来自 CPU 的内存请求的顺序

在总存储顺序 (TSO) 内存一致性模型下,x86 cpu 将有一个写入缓冲区来缓冲写入请求,并且可以为来自写入缓冲区的重新排序的读取请求提供服务。并且它说写缓冲区中的写请求将退出并以FIFO顺序向缓存层次结构发出,这与程序顺序相同。

我很好奇:

为了服务从写缓冲区发出的写请求,一级缓存控制器是否处理写请求,完成写请求的缓存一致性,并按照与发出顺序相同的顺序将数据插入一级缓存?

hardware x86 cpu-architecture memory-barriers cpu-cache

5
推荐指数
2
解决办法
656
查看次数

理解内存障碍

我试图以一种对Java无锁程序员有用的级别来理解内存障碍,我觉得这个级别介于学习volatile和根据x86手册学习存储/加载缓冲区之间。

我花了一些时间阅读一堆博客/食谱,并提出了以下摘要。知识渊博的人可以看一下摘要,看看我是否错过或列出了不正确的内容。

围栏:

Name             : LFENCE/Load Barrier/Acquire Fence
Barriers         : LoadLoad + LoadStore
Details          : Given sequence {Load1, LFENCE, Load2, Store1}, the
                   barrier ensures that Load1 can't be moved south and
                   Load2 and Store1 can't be moved north of the
                   barrier. 
                   Note that Load2 and Store1 can still be reordered.

Buffer Effect    : Causes the contents of the LoadBuffer 
                   (pending loads) to be processed for that CPU.This
                   makes program state exposed from other CPUs visible
                   to this CPU before Load2 …
Run Code Online (Sandbox Code Playgroud)

java x86 volatile memory-barriers

4
推荐指数
1
解决办法
889
查看次数

推测执行的 CPU 分支是否可以包含访问 RAM 的操作码?

据我了解,当 CPU 推测性地执行一段代码时,它会在切换到推测性分支之前“备份”寄存器状态,以便如果预测结果错误(使分支无用)——寄存器状态将是安全恢复,而不会破坏“状态”。

所以,我的问题是:推测执行的 CPU 分支是否可以包含访问 RAM 的操作码?

我的意思是,访问 RAM 不是“原子”操作——如果数据当前不在 CPU 缓存中,那么从内存中读取一个简单的操作码可能会导致实际的 RAM 访问,这可能会变成一个非常耗时的操作,从 CPU 的角度来看。

如果在推测分支中确实允许这种访问,它是否仅用于读取操作?因为,我只能假设,如果一个分支被丢弃并执行“回滚”,根据它的大小恢复写操作可能会变得非常缓慢和棘手。而且,可以肯定的是,至少在某种程度上支持读/写操作,因为寄存器本身,在某些 CPU 上,据我所知,物理上位于 CPU 缓存上。

所以,也许更精确的表述是:推测执行的一段代码有什么限制?

cpu cpu-architecture speculative-execution

4
推荐指数
1
解决办法
518
查看次数