可托管代码影响指令级并行性
本文关键字:并行性 指令 影响 托管代码 | 更新日期: 2023-09-27 18:10:42
是否有任何方法可以影响指令级并行编写c#代码?换句话说,是否有一种方法可以"帮助"编译器生成最好地利用ILP的代码?我问这个问题是因为我试图从机器架构的一些概念中抽象出来,我需要知道这是否可能。如果没有,那么我将有理由从ILP中抽象出来。
EDIT:您将注意到我不想以任何方式使用c#来利用ILP。我的问题恰恰相反。转述:"我希望没有办法从c#中利用ILP"
谢谢。
ILP是CPU的一个特性。你没有办法控制它。编译器通过破坏依赖链来尽力利用它。
这可能包括。net JIT编译器,但是我没有证据证明这一点。
在获得指令级并行性时,您将受JIT的支配。谁知道JIT实际上做了哪些优化?如果我真的需要的话,我会选择另一种语言,比如c++。
为了最好地利用ILP,你需要打破依赖链。这应该仍然适用。
然而,在所有的抽象中,我怀疑在最极端的情况下仍然有可能有效地利用这一点。你有什么需要的例子吗?
没有明确或直接的方法来影响或提示。net编译器在IL或c#中执行此操作。这完全是编译器的工作。
你能做的唯一影响就是构建你的程序,使它更有可能(尽管不能保证)为你做这件事,而且很难知道它是否对结构起作用。
您可以在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客户端配置文件,发布):
在虚拟机上(我认为没有ILP):
真的真正的
和马克斯。: 111,1894
独立Max。: 106886
中国人均:78,163619
ILP平均值:77,682513
Nor Min.: 58,3035
ILP最小值:56,7672
On a Xenon:
真的真正的
和马克斯。[40,5892]
独立Max。: 30, 8906年
中国人均:35,637308
ILP平均值:25,45341
ILP最小值:23,7888
结果说明:
在调试中,编译器没有应用优化,但是第二个for循环比第一个更优化,所以有显著的区别。
答案似乎在发布模式构建程序集的执行结果中。IL编译器/JIT-er最好将性能消耗最小化(我认为甚至是ILP)。但是,无论您是否编写类似第二个for循环的代码,您都可以在特殊情况下获得更好的结果,并且在某些架构上,第二个循环可以优于第一个循环。但
遗憾的是,前面提到的你任由JIT摆布
。遗憾的是,没有提到实现可以定义更多的优化,比如ILP(可以在规范中放置一个简短的段落)。但是它们不能列举代码的每一种架构优化形式,而CLI在更高的层次上:
这是一个非常复杂的问题,只有通过实验才能回答。我不认为这样能得到更精确的答案。我认为这个问题是误导性的,因为它不依赖于c#,它依赖于CLI的实现。这是从。net语言和IL中很好地抽象出来的。
可能有很多影响因素,很难正确回答这样一个关于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/例四包含了一个简短但恰当的回答。