当前位置: 首页 > 面试题库 >

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

李言
2023-03-14
问题内容

我已经花了一个星期的时间来尝试完成这项任务,我希望这里有人可以带领我走上正确的道路。让我从讲师的指示开始:

您的分配与我们的第一个实验室分配相反,后者是优化素数程序。您在此作业中的目的是简化程序,即使其运行缓慢。这两个都是占用大量CPU的程序。他们需要几秒钟才能在我们的实验室PC上运行。您可能无法更改算法。

要优化程序,请使用有关Intel i7管道运行方式的知识。想像一下重新排序指令路径以引入WAR,RAW和其他危害的方法。想办法最大限度地减少缓存的有效性。令人作呕的无能。

作业中选择了磨刀石或蒙特卡洛程序。缓存效率注释大部分仅适用于Whetstone,但我选择了蒙特卡洛模拟程序:

// 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;
  double euclid_sq = 0.0;

  // Continue generating two uniform random variables
  // until the square of their "euclidean distance" 
  // is less than unity
  do {
    x = 2.0 * rand() / static_cast<double>(RAND_MAX)-1;
    y = 2.0 * rand() / static_cast<double>(RAND_MAX)-1;
    euclid_sq = x*x + y*y;
  } while (euclid_sq >= 1.0);

  return x*sqrt(-2*log(euclid_sq)/euclid_sq);
}

// Pricing a European vanilla call option with a Monte Carlo method
double monte_carlo_call_price(const int& num_sims, const double& S, const double& K, const double& r, const double& v, const double& T) {
  double S_adjust = S * exp(T*(r-0.5*v*v));
  double S_cur = 0.0;
  double payoff_sum = 0.0;

  for (int i=0; i<num_sims; i++) {
    double gauss_bm = gaussian_box_muller();
    S_cur = S_adjust * exp(sqrt(v*v*T)*gauss_bm);
    payoff_sum += std::max(S_cur - K, 0.0);
  }

  return (payoff_sum / static_cast<double>(num_sims)) * exp(-r*T);
}

// Pricing a European vanilla put option with a Monte Carlo method
double monte_carlo_put_price(const int& num_sims, const double& S, const double& K, const double& r, const double& v, const double& T) {
  double S_adjust = S * exp(T*(r-0.5*v*v));
  double S_cur = 0.0;
  double payoff_sum = 0.0;

  for (int i=0; i<num_sims; i++) {
    double gauss_bm = gaussian_box_muller();
    S_cur = S_adjust * exp(sqrt(v*v*T)*gauss_bm);
    payoff_sum += std::max(K - S_cur, 0.0);
  }

  return (payoff_sum / static_cast<double>(num_sims)) * exp(-r*T);
}

int main(int argc, char **argv) {
  // First we create the parameter list                                                                               
  int num_sims = 10000000;   // Number of simulated asset paths                                                       
  double S = 100.0;  // Option price                                                                                  
  double K = 100.0;  // Strike price                                                                                  
  double r = 0.05;   // Risk-free rate (5%)                                                                           
  double v = 0.2;    // Volatility of the underlying (20%)                                                            
  double T = 1.0;    // One year until expiry                                                                         

  // Then we calculate the call/put values via Monte Carlo                                                                          
  double call = monte_carlo_call_price(num_sims, S, K, r, v, T);
  double put = monte_carlo_put_price(num_sims, S, K, r, v, T);

  // Finally we output the parameters and prices                                                                      
  std::cout << "Number of Paths: " << num_sims << std::endl;
  std::cout << "Underlying:      " << S << std::endl;
  std::cout << "Strike:          " << K << std::endl;
  std::cout << "Risk-Free Rate:  " << r << std::endl;
  std::cout << "Volatility:      " << v << std::endl;
  std::cout << "Maturity:        " << T << std::endl;

  std::cout << "Call Price:      " << call << std::endl;
  std::cout << "Put Price:       " << put << std::endl;

  return 0;
}

我所做的更改似乎将代码的运行时间增加了一秒钟,但是我不确定要更改哪些内容以停止管道而不添加代码。指向正确方向的指示非常棒,我感谢您的任何答复。

更新:进行此作业的教授发布了一些详细信息
重点是:

  • 这是社区学院的第二学期建筑课程(使用轩尼诗和帕特森教材)。
  • 实验室计算机具有Haswell CPU
  • 学生们已经了解了该CPUID指令以及如何确定缓存大小以及内在函数和CLFLUSH指令。
  • 允许使用任何编译器选项,内联汇编语言也是如此。
  • 宣布编写自己的平方根算法超出了预期

Cowmoogun在meta线程上的评论表明,尚不清楚编译器优化是否可能是其中的一部分,并且假定-O0,并且运行时增加17%是合理的。

因此,听起来任务的目的是让学生重新安排现有的工作,以减少指令级的并行性或类似的事情,但是人们深入研究并了解更多知识并不是一件坏事。

请记住,这是一个计算机体系结构问题,而不是有关一般如何使C ++变慢的问题。


问题答案:

重要的背景知识:Agner Fog的microarch pdf,以及Ulrich Drepper的《每个程序员应该了解的内存》。另请参阅x86标记Wiki,尤其是Intel的优化手册,以及David Kanter 对Haswell微体系结构的分析,并带有图表。

任务很酷;比我所看到的要求学生优化一些代码gcc -O0的方法要好得多,学习了很多与实际代码无关紧要的技巧。在这种情况下,系统会要求您了解CPU管道,并用它来指导您的优化工作,而不仅仅是盲目的猜测。 这个过程中最有趣的部分是用“恶魔般的无能”证明每个悲观的理由,而不是故意的恶意。

作业措辞和代码存在问题:

此代码的特定于uarch的选项受到限制。它不使用任何数组,大部分成本是对exp/ log库函数的调用。没有一个或多或少的指令级并行性的明显方法,循环承载的依赖链非常短。

我很乐意看到一个答案,该答案试图通过重新安排表达式来更改依赖项来降低速度,从而仅从依赖项(危害)中减少ILP,从而减慢速度。 我没有尝试过。

英特尔Sandybridge系列CPU是激进的无序设计,需要花费大量的晶体管和功能来寻找并行性,并避免可能困扰传统RISC有序流水线的危险(依赖性)。通常,减慢速度的唯一传统危险是RAW“真实”依赖关系,这些依赖关系会导致吞吐量受延迟的限制。

由于寄存器更名,寄存器的战争和战争危险几乎不是问题。(popcnt/lzcnt/除外,尽管它们仅是写操作,但它们对Intel CPU的目标tzcnt具有虚假的依赖关系,即WAW被视为RAW危险+写操作)。对于内存排序,现代CPU使用存储队列将提交到缓存的时间延迟到退役之前,这也避免了WAR和WAW危害。

为什么mulss在Haswell上只需要3个周期,而不同于Agner的指令表?关于寄存器重命名和在FP点积循环中隐藏FMA延迟的更多信息。

Nehalem(Core2的后继产品)引入了“ i7”品牌名称,一些英特尔手册甚至在看起来像Nehalem时也称“ Core i7”,但他们保留了Sandybridge和以后的微体系结构的“ i7”品牌。 SnB是P6家族进化为新物种SnB家族的时候。在许多方面,Nehalem与Pentium III的共同点比与Sandybridge的共同点更多(例如,寄存器读取停顿和ROB读取停顿不会在SnB上发生,因为它改为使用物理寄存器文件。还有uop缓存和不同的内部uop格式)。 术语“ i7体系结构”没有用,因为将SnB系列与Nehalem而不是Core2组合在一起几乎没有意义。(不过,Nehalem确实引入了共享包容性L3缓存体系结构,用于将多个内核连接在一起。还集成了GPU。因此在芯片级,命名更有意义。)

恶魔般的无能为力的好点子摘要
即使是完全没有能力的人,也不太可能添加明显无用的工作或无限循环,并且使C ++ / Boost类混乱也超出了分配的范围。

具有单个共享 std::atomic<uint64_t>循环计数器的多线程,因此发生了正确的迭代总数。原子uint64_t尤其糟糕-m32 -march=i586。要获得加分,请使其不对齐,并以不均匀的分割(不是4:4)穿过页面边界。
其他非原子变量的错误共享 ->清除内存顺序错误推测管道,以及额外的缓存未命中。
而不是-在FP变量上使用,而是对高字节与0x80进行异或运算以翻转符号位,从而导致存储转发停顿。
每次迭代都要独立计时,甚至比还要重RDTSC。例如CPUID/ RDTSC或进行系统调用的时间函数。序列化指令本质上是管道不友好的。
变化乘以常数,除以常数(即“易于阅读”)。 div速度慢且未完全流水线化。
使用AVX(SIMD)向量化乘法/平方,但vzeroupper在调用标量数学库exp()和log()函数之前无法使用,从而导致AVX <-> SSE转换停顿。
将RNG输出存储在链接列表中,或存储在无序遍历的数组中。每次迭代的结果相同,最后求和。
这个答案也包括在内,但不包括在摘要中:建议在非流水线CPU上的运行速度同样慢,或者即使是恶魔般的无能也似乎没有道理。例如,许多编译器想法产生了明显不同的/更糟糕的汇编。

多线程严重
也许使用OpenMP进行很少迭代的多线程循环,其开销远大于速度增益。但是,蒙特卡洛代码具有足够的并行度,实际上可以提高速度。如果我们成功地使每次迭代变慢。(每个线程计算一个partial payoff_sum,最后添加)。 #omp parallel在那个循环上可能是一个优化,而不是悲观。

多线程,但强制两个线程共享同一循环计数器(具有atomic增量,因此迭代总数正确)。这似乎是有害的逻辑。这意味着将static变量用作循环计数器。这证明了使用atomicfor循环计数器的合理性,并创建了实际的高速缓存行ping-ponging(只要线程不在具有超线程的同一个物理内核上运行;这可能不会那么慢)。无论如何,这比的无可争议的情况要慢得多lock inc。并lock cmpxchg8b以原子方式增加了竞争uint64_t32位系统上则要重试在一个循环而不是硬件的仲裁原子inc。

还创建错误共享,其中多个线程将其私有数据(例如RNG状态)保留在同一缓存行的不同字节中。 (有关它的Intel教程,包括要查看的perf计数器)。 这有一个微体系结构特定的方面:英特尔CPU推测没有发生内存错误排序,并且至少在P4上有一个内存顺序机器清除性能事件可以检测到这一点。在Haswell上的惩罚可能不会那么大。正如该链接所指出的那样,locked指令假定会发生这种情况,从而避免了错误的猜测。正常负载推测在执行负载和按程序顺序退出之间,其他内核不会使缓存行无效(除非您使用pause)。没有locked指令的真正共享通常是一个错误。将非原子共享循环计数器与原子案例进行比较会很有趣。要真正悲观,请保留共享的原子循环计数器,并在某些其他变量的相同或不同的高速缓存行中造成错误共享。

特定于uarch的随机想法:
如果您可以引入任何不可预测的分支,那将大大简化代码。现代的x86 CPU具有很长的流水线,因此(从uop缓存运行时)错误估计会花费约15个周期。

依赖链:
我认为这是作业的预期部分之一。

通过选择具有一个长依赖性链而不是多个短依赖性链的操作顺序,可以破坏CPU利用指令级并行性的能力。除非您使用-ffast-math,否则不允许编译器更改FP计算的操作顺序,因为这可能会更改结果(如下所述)。

为了真正有效地执行此操作,请增加循环承载的依赖链的长度。但是,没有什么比这明显的了:编写的循环具有非常短的循环承载的依赖链:只是FP添加。(3个周期)。多个迭代可以立即进行计算,因为它们可以payoff_sum +=在上一次迭代结束之前早点开始。(log()并exp接受许多指令,但不多于Haswell用来查找并行性的乱序窗口:ROB大小= 192融合域uops,而调度程序大小= 60未融合域uops。当前迭代的执行进展到足以为下一个迭代发出的指令腾出空间时,当较旧的指令离开执行单元时,其输入准备就绪的任何部分(即独立/独立的Dep链)都可以开始执行。免费(例如,因为瓶颈是延迟,而不是吞吐量)。

几乎可以肯定的是,RNG状态将是比更长的循环承载依赖链addps。

使用较慢/更多的FP操作(尤其是更多的划分):
除以2.0而不是乘以0.5,依此类推。FP乘法在Intel设计中流水线较多,在Haswell及更高版本上每0.5c吞吐量有一个。 FPdivsd/divpd仅部分流水线。(尽管Skylake的每4c吞吐量令人印象深刻divpd xmm,延迟为13-14c,而Nehalem(7-22c)根本没有流水线。)

do { ...; euclid_sq = x*x + y*y; } while (euclid_sq >= 1.0);显然是测试的距离,所以显然这将是适当的,以sqrt()它。:P(sqrt甚至比还要慢div)。

正如@Paul Clayton所建议的那样,用关联/分布等效项重写表达式会带来更多工作(只要您不使用-ffast-math允许编译器重新优化的方法即可)。 (exp(T*(r-0.5*v*v))可能成为exp(T*r - T*v*v/2.0)。请注意,虽然实数数学是关联的,但即使不考虑上溢/ NaN ,浮点数学也不是(这就是为什么-ffast-math默认情况下不启用)。请参阅Paul的评论,以获得非常多毛的嵌套pow()建议。

如果您可以将计算结果缩减为非常小的数字,那么当对两个正数的运算产生反范数时,FP数学运算会花费大约120个额外的周期来捕获微码。有关确切数字和详细信息,请参见Agner Fog的microarch pdf。这是不可能的,因为您有很多乘数,因此比例因子将被平方并一直下溢至0.0。我看不出有什么方法可以证明无能(即使是恶魔般)的必要缩放,只是故意的恶意。

如果可以使用内在函数(<immintrin.h>)
用于movnti从缓存中逐出数据。恶魔般的:它是新的且无序的,因此应该让CPU更快地运行,对吗?或者,在某人有可能要严格执行此操作的情况下,请查看该链接的问题(对于仅在某些位置很热的分散写入)。 clflush没有恶意可能是不可能的。

在FP数学运算之间使用整数混洗会导致旁路延迟。

混合SSE和AVX指令不正确使用的vzeroupper预SKYLAKE微架构的原因大排挡(和不同的惩罚在SKYLAKE微架构)。即使没有这种情况,严重的矢量化也可能比标量更糟(将数据混入/移出矢量所花费的周期要比对256b个矢量一次执行4次Monte-Carlo迭代的add / sub / mul / div / sqrt操作节省的周期更多) 。add / sub / mul执行单元是完全流水线的和全角的,但是256b向量上的div和sqrt却不如128b向量(或标量)上的div和sqrt快,因此的加速并不显着double。

exp()并且log()没有硬件支持,因此该部分需要将向量元素提取回标量并分别调用库函数,然后将结果重新排列回向量。libm通常被编译为仅使用SSE2,因此将使用标量数学指令的传统SSE编码。如果您的代码使用256b向量并且exp没有先执行调用vzeroupper,那么您将停顿。返回后,像vmovsd将下一个矢量元素设置为arg 的AVX-128指令exp也将停止。然后exp(),当它运行SSE指令时将再次停顿。 这正是此问题中发生的事情,导致速度降低了10倍。 (感谢@ZBoson)。

有关此代码,另请参阅Nathan Kurz对Intel的mathlib与glibc进行的实验。未来的glibc将带有的向量化实现exp()等。

如果定位到前IvB或esp。Nehalem,尝试让gcc导致16位或8位操作后跟32位或64位操作导致部分寄存器停顿。在大多数情况下,gcc将movzx在8位或16位操作之后使用,但是在这种情况下,gcc会进行修改ah,然后读取ax

使用(内联)asm:
使用(inline)asm,您可能会破坏uop缓存:不能容纳在三个6uop缓存行中的32B代码块会强制从uop缓存切换到解码器。在内部循环内的分支目标上ALIGN使用许多单字节nops而不是几个long nop的不称职可能会解决问题。或者将对齐填充放在标签之后,而不是之前。:P仅当前端是一个瓶颈时才重要,而如果我们成功地对其余代码进行了悲观就不会如此。

使用自修改代码触发管道清除(又称机器核)。

LCP因16位指令的停顿而导致立即数太大而无法容纳8位,因此不太可能有用。SnB及以后版本的uop缓存意味着您只需支付一次解码费用。在Nehalem(第一个i7)上,它可能适用于不适合28 uop循环缓冲区的循环。gcc有时会生成此类指令,即使-mtune=intel使用32位指令时也是如此。

定时的一个常见习惯用法是CPUID(序列化)RDTSC。使用CPUID/ 分别为每个迭代计时RDTSC,以确保RDTSC不会按照较早的指令对它们进行重新排序,这会使事情变慢很多。(在现实生活中,计时的明智方法是将所有迭代计时在一起,而不是分别计时并累加起来)。

导致大量缓存未命中和其他内存减慢
union { double d; char a[8]; }对一些变量使用a 。 通过仅对一个字节进行窄存储(或“读-修改-写”)而导致存储转发停顿。(该Wiki文章还介绍了有关加载/存储队列的许多其他微体系结构内容)。例如,仅对高字节而不是运算符使用XOR 0x80翻转a的符号double-。这位无能的开发者可能听说过FP比整数慢,因此尝试使用整数op尽可能多地做。(针对SSE寄存器中FP数学的非常好的编译器可能会将其编译为xorps 在另一个xmm寄存器中使用常量,但是对于x87来说,这并不可怕,唯一的方法是,如果编译器意识到它正在取反值,并用减法替换下一个加法。)

使用volatile,如果你有编译-O3和不使用std::atomic,强制编译实际存储/重装所有的地方。全局变量(而不是局部变量)也将强制执行一些存储/重装操作,但是C ++内存模型的弱排序并不要求编译器始终将溢出/重装到内存中。

用大结构的成员替换局部变量,这样就可以控制内存布局。

在结构中使用数组进行填充(并存储随机数以证明其存在的合理性)。

选择您的内存布局,以便所有内容进入L1高速缓存中同一“集合”中的不同行。它只有8路关联,即每组有8条“路”。高速缓存行为64B。

更好的是,将事物精确地分开4096B,因为加载对存储到不同页面的错误依赖关系,但页面内的偏移量相同。激进的乱序CPU使用内存消除歧义来确定何时可以对装载和存储进行重新排序而不改变结果,并且Intel的实现中存在假阳性,从而阻止了提前开始装载。可能他们只检查页面偏移量以下的位,因此检查可以在TLB将高位从虚拟页面转换为物理页面之前开始。除Agner的指南外,请参阅Stephen Canon的答案,以及@Krazy Glew对同一问题的答案结尾附近的部分。(安迪·格莱夫(Andy Glew)是英特尔最初的P6微体系结构的架构师之一。)

使用__attribute__((packed))让您误对准变量,使它们跨越高速缓存行或偶数页边界。(因此,一个负载double需要来自两条高速缓存行的数据)。除交叉高速缓存行和页面行外,未对齐的负载在任何Intel i7 uarch中均不受影响。 高速缓存行拆分仍然需要额外的周期。Skylake将页面拆分加载的代价从100个周期减少到5个周期。(第2.1.3节)。也许与能够并行执行两个页面遍历有关。

在上进行页面拆分atomic<uint64_t>应该是最糟糕的情况,尤其是。如果它在一页中是5个字节,在另一页中是3个字节,或者不是4:4。在某些Iarch上使用16B向量对缓存行进行分割时,即使在中间进行拆分也更有效。将所有内容放入alignas(4096) struct __attribute((packed))(当然是为了节省空间),包括一个用于存储RNG结果的数组。通过使用uint8_t或uint16_t在计数器前放置东西来实现未对准。

如果您可以让编译器使用索引寻址模式,那将击败uop micro-fusion。也许通过使用#defines将简单的标量变量替换为my_data[constant]

如果您可以引入一个更高级别的间接寻址,那么就不会很早就知道加载/存储地址,这可能会进一步造成负面影响。

以非连续顺序遍历数组
我认为我们首先可以提出引入数组的不称职理由:它使我们可以将随机数生成与随机数使用分开。每次迭代的结果也可以存储在数组中,以便以后进行求和(具有更多的恶魔般的能力)。

对于“最大随机性”,我们可以让一个线程在随机数组上循环,向其中写入新的随机数。消耗随机数的线程可以生成一个随机索引以从中加载随机数。(这里有一些工作,但是在微体系结构上,它有助于及早知道加载地址,因此可以在需要加载的数据之前解决任何可能的加载延迟。)在不同内核上具有读写器将导致内存排序错误-推测管道清除(如先前在虚假共享情况下所述)。

为了获得最大的悲观性,请以4096字节的步幅(即512倍)遍历数组。例如

for (int i=0 ; i<512; i++)
    for (int j=i ; j<UPPER_BOUND ; j+=512)
        monte_carlo_step(rng_array[j]);
因此,访问模式为0,4096,8192,...,
8,4104,8200,...
16,4112,8208,...

这就是访问double rng_array[MAX_ROWS][512]错误顺序的2D数组所得到的结果(如@JesperJuhl所建议的那样,循环遍历行,而不是内部循环中行中的列)。如果恶魔般的无能行为可以证明二维数组具有这样的尺寸,那么花园中各种现实世界的无能行为就很容易以错误的访问模式来证明循环。这发生在现实生活中的真实代码中。

如果数组不那么大,请在必要时调整循环范围以使用许多不同的页面,而不是重复使用相同的几页。跨页面无法(完全(或完全))执行硬件预取。预取器可以跟踪每个页面中的一个前向流和一个后向流(在此处发生这种情况),但是仅当内存带宽尚未被非预取所饱和时,才对它进行操作。

这也将产生大量TLB未命中,除非该网页被合并成一个hugepage(Linux确实这个机会主义匿名(没有文件支持)的分配一样malloc/ new在使用mmap(MAP_ANONYMOUS))。

可以使用链接列表来代替存储结果列表的数组。然后,每次迭代都需要一个指针追逐负载(下一个负载的负载地址存在RAW真正的依赖危险)。使用错误的分配器,您可能设法分散内存中的列表节点,从而击败缓存。如果使用了令人讨厌的分配器,它将每个节点放在其自己页面的开头。(例如,mmap(MAP_ANONYMOUS)直接分配,而不会破坏页面或跟踪对象大小以正确支持free)。

这些并不是真正的微体系结构特定的,并且与管道没有什么关系(大多数非管道CPU也会减慢速度)。

有点离题:使编译器生成更糟糕的代码/做更多工作:
使用C ++ 11 std::atomic<int>std::atomic<double>以获得最简洁的代码。lock即使没有其他线程的争用,MFENCEs和ed指令也非常慢。

-m32会使代码变慢,因为x87代码比SSE2代码差。基于堆栈的32位调用约定接受更多指令,甚至将堆栈上的FP args传递给诸如之类的函数exp()。 atomic<uint64_t>::operator++开启-m32需要lock cmpxchg8B循环(i586)。(因此,将其用于循环计数器![邪恶的笑声])。

-march=i386也会感到悲观(感谢@Jesper)。FP fcom比686要慢fcomi。586之前的版本没有提供原子的64位存储(更不用说cmpxchg了),因此所有64位atomic操作都编译为libgcc函数调用(可能是为i686编译的,而不是实际使用的锁)。在上一段中的Godbolt编译器资源管理器链接上进行尝试。

在sizeof()为10或16(带填充用于对齐)的ABI中,使用long double/ sqrtl/ expl可提高精度和减慢速度long double。(IIRC,64位Windows应用8字节longdouble等效于double(无论如何,负载/ 10byte的商店(80bit的)FP操作数是4/7微指令,与float或double仅服用1 UOP各为fld m64/m32/ fst)。强制的x87与long double负自动矢量即使对于海湾合作委员会-m64 -march=haswell -O3。

如果不使用atomic<uint64_t>循环计数器,则longdouble用于所有事物,包括循环计数器。

atomic<double>可以编译,但+=不支持类似read-modify-write的操作(即使在64bit上也是如此)。 atomic<longdouble>必须仅针对原子加载/存储调用库函数。这可能真的效率不高,因为x86 ISA自然不支持原子10字节的加载/存储,而我认为不加锁(cmpxchg16b)的唯一方法需要64位模式。

在中-O0,通过将部分分配给临时var来破坏较大的表达式将导致更多的存储/重载。没有volatile或没有东西,这与真实代码的真实构建将使用的优化设置无关。

C别名规则允许a char别名任何东西,因此通过char*强制存储可以使编译器在字节存储之前/之后(甚至在)存储/重新加载所有内容-O3。(例如,这是对在的数组上运行的代码uint8_t进行自动向量化的问题。)

尝试使用uint16_t循环计数器,以强制截断为16位,这可能是通过使用16位操作数大小(潜在的停顿)和/或额外的movzx指令(安全)来实现的。 签名溢出是未定义的行为,所以,除非你使用-fwrapv或至少-fno-strict-overflow,签署了循环计数器不必重新登录扩展每次迭代,即使作为偏移64位指针。

强制从整数到另一个的转换float。和/或double<=>float转换。指令的延迟大于一,并且标量int-> float(cvtsi2ss)的设计很差,无法将xmm寄存器的其余部分不置零。(pxor出于这个原因,gcc插入了一个额外的代码来打破依赖关系。)

经常将您的CPU关联性设置为其他CPU(@Egwor建议)。恶性推理:您不希望一个内核长时间运行线程而过热,是吗?也许交换到另一个内核将使该内核加速到更高的时钟速度。(实际上:它们彼此之间的温度非常接近,因此除非在多插座系统中,否则这几乎是不可能的)。现在,只是调错了,并且经常这样做。除了在操作系统保存/恢复线程状态上花费的时间外,新内核还具有冷的L2 / L1缓存,uop缓存和分支预测器。

频繁引入不必要的系统调用可能会使您变慢,无论它们是什么。尽管某些重要但简单的方法gettimeofday可以在用户空间中实现,而无需过渡到内核模式。(Linux上的glibc在内核的帮助下完成了此操作,因为内核会在中导出代码vdso)。

有关更多系统调用开销(包括返回到用户空间后的缓存/ TLB丢失,而不仅仅是上下文切换本身),FlexSC论文对当前情况进行了一些出色的性能分析,并提出了关于批处理系统的建议来自大型多线程服务器进程的调用。



 类似资料:
  • 我已经绞尽脑汁了一个星期,想完成这个任务,我希望这里有人能引导我走上正确的道路。我先从教官的指示说起: 你的作业与我们的第一个实验作业相反,我们的第一个实验作业是优化一个素数程序。你在这份作业中的目的是使程序悲观,即使它运行得更慢。这两个都是CPU密集型程序。在我们的实验室电脑上运行需要几秒钟的时间。您不能更改算法。 若要解除程序的优化,请使用您对Intel i7管道运行方式的了解。想象一下重新排

  • 我想安装英特尔 HAXM(使 Android 模拟器更快),但安装程序显示我的计算机“不支持虚拟化技术 (VT-x)”。 在BIOS中,我看到我启用了虚拟化技术,并且我还有运行良好的Windows Phone模拟器(需要这项技术)。 我下载了Intel Identification Utility,它说我的Processor不支持这项技术(尽管上面的事实和互联网上的搜索表明我的i5确实支持这项科技

  • 本人背景算法工程师,cv方向论文两篇,nlp实习经历。 一轮 25min 自我介绍 询问项目论文 问题:resnet架构、c++程序执行过程 反问 二轮 40min 自我介绍 询问项目论文(论文很细致,结构、损失、数据集、实验结果、创新点) 问题:Transformer架构、核心公式以及采用什么的正则化、BN和LN的区别。其实大部分是根据论文内容发散 反问 三轮HR面 25min 没有自我介绍 问

  • 如何使用Java中的Intel AVX矢量指令集?这是一个简单的问题,但答案似乎很难找到。

  • Android 应用性能优化系列 原文链接分别为 : https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE https://www.udacity.com/course/ud825 译者 : 胡凯 Android性能优化典范 Android性能优化之渲染篇 Android性能优化之运算篇 Android性能

  • Docker需要哪些英特尔虚拟化技术? 在英特尔cpu上运行的Linux系统上,英特尔需要哪些虚拟化技术来完成Docker容器的执行?例如,有VT-X、… 或者没有必要使用这样的技术,因为Docker与现有的虚拟化解决方案(如VirtualBox)有所不同。在这种情况下,为什么没有必要?