Var*_*ron 12 .net c# clr mono cil
我注意到包装单个浮点数的结构明显比直接使用浮点数慢,大约有一半的性能.
using System;
using System.Diagnostics;
struct Vector1 {
    public float X;
    public Vector1(float x) {
        X = x;
    }
    public static Vector1 operator +(Vector1 a, Vector1 b) {
        a.X = a.X + b.X;
        return a;
    }
}
Run Code Online (Sandbox Code Playgroud)
但是,在添加额外的"额外"字段后,似乎会发生一些魔法并且性能再次变得更加合理:
struct Vector1Magic {
    public float X;
    private bool magic;
    public Vector1Magic(float x) {
        X = x;
        magic = true;
    }
    public static Vector1Magic operator +(Vector1Magic a, Vector1Magic b) {
        a.X = a.X + b.X;
        return a;
    }
}
Run Code Online (Sandbox Code Playgroud)
我用来对这些代码进行基准测试的代码如下:
class Program {
    static void Main(string[] args) {
        int iterationCount = 1000000000;
        var sw = new Stopwatch();
        sw.Start();
        var total = 0.0f;
        for (int i = 0; i < iterationCount; i++) {
            var v = (float) i;
            total = total + v;
        }
        sw.Stop();
        Console.WriteLine("Float time was {0} for {1} iterations.", sw.Elapsed, iterationCount);
        Console.WriteLine("total = {0}", total);
        sw.Reset();
        sw.Start();
        var totalV = new Vector1(0.0f);
        for (int i = 0; i < iterationCount; i++) {
            var v = new Vector1(i);
            totalV += v;
        }
        sw.Stop();
        Console.WriteLine("Vector1 time was {0} for {1} iterations.", sw.Elapsed, iterationCount);
        Console.WriteLine("totalV = {0}", totalV);
        sw.Reset();
        sw.Start();
        var totalVm = new Vector1Magic(0.0f);
        for (int i = 0; i < iterationCount; i++) {
            var vm = new Vector1Magic(i);
            totalVm += vm;
        }
        sw.Stop();
        Console.WriteLine("Vector1Magic time was {0} for {1} iterations.", sw.Elapsed, iterationCount);
        Console.WriteLine("totalVm = {0}", totalVm);
        Console.Read();
    }
}
Run Code Online (Sandbox Code Playgroud)
随着基准测试结果:
Float time was 00:00:02.2444910 for 1000000000 iterations.
Vector1 time was 00:00:04.4490656 for 1000000000 iterations.
Vector1Magic time was 00:00:02.2262701 for 1000000000 iterations.
Run Code Online (Sandbox Code Playgroud)
编译器/环境设置:操作系统:Windows 10 64位工具链:VS2017框架:.Net 4.6.2目标:任何CPU首选32位
如果将64位设置为目标,我们的结果更可预测,但比我们在32位目标上使用Vector1Magic看到的更糟糕:
Float time was 00:00:00.6800014 for 1000000000 iterations.
Vector1 time was 00:00:04.4572642 for 1000000000 iterations.
Vector1Magic time was 00:00:05.7806399 for 1000000000 iterations.
Run Code Online (Sandbox Code Playgroud)
对于真正的向导,我在这里包含了IL的转储:https://pastebin.com/sz2QLGEx
进一步调查表明,这似乎是特定于Windows运行时,因为单声道编译器生成相同的IL.
在单声道运行时,与原始浮点数相比,两种结构变体的性能大约低2倍.这与我们在.Net上看到的性能有很大不同.
这里发生了什么?
*注意这个问题最初包括一个有缺陷的基准测试过程(感谢Max Payne指出这一点),并且已经更新以更准确地反映时间.
jit 有一种称为“结构提升”的优化,它可以有效地用多个局部变量替换一个局部结构体或参数,每个局部变量对应一个结构体的字段。
然而,单个 struct-wrapped float 的结构提升被禁用。原因有点模糊,但大致如下:
因此,粗略地说,jit 优先考虑降低呼叫站点的成本,而不是提高使用该字段的地方的成本。有时(如您上面的情况,运营成本占主导地位)这不是正确的选择。
如您所见,如果您将结构体变大,则传递和返回结构体的规则会发生变化(现在通过引用返回传递)并且这会解除对提升的阻止。
在CoreCLR 源代码中,您可以在Compiler::lvaShouldPromoteStructVar.