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

时间:2016-05-21 09:29:41

标签: c++ optimization x86 intel cpu-architecture

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

  

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

我所做的更改似乎将代码运行时间增加了一秒,但我不完全确定在不添加代码的情况下我可以更改以停止管道。指向正确的方向将是非常棒的,我感谢任何回应。

更新:the professor who gave this assignment posted some details

亮点是:

  • 这是社区大学的第二学期建筑课(使用轩尼诗和帕特森教科书)。
  • 实验室计算机具有Haswell CPU
  • 学生们已接触到CPUID指令以及如何确定缓存大小,以及内在函数和CLFLUSH指令。
  • 允许任何编译器选项,因此是内联asm。
  • 宣布编写自己的平方根算法是在苍白的
  • 之外

Cowmoogun对元线程的评论表明it wasn't clear compiler optimizations could be part of this, and assumed -O0,并且运行时间增加17%是合理的。

所以听起来这个任务的目标是让学生重新排序现有的工作,以减少指令级并行或类似的事情,但人们深入研究并学到更多东西并不是一件坏事。 / p>

请记住,这是一个计算机架构问题,而不是关于如何使C ++变得缓慢的问题。

3 个答案:

答案 0 :(得分:32)

你可以采取一些措施使事情尽可能地糟糕:

  • 编译i386架构的代码。这将阻止使用SSE和更新的指令并强制使用x87 FPU。

  • 到处使用std::atomic个变量。由于编译器被迫在整个地方插入内存屏障,这将使它们非常昂贵。这是一个无能为力的人可能会“确保线程安全”的事情。

  • 确保以最糟糕的方式访问内存以供预取程序预测(列主要与行主要)。

  • 为了使您的变量更加昂贵,您可以通过使用new分配它们来确保它们都具有“动态存储持续时间”(堆分配),而不是让它们具有“自动存储持续时间”(堆栈已分配) )。

  • 确保您分配的所有内存都非常奇怪地对齐,并且无论如何都要避免分配大页面,因为这样做会使TLB效率太高。

  • 无论您做什么,都不要在启用编译器优化器的情况下构建代码。并确保启用最具表现力的调试符号(不会使代码运行更慢,但会浪费一些额外的磁盘空间)。

注意:这个答案基本上只是总结了我对@Peter Cordes已经纳入他非常好的答案的评论。如果你只剩下一个,建议他得到你的支持:)

答案 1 :(得分:10)

您可以使用long double进行计算。在x86上,它应该是80位格式。只有传统的x87 FPU才支持此功能。

x87 FPU的几个缺点:

  1. 缺少SIMD,可能需要更多说明。
  2. 基于堆栈,对于超标量和流水线架构存在问题。
  3. 独立且非常小的寄存器集,可能需要从其他寄存器进行更多转换以及更多内存操作。
  4. 在Core i7上有3个SSE端口和2个x87端口,处理器可以执行较少的并行指令。

答案 2 :(得分:3)

迟到的答案,但我觉得我们没有滥用链接列表和TLB。

使用mmap分配节点,以便您主要使用地址的MSB。这应该导致长TLB查找链,页面为12位,为转换留下52位,或者每次必须遍历大约5个级别。运气好的话,他们必须每次进入内存进行5级查询加上1次内存访问才能进入你的节点,最高级别很可能是在某个地方的缓存中,所以我们可以希望获得5 *内存访问。放置节点,使其跨越最差的边界,以便读取下一个指针将导致另外3-4次翻译查找。由于大量的翻译查找,这也可能完全破坏缓存。此外,虚拟表的大小可能会导致大多数用户数据被分页到磁盘上额外的时间。

从单个链表中读取时,请务必每次从列表的开头读取,以便在读取单个数字时造成最大延迟。