托管代码可以影响指令级并行吗?

时间:2011-10-06 21:29:59

标签: c# cpu-architecture

有什么方法可以影响编写C#代码的指令级并行性?换句话说,有没有办法可以“帮助”编译器生成最能利用ILP的代码?我问这个是因为我试图从机器架构的一些概念中抽象出来,我需要知道这是否可行。如果没有,那么我将有理由从ILP中抽象出来。

编辑:您会注意到我不想以任何方式使用C#来利用ILP。我的问题恰恰相反。释义:“我希望没有办法从C#中利用ILP”

感谢。

4 个答案:

答案 0 :(得分:1)

ILP是CPU的一项功能。你无法控制它。 编译器通过打破依赖链来尽力利用它。

这可能包括.Net JIT编译器,但我没有证据证明这一点。

答案 1 :(得分:0)

在获得指令级并行性时,您将受JIT的支配。 谁知道JIT实际上做了哪些优化?如果我真的需要,我会选择另一种语言,比如C ++。

为了最好地利用ILP,您需要打破依赖链。这应该仍然适用。请参阅此thread

然而,在所有的抽象中,我怀疑在最极端的情况下仍然可以有效地利用它。你需要什么样的例子?

答案 2 :(得分:0)

没有明确或直接的方法来影响或暗示IL或C#中的.NET编译器来执行此操作。这完全是编译器的工作。

您可以对此进行的唯一影响是构建您的程序,以便更有可能(尽管不能保证)为您执行此操作,并且很难知道它是否甚至对结构起作用或不。这很好地抽象了.NET语言和IL。

答案 3 :(得分:0)

您可以在CLI中使用ILP。所以简短的回答是否。

再长一点:

之前我为一个简单的图像处理任务编写了一个代码,并使用这种优化来使我的代码“更快”。

一个“简短”的例子:

static void Main( string[] args )
{
  const int ITERATION_NUMBER = 100;

  TimeSpan[] normal = new TimeSpan[ITERATION_NUMBER];
  TimeSpan[] ilp = new TimeSpan[ITERATION_NUMBER];

  int SIZE = 4000000;
  float[] data = new float[SIZE];
  float safe = 0.0f;

  //Normal for
  Stopwatch sw = new Stopwatch();

  for (int iteration = 0; iteration < ITERATION_NUMBER; iteration++)
  {
    //Initialization
    for (int i = 0; i < data.Length; i++)
    {
      data[i] = 1.0f;
    }

    sw.Start();
    for (int index = 0; index < data.Length; index++)
    {
      data[index] /= 3.0f * data[index] > 2.0f / data[index] ? 2.0f / data[index] : 3.0f * data[index];
    }
    sw.Stop();
    normal[iteration] = sw.Elapsed;

    safe = data[0];

    //Initialization
    for (int i = 0; i < data.Length; i++)
    {
      data[i] = 1.0f;
    }

    sw.Reset();

    //ILP For
    sw.Start();
    float ac1, ac2, ac3, ac4;
    int length = data.Length / 4;
    for (int i = 0; i < length; i++)
    {
      int index0 = i << 2;

      int index1 = index0;
      int index2 = index0 + 1;
      int index3 = index0 + 2;
      int index4 = index0 + 3;

      ac1 = 3.0f * data[index1] > 2.0f / data[index1] ? 2.0f / data[index1] : 3.0f * data[index1];

      ac2 = 3.0f * data[index2] > 2.0f / data[index2] ? 2.0f / data[index2] : 3.0f * data[index2];

      ac3 = 3.0f * data[index3] > 2.0f / data[index3] ? 2.0f / data[index3] : 3.0f * data[index3];

      ac4 = 3.0f * data[index4] > 2.0f / data[index4] ? 2.0f / data[index4] : 3.0f * data[index4];

      data[index1] /= ac1;
      data[index2] /= ac2;
      data[index3] /= ac3;
      data[index4] /= ac4;
    }
    sw.Stop();
    ilp[iteration] = sw.Elapsed;

    sw.Reset();
  }
  Console.WriteLine(data.All(item => item == data[0]));
  Console.WriteLine(data[0] == safe);
  Console.WriteLine();

  double normalElapsed = normal.Max(time => time.TotalMilliseconds);
  Console.WriteLine(String.Format("Normal Max.: {0}", normalElapsed));
  double ilpElapsed = ilp.Max(time => time.TotalMilliseconds);
  Console.WriteLine(String.Format("ILP    Max.: {0}", ilpElapsed));
  Console.WriteLine();
  normalElapsed = normal.Average(time => time.TotalMilliseconds);
  Console.WriteLine(String.Format("Normal Avg.: {0}", normalElapsed));
  ilpElapsed = ilp.Average(time => time.TotalMilliseconds);
  Console.WriteLine(String.Format("ILP    Avg.: {0}", ilpElapsed));
  Console.WriteLine();
  normalElapsed = normal.Min(time => time.TotalMilliseconds);
  Console.WriteLine(String.Format("Normal Min.: {0}", normalElapsed));
  ilpElapsed = ilp.Min(time => time.TotalMilliseconds);
  Console.WriteLine(String.Format("ILP    Min.: {0}", ilpElapsed));
}

结果是(在.Net framework 4.0 Client profile上,发布):

在虚拟机上(我认为没有ILP):

真 真

Nor Max。:111,1894
ILP Max。:106,886

Nor Avg。:78,163619
ILP平均值:77,682513

Nor Min。:58,3035
最近的ILP:56,7672

在氙气上

真 真

Nor Max。:40,5892
ILP Max。:30,8906

Nor Avg。:35,637308
ILP平均值:25,45341

Nor Min。:34,4247
最近的ILP:23,7888

结果说明:

在Debug中,编译器没有应用opication,但第二个for循环比第一个更优化,因此存在显着差异。

答案似乎是在发布模式构建程序集的执行结果中。 IL编译器/ JIT-er最好尽量减少性能消耗(我认为甚至是ILP)。但是,无论你是否制作类似第二个for循环的代码,你都可以在特殊情况下获得更好的结果,而第二个循环可以在某些架构上超过第一个循环。但是

  

你受JIT的支配

可悲的是,如上所述。可悲的是,没有提及实现可以定义更多优化,如ILP(可以在规范中放置一个简短的段落)。但是他们无法枚举代码的各种形式的构建验证,而且CLI处于更高层次:

  

这很好地抽象了.NET语言和IL。

这是一个非常复杂的问题,只能以实验方式回答它。我不认为我们可以通过这样的方式得到更精确的回答。我认为这个问题具有误导性,因为它不依赖于C#,它取决于CLI的实现。

可能会有很多影响因素,在我们将其视为黑盒子之前,很难正确回答像JIT这样的问题。

我在第512-513页上找到了关于循环矢量化和自动读取的内容: http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf

我认为他们没有明确说明JIT-er在这种情况下需要如何表现,而且必须选择优化方式。所以我认为你可以影响,如果你可以编写更优的代码,JIT将尽可能使用ILP。

我认为,因为他们没有指明,有可能。

所以答案似乎不是。我相信如果规范没有说明,你不能在CLI的情况下从ILP中抽象出来。

<强>更新

之前我发现了一篇博文,但直到现在我才发现它: http://igoro.com/archive/gallery-of-processor-cache-effects/ 示例四包含一个简短但适当的答案。