Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

466
Vistas
¿Cómo acelerar la ejecución dinámica de C# (.NET) desde C++?

Tengo una pregunta avanzada y hasta ahora me ha llevado muchas horas intentar encontrar una solución que no utilice la reflexión.

El escenario es que tenemos una aplicación C++ que ejecuta nuestro propio lenguaje de secuencias de comandos interpretado y ese lenguaje de secuencias de comandos puede interactuar con C# (.NET), por ejemplo, para crear cuadros de diálogo de Windows Forms. Logramos esto mediante el uso de ICorRuntimeHost, una biblioteca auxiliar de C++/CLI y, en última instancia, reflexión para llamar a métodos, propiedades, etc.

He dado un paso atrás y estoy tratando de ver cómo puedo ejecutar el código de la manera más eficiente en C# sin tener que escribir las llamadas al método directamente. Estoy usando BenchmarkDotNet para comparar el código. También tengo la intención de usar solo C ++/CLI sin ICorRuntimeHost, lo que significaría que el código C # a continuación debería escribirse en C ++/CLI en última instancia.

El código de referencia es el siguiente:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; namespace DotNetExecutorTest { public class BenchmarksIndexOf { private readonly int NumberOfCalls = 1000; [Benchmark] public long Reflection() { long sum = 0; for (int i = 0; i < NumberOfCalls; i++) { sum += (int)(typeof(string).InvokeMember("IndexOf", System.Reflection.BindingFlags.InvokeMethod, null, "Hello", new object[] { 'H' })); } return sum; } [Benchmark] public long TypedOpenDelegate() { long sum = 0; var methodInfo = typeof(string).GetMethod("IndexOf", new Type[] { typeof(char) }); Func<string, char, int> func = (Func<string, char, int>)Delegate.CreateDelegate(typeof(Func<string, char, int>), methodInfo); for (int i = 0; i < NumberOfCalls; i++) { sum += func("Hello", 'H'); } return sum; } [Benchmark] public long NativeCSharp() { long sum = 0; for (int i = 0; i < NumberOfCalls; i++) { sum += "Hello".IndexOf('H'); } return sum; } [Benchmark] public long FastExecutorV1() { long sum = 0; for (int i = 0; i < NumberOfCalls; i++) { sum += (int)(DotNetExecutorFastV1.ExecuteMethod(typeof(string), "IndexOf", false, "Hello", new Type[] { typeof(char) }, new object[] { 'H' })); } return sum; } [Benchmark] public long FastExecutorV2() { long sum = 0; for (int i = 0; i < NumberOfCalls; i++) { sum += (int)(DotNetExecutorFastV2.ExecuteMethod(typeof(string), "IndexOf", false, "Hello", new Type[] { typeof(char) }, new object[] { 'H' })); } return sum; } [Benchmark] public long SlowExecutor() { long sum = 0; for (int i = 0; i < NumberOfCalls; i++) { sum += (int)(DotNetExecutorSlow.ExecuteMethod(typeof(string), "IndexOf", false, "Hello", new Type[] { typeof(char) }, new object[] { 'H' })); } return sum; } [Benchmark] public long MethodInfoExecutor() { long sum = 0; for (int i = 0; i < NumberOfCalls; i++) { sum += (int)(DotNetExecutorMethodInfo.ExecuteMethod(typeof(string), "IndexOf", false, "Hello", new Type[] { typeof(char) }, new object[] { 'H' })); } return sum; } } }

Y los resultados son:

Método Significar Error Desv.estándar Mediana
Reflexión 1,106.853 us 53.9937 nosotros 159.2016 nosotros 1,000.888 us
TypedOpenDelegate 6.937 us 0.0802 nosotros 0.0626 nosotros 6.942 us
NativeCSharp 3.328 us 0.0430 nosotros 0.0381 nosotros 3.312 nosotros
FastExecutorV1 208.947 nosotros 1.1720 nosotros 1.0390 nosotros 208.974 nosotros
FastExecutorV2 203.321 nosotros 3.9627 nosotros 4.2400 us 202.109 nosotros
Ejecutor lento 1,184.866 us 7.4664 nosotros 6.2348 nosotros 1,182.970 us
MethodInfoExecutor 401.875 nosotros 2.3886 nosotros 2.1175 nosotros 402.048 nosotros

La explicación rápida de los métodos es la siguiente:

  • Reflexión: Reflexión pura y simple, es lo que estamos usando actualmente.
  • TypedOpenDelegate: la solución más prometedora a primera vista, sin embargo, debido a que todos nuestros miembros de .NET se invocan dinámicamente, no podemos usar el tipo estático de Func<string, char, int> y ahí parece ser exactamente donde se gana el rendimiento.
  • NativeCSharp: simplemente C#.
  • FastExecutorV1: la ejecución personalizada basada en árboles de expresión que almacenan instancias de Func<object, object[], object> , según delegados abiertos, se explicará más adelante.
  • FastExecutorV1V La ejecución personalizada basada en árboles de expresión que almacenan instancias de Func<object, object[], object> , en función de llamadas a métodos, se explicará más adelante.
  • SlowExecutor: al almacenar delegados abiertos de los objetos MethodInfo y luego llamar a DynamicInvoke en ellos, el rendimiento es incluso peor que solo usar la reflexión.
  • MethodInfoExecutor: rendimiento prometedor simplemente llamando a MethodInfo.Invoke, pero no el mejor rendimiento.

Entonces, primero puedo explicar el MethodInfoExecutor, en realidad es bastante simple:

 public class DotNetExecutorMethodInfo { private static readonly Dictionary<DotNetKey, MethodInfo> methodInfos = new Dictionary<DotNetKey, MethodInfo>(); public static void ResetDictionary() { methodInfos.Clear(); } public static object ExecuteMethod(Type type, string name, bool isStatic, object instance, Type[] parameterTypes, object[] parameters) { var dotNetKey = new DotNetKey { type = type, dotNetType = DotNetType.Method, name = name, isStatic = isStatic, parameterTypes = parameterTypes }; if (!methodInfos.TryGetValue(dotNetKey, out var methodInfo)) { methodInfo = type.GetMethod(name, BindingFlags.Public | (isStatic ? BindingFlags.Static : BindingFlags.Instance), null, parameterTypes, null); methodInfos[dotNetKey] = methodInfo; } return methodInfo.Invoke(isStatic ? null : instance, parameters); } }

Para completar la fuente DotNetKey:

 public struct DotNetKey : IEquatable<DotNetKey> { public Type type; public DotNetType dotNetType; public bool isStatic; public string name; public Type[] parameterTypes; public bool Equals(DotNetKey other) { if (type != other.type) { return false; } if (dotNetType != other.dotNetType) { return false; } if (isStatic != other.isStatic) { return false; } if (name != other.name) { return false; } if (!Enumerable.SequenceEqual(parameterTypes, other.parameterTypes)) { return false; } return true; } public override int GetHashCode() { var hash = 17 * (type, dotNetType, isStatic, name).GetHashCode(); foreach (var type in parameterTypes) { hash = hash * 23 + type.GetHashCode(); } return hash; } }

Y DotNetType:

 public enum DotNetType { Method }

Y luego tenemos nuestra solución compleja que es la siguiente, incluso tiene dos versiones con el mismo rendimiento.

Versión 1, la versión original con delegados abiertos:

 public class DotNetExecutorFastV1 { private static readonly Dictionary<DotNetKey, Func<object, object[], object>> funcs = new Dictionary<DotNetKey, Func<object, object[], object>>(); public static object ExecuteMethod(Type type, string name, bool isStatic, object instance, Type[] parameterTypes, object[] parameters) { var func = FindFunc(type, DotNetType.Method, name, isStatic, parameterTypes); return func(instance, parameters); } private static Func<object, object[], object> FindFunc(Type type, DotNetType dotNetType, string name, bool isStatic, Type[] parameterTypes) { var dotNetKey = new DotNetKey { type = type, dotNetType = dotNetType, name = name, isStatic = isStatic, parameterTypes = parameterTypes }; if (funcs.TryGetValue(dotNetKey, out var func)) { return func; } else { var newFunc = CreateMethodFunc(type, name, isStatic, parameterTypes); funcs.Add(dotNetKey, newFunc); return newFunc; } } private static Func<object, object[], object> CreateMethodFunc(Type type, string name, bool isStatic, Type[] parameterTypes) { var methodInfo = type.GetMethod(name, BindingFlags.Public | (isStatic ? BindingFlags.Static : BindingFlags.Instance), null, parameterTypes, null); var openDelegate = Delegate.CreateDelegate(OpenDelegateType(type, isStatic, methodInfo, parameterTypes), methodInfo); var instance = Expression.Parameter(typeof(object), "instance"); var parameterArray = Expression.Parameter(typeof(object[]), "parameterArray"); var parameterExpressions = parameterTypes.Select((parameterType, index) => Expression.Convert(Expression.ArrayAccess(parameterArray, Expression.Constant(index)), parameterType)); var parameters = new List<Expression>(); if (isStatic) { parameters.AddRange(parameterExpressions); } else { parameters.Add(Expression.Convert(instance, isStatic ? typeof(object) : type)); parameters.AddRange(parameterExpressions); } var invokeExpression = Expression.Invoke( Expression.Constant(openDelegate), parameters ); Expression bodyExpression; if (methodInfo.ReturnType == typeof(void)) { bodyExpression = Expression.Block( invokeExpression, Expression.Constant(null) ); } else { bodyExpression = Expression.Convert(invokeExpression, typeof(object)); } var lambda = Expression.Lambda<Func<object, object[], object>>( bodyExpression, instance, parameterArray ); return lambda.Compile(); } private static Type OpenDelegateType(Type type, bool isStatic, MethodInfo methodInfo, Type[] parameterTypes) { var types = new List<Type>(); if (!isStatic) { types.Add(type); // type of instance class } types.AddRange(parameterTypes); // parameters if (methodInfo.ReturnType != typeof(void)) { types.Add(methodInfo.ReturnType); // return type } if (methodInfo.ReturnType == typeof(void)) { return Expression.GetActionType(types.ToArray()); } else { return Expression.GetFuncType(types.ToArray()); } } }

Y la versión 2, que usa un CreateMethodFunc diferente:

 public class DotNetExecutorFastV2 { private static readonly Dictionary<DotNetKey, Func<object, object[], object>> funcs = new Dictionary<DotNetKey, Func<object, object[], object>>(); public static object ExecuteMethod(Type type, string name, bool isStatic, object instance, Type[] parameterTypes, object[] parameters) { var func = FindFunc(type, DotNetType.Method, name, isStatic, parameterTypes); return func(instance, parameters); } private static Func<object, object[], object> FindFunc(Type type, DotNetType dotNetType, string name, bool isStatic, Type[] parameterTypes) { var dotNetKey = new DotNetKey { type = type, dotNetType = dotNetType, name = name, isStatic = isStatic, parameterTypes = parameterTypes }; if (funcs.TryGetValue(dotNetKey, out var func)) { return func; } else { var newFunc = CreateMethodFunc(type, name, isStatic, parameterTypes); funcs.Add(dotNetKey, newFunc); return newFunc; } } private static Func<object, object[], object> CreateMethodFunc(Type type, string name, bool isStatic, Type[] parameterTypes) { var methodInfo = type.GetMethod(name, BindingFlags.Public | (isStatic ? BindingFlags.Static : BindingFlags.Instance), null, parameterTypes, null); var instance = Expression.Parameter(typeof(object), "instance"); var parameterArray = Expression.Parameter(typeof(object[]), "parameterArray"); var parameterExpressions = parameterTypes.Select((parameterType, index) => Expression.Convert(Expression.ArrayAccess(parameterArray, Expression.Constant(index)), parameterType)); Expression callExpression; if (isStatic) { callExpression = Expression.Call( methodInfo, parameterExpressions ); } else { callExpression = Expression.Call( Expression.Convert(instance, type), methodInfo, parameterExpressions ); } Expression bodyExpression; if (methodInfo.ReturnType == typeof(void)) { bodyExpression = Expression.Block( callExpression, Expression.Constant(null) ); } else { bodyExpression = Expression.Convert(callExpression, typeof(object)); } var lambda = Expression.Lambda<Func<object, object[], object>>( bodyExpression, instance, parameterArray ); return lambda.Compile(); } }

Básicamente, lo que estoy haciendo es intentar desbloquear el rendimiento de los delegados fuertemente tipados escribiéndolos como Func<object, object[], object> pero en algún lugar sigo perdiendo rendimiento, no estoy seguro de si eso sucede debido a la llamada de método adicional o por el acceso a la matriz o por otra cosa.

Así que la pregunta sigue siendo: ¿Cómo aceleraría esto? Idealmente, obtendría un rendimiento cercano al nativo y debe ser completamente dinámico, ya que el sitio de llamadas de C ++ debería poder invocar cualquier método C # (.NET).

over 4 years ago · Santiago Trujillo
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda