gpt4 book ai didi

C# EMIT IL 性能问题

转载 作者:行者123 更新时间:2023-11-30 23:26:11 24 4
gpt4 key购买 nike

我正在开发一个引擎,我们可以在运行时动态复制大量属性。根据情况,我们可能会也可能不会修改属性值。它最初是用反射写的,但是由于性能问题,我们最近在 Reflection.Emit 中重新写了它。重写完成,性能明显好很多,但现在代码正在与手写的 C# 进行基准测试。显然,为了公平起见,用于基准测试的手写 C#IL 具有“相似的功能”(您很快就会明白我的意思) .

一些 IL 引擎已经被签署,因为它以优异的成绩通过了测试,并且与手写的 C# 几乎是 1:1 的。这告诉我:

  1. 调用动态方法没有开销

  2. 我们的总体概念和实现是正确的

  3. 基准测试是正确的

  4. IL 和手写的 C# 正在以完全相同的方式进行测试,因此没有有趣的 JIT 业务在进行(我别想)

我们期望 IL 比手写的稍微慢一些,但到目前为止情况并非如此。在长回合中它可能会慢几毫秒,但您可以在 IL 中使用快捷方式,这样有助于弥补差异。

在一种特殊情况下,它的速度要慢得多。慢 2 倍。

C# 中,您将拥有:

class Source
{
public string S1 { get; set; }
public int I1 { get; set; }
public int I2 { get; set; }
public double D1 { get; set; }
public double D2 { get; set; }
public double D3 { get; set; }
}

class Dest
{
public string S1 { get; set; }
public int I1 { get; set; }
public string I2 { get; set; }
public double D1 { get; set; }
public int D2 { get; set; }
public string D3 { get; set; }
}

static Dest Test(Source s)
{
Dest d = new Dest();

object o = s.D3;

if (o != null)
d.D3 = o.ToString();

return d;
}

这就是我所说的类似功能的意思。为了通用,当我们将属性复制到字符串时,我们首先将其装箱,然后调用 Object.ToString()。 native 地,值类型调用 ToString 不同,因此上面的代码是同类。

如果我注释掉 D3 copy/ToString 并取消注释其他 5 个属性,我将回到 1:1 与 C#.

你会注意到 I2int -> string,但出于某种原因,那个没有同样的问题与 double -> string 一样。我知道 double ToString() 通常更昂贵,但该费用也应显示在 C# 代码中,但事实并非如此。

我为 D3 副本发出的代码与我为 I2 副本发出的代码相同,为什么 D3 的开销很大复制?

编辑:

编译器发出:

IL_0000: newobj instance void ConsoleApplication3.Dest::.ctor()
IL_0005: ldarg.0
IL_0006: callvirt instance float64 ConsoleApplication3.Source::get_D3()
IL_000b: box [mscorlib]System.Double
IL_0010: stloc.0
IL_0011: dup
IL_0012: ldloc.0
IL_0013: brtrue.s IL_0018

IL_0015: ldnull
IL_0016: br.s IL_001e

IL_0018: ldloc.0
IL_0019: callvirt instance string [mscorlib]System.Object::ToString()

IL_001e: callvirt instance void ConsoleApplication3.Dest::set_D3(string)
IL_0023: ret

我的代码的这个特定部分不会为 Dest 对象发出新的,这是在其他地方完成的。如上面的 C# 所示,dup 正在复制 Dest 对象。

LocalBuilder localBuilderObject = generator.DeclareLocal(_typeOfObject);

Label labelNull = generator.DefineLabel();
Label labelNotNull = generator.DefineLabel();

generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Callvirt, miGetter);
generator.Emit(OpCodes.Box, typeSource);
generator.Emit(OpCodes.Stloc_S, localBuilderObject);
generator.Emit(OpCodes.Dup);
generator.Emit(OpCodes.Ldloc_S, localBuilderObject);
generator.Emit(OpCodes.Brtrue, labelNotNull);
generator.Emit(OpCodes.Ldnull);
generator.Emit(OpCodes.Br, labelNull);
generator.MarkLabel(labelNotNull);
generator.Emit(OpCodes.Ldloc_S, localBuilderObject);
generator.Emit(OpCodes.Callvirt, _miToString);
generator.MarkLabel(labelNull);
generator.Emit(OpCodes.Callvirt,miSetter);

正如我提到的,我将类型框起来,这样我就可以一般地调用 Object::ToString() 而不必担心值类型。 Ref 类型也经过这条路径。 C# 代码被设计成这样,但仍然需要 1/2 的时间???

我整个周末都在处理这个问题。进一步测试显示其他值类型是 1:1。 intlong 等。由于某种原因,double 导致了问题。

最佳答案

正如您在 C# 编译代码中所见,使用了快速本地访问指令:

IL_000b: box [mscorlib]System.Double
IL_0010: stloc.0
IL_0011: dup
IL_0012: ldloc.0
...
IL_0018: ldloc.0

相反,在您的 IL 生成的代码中,您使用 STLoc.sldloc.s,它们也采用本地索引的操作数.

还要确保缓存(如果 C# 运行速度只快两倍,则可能是缓存)为每个 Type 生成的方法。

关于C# EMIT IL 性能问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36961159/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com