IL优化尝试导致执行速度变慢

Mar*_*sen 3 c# optimization il

考虑这更多是一个学术问题,而不是实际问题.

在重新创建一个轮子,即编写一个迷你ORM /类型映射器时,我发出了一些IL来将对象的属性转换为添加到SqlCommand的SqlParameters.

我的第一次尝试基本上是编写C#代码并将其转换为IL 1:1,从而产生以下代码(完美无缺地工作).请注意,注释表示运行以下发出的IL后的堆栈:

int paramLocIndex = localIndex++;

 // Get the type handler [typeHandler]
il.Emit(OpCodes.Call, ClrTypeHandlers[prop.PropertyType].GetType().GetMethod("GetTypeHandler", BindingFlags.NonPublic | BindingFlags.Static));

// Load the object [typeHandler, object]
il.Emit(OpCodes.Ldloc_0);

// Get the property value [typeHandler, value]
il.Emit(OpCodes.Call, prop.GetMethod);

// Box the value [typeHandler, boxedValue]
il.Emit(OpCodes.Box, prop.PropertyType);

// Let the type handler create the param [param]
il.Emit(OpCodes.Callvirt, typeof(SqlTypeHandler).GetMethod("CreateParamFromValue", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(object) }, null));

// Store the parameter as a variable []
il.DeclareLocal(typeof(SqlParameter));
il.Emit(OpCodes.Stloc, paramLocIndex);

// Load the parameter again [param]
il.Emit(OpCodes.Ldloc, paramLocIndex);

// Load the parameter name [param, paramName]
il.Emit(OpCodes.Ldstr, paramName);

// Set the parameter name []
il.Emit(OpCodes.Call, typeof(SqlParameter).GetMethod("set_ParameterName"));

// Load the command [cmd]
il.Emit(OpCodes.Ldarg_0);

// Load the parameter collection [paramCollection]
il.Emit(OpCodes.Call, typeof(SqlCommand).GetMethod("get_Parameters"));

// Load the parameter [paramCollection, param]
il.Emit(OpCodes.Ldloc, paramLocIndex);

// Add the parameter to the collection [param]
il.Emit(OpCodes.Call, typeof(SqlParameterCollection).GetMethod("Add", new[] { typeof(SqlParameter) }));

// Get rid of the added parameter, as returned by SqlParameterCollection.Add []
il.Emit(OpCodes.Pop);
Run Code Online (Sandbox Code Playgroud)

虽然上述工作,我想尝试优化它.这导致了以下代码,它也可以完美地运行:

// Load the command [cmd]
il.Emit(OpCodes.Ldarg_0);

// Load the parameter collection [paramCollection]
il.Emit(OpCodes.Call, typeof(SqlCommand).GetMethod("get_Parameters"));

// Get the type handler [paramCollection, typeHandler]
il.Emit(OpCodes.Call, ClrTypeHandlers[prop.PropertyType].GetType().GetMethod("GetTypeHandler", BindingFlags.NonPublic | BindingFlags.Static));

// Load the object [paramCollection, typeHandler, object]
il.Emit(OpCodes.Ldloc_0);

// Get the property value [paramCollection, typeHandler, value]
il.Emit(OpCodes.Call, prop.GetMethod);

// Box the value [paramCollection, typeHandler, boxedValue]
il.Emit(OpCodes.Box, prop.PropertyType);

// Let the type handler create the param [paramCollection, param]
il.Emit(OpCodes.Callvirt, typeof(SqlTypeHandler).GetMethod("CreateParamFromValue", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(object) }, null));

// Add the parameter to the collection [param]
il.Emit(OpCodes.Call, typeof(SqlParameterCollection).GetMethod("Add", new[] { typeof(SqlParameter) }));

// Load the parameter name [param, paramName]
il.Emit(OpCodes.Ldstr, paramName);

// Set the parameter name []
il.Emit(OpCodes.Call, typeof(SqlParameter).GetMethod("set_ParameterName"));
Run Code Online (Sandbox Code Playgroud)

虽然优化的尝试导致更少的指令,避免变量等,但它仍然运行得更慢.第一次尝试以21500个刻度运行,而"优化"版本以75000个刻度运行.

这两个都很快,但我很好奇为什么第一个运行得那么快.我知道从IL到机器代码的第二级编译 - 答案可能在于IL编译器执行的优化.问题是,如果我想进一步探讨这个问题,我有哪些选择?第二次IL尝试运行缓慢的任何明显原因?

Dav*_*wen 5

我不认为它与IL编译优化有任何关系,但与框架处理添加到SqlParameterCollection的更无聊的方式有关.

在第一个(更长)方法中,您:

  • 创建SqlParameter
  • 分配其属性名称
  • 将其添加到集合中

在优化版中,您:

  • 创建SqlParameter
  • 将其添加到集合中
  • 分配其属性名称

请注意区别?最后两个步骤是转换它似乎相当学术,但如果你启动反射器并查看SqlParameterCollection源你偶然发现这段代码:

private void Validate(int index, object value)
{
    /* snip */
    if (((SqlParameter) value).ParameterName.Length == 0)
    {
        string str;
        index = 1;
        do
        {
            str = "Parameter" + index.ToString(CultureInfo.CurrentCulture);
            index++;
        }
        while (-1 != this.IndexOf(str));
        ((SqlParameter) value).ParameterName = str;
    }
}
Run Code Online (Sandbox Code Playgroud)

当您将其添加到没有参数名称的集合时,会为其生成一个,并且此过程涉及到列表中的分配,循环和查找,这应该可以轻松地说明您注意到的额外运行时间.

如果您试图避免创建局部变量,SqlTypeHandler似乎是您控制的类?因此,您可以将参数名称作为参数添加到CreateParamFromValue方法并在其中进行分配,因此当它添加到集合时,它已经具有名称.