Pregunta

Tengo la intención de crear un servicio web que realice una gran cantidad de cálculos especificados manualmente lo más rápido posible, y he estado explorando el uso de DLR.

Lo siento si esto es largo, pero no dude en escatimar y obtener la esencia general.

He estado usando la biblioteca Ironpython, ya que hace que los cálculos sean muy fáciles de especificar. My Works Laptop ofrece un rendimiento de aproximadamente 400,000 cálculos por segundo haciendo lo siguiente:

ScriptEngine py = Python.CreateEngine();
ScriptScope pys = py.CreateScope();

ScriptSource src = py.CreateScriptSourceFromString(@"
def result():
    res = [None]*1000000
    for i in range(0, 1000000):
        res[i] = b.GetValue() + 1
    return res
result()
");

CompiledCode compiled = src.Compile();
pys.SetVariable("b", new DynamicValue());

long start = DateTime.Now.Ticks;
var res = compiled.Execute(pys);
long end = DateTime.Now.Ticks;

Console.WriteLine("...Finished. Sample data:");

for (int i = 0; i < 10; i++)
{
    Console.WriteLine(res[i]);
}

Console.WriteLine("Took " + (end - start) / 10000 + "ms to run 1000000 times.");

Donde DynamicValue es una clase que devuelve números aleatorios de una matriz preconstruida (sembrada y construida en tiempo de ejecución).

Cuando creo una clase DLR para hacer lo mismo, obtengo un rendimiento mucho mayor (~ 10,000,000 de cálculos por segundo). La clase es la siguiente:

class DynamicCalc : IDynamicMetaObjectProvider
{
    DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter)
    {
        return new DynamicCalcMetaObject(parameter, this);
    }

    private class DynamicCalcMetaObject : DynamicMetaObject
    {
        internal DynamicCalcMetaObject(Expression parameter, DynamicCalc value) : base(parameter, BindingRestrictions.Empty, value) { }

        public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
        {
            Expression Add = Expression.Convert(Expression.Add(args[0].Expression, args[1].Expression), typeof(System.Object));
            DynamicMetaObject methodInfo = new DynamicMetaObject(Expression.Block(Add), BindingRestrictions.GetTypeRestriction(Expression, LimitType));
            return methodInfo;
        }
    }
}

y se llama/se prueba de la misma manera haciendo lo siguiente:

dynamic obj = new DynamicCalc();
long t1 = DateTime.Now.Ticks;
for (int i = 0; i < 10000000; i++)
{
    results[i] = obj.Add(ar1[i], ar2[i]);
}
long t2 = DateTime.Now.Ticks;

Donde AR1 y AR2 son matrices pre-construidas de tiempo de ejecución de números aleatorios.

La velocidad es excelente de esta manera, pero no es fácil especificar el cálculo. Básicamente, estaría buscando crear mi propio Lexer & Parser, mientras que Ironpython tiene todo lo que ya necesito.

Hubiera pensado que podría obtener un rendimiento mucho mejor de Ironpython, ya que se implementa en la parte superior del DLR, y podría hacerlo mejor de lo que estoy obteniendo.

¿Mi ejemplo es hacer el mejor uso del motor Ironpython? ¿Es posible obtener un rendimiento significativamente mejor?

(Editar) Igual que el primer ejemplo pero con el bucle en C#, configurar variables y llamar a la función Python:

ScriptSource src = py.CreateScriptSourceFromString(@"b + 1");

CompiledCode compiled = src.Compile();

double[] res = new double[1000000];

for(int i=0; i<1000000; i++)
{
    pys.SetVariable("b", args1[i]);
    res[i] = compiled.Execute(pys);
}

Donde Pys es un scriptscope de PY, y Args1 es una matriz preconstruida de dobles aleatorios. Este ejemplo se ejecuta más lento que ejecutar el bucle en el código de Python y pasar en todas las matrices.

¿Fue útil?

Solución

El comentario de Delnan te lleva a algunos de los problemas aquí. Pero me volveré específico sobre cuáles son las diferencias aquí. En la versión C#, ha eliminado una cantidad significativa de las llamadas dinámicas que tiene en la versión de Python. Para empezar, su bucle se escribe en INT y parece que AR1 y AR2 son matrices fuertemente escrita. Entonces, en la versión C#, las únicas operaciones dinámicas que tiene son la llamada a obj.add (que es 1 operación en C#) y potencialmente la asignación a los resultados si no se escribe al objeto que parece poco probable. También tenga en cuenta que todo este código está libre de bloqueos.

En la versión de Python, primero tiene la asignación de la lista: esto también parece estar durante su temporizador donde en C# no parece que sea. Luego tienes la llamada dinámica para el rango, afortunadamente eso solo sucede una vez. Pero eso nuevamente crea una lista gigantesca en la memoria: la sugerencia de Delnan de Xrange es una mejora aquí. Luego tienes el contador de bucle I que se está encerrando a un objeto para cada iteración a través del bucle. Luego tiene el llamado a B.getValue () que en realidad es 2 Invocatiosn dinámico: primero obtenga un miembro para obtener el método "GetValue" y luego una invocatoria en ese objeto de método límite. Esto nuevamente está creando un nuevo objeto para cada iteración del bucle. Luego tiene el resultado de B.getValue () que puede ser otro valor más que esté encerrado en cada iteración. Luego agrega 1 a ese resultado y tiene otra operación de boxeo en cada iteración. Finalmente, almacena esto en su lista, que es otra operación dinámica: creo que esta operación final debe bloquearse para garantizar que la lista permanezca consistente (nuevamente, la sugerencia de Delnan de usar una comprensión de la lista mejora esto).

Entonces, en resumen durante el bucle tenemos:

                            C#       IronPython
Dynamic Operations           1           4
Allocations                  1           4
Locks Acquired               0           1

Entonces, básicamente, el comportamiento dinámico de Python tiene un costo frente a C#. Si quieres lo mejor de ambos mundos, puedes intentar equilibrar lo que haces en C# vs lo que haces en Python. Por ejemplo, puede escribir el bucle en C# y hacer que llame a un delegado que es una función de Python (puede hacer alcance. GetVariable> Para obtener una función del alcance como delegado). También podría considerar asignar una matriz de .NET para obtener los resultados si realmente necesita obtener hasta el último rendimiento, ya que puede reducir el conjunto de trabajo y la copia de GC al no mantener un montón de valores en caja.

Para hacer el delegado, podría hacer que el usuario escriba:

def computeValue(value):
    return value + 1

Luego, en el código C#, harías:

CompiledCode compiled = src.Compile();
compiled.Execute(pys);
var computer = pys.GetVariable<Func<object,object>>("computeValue");

Ahora puedes hacer:

for (int i = 0; i < 10000000; i++)
{
    results[i] = computer(i);
}

Otros consejos

Si se preocupa por la velocidad de cálculo, ¿es mejor ver nivel bajo especificación de cálculo? Python y C# son idiomas de alto nivel, y su tiempo de ejecución de implementación puede pasar mucho tiempo para un trabajo encubierto.

Mire en esta biblioteca de envoltorio LLVM: http://www.llvmpy.org

  • Instálelo con: pip install llvmpy ply
  • o en Debian Linux: apt install python-llvmpy python-ply

Todavía necesita escribir un pequeño compilador (puede usar Biblioteca), y enléelo con llamadas LLVM JIT (ver motor de ejecución de LLVM), pero este enfoque puede ser más efectivo (el código generado mucho más cerca del código de la CPU real), y multiplataforma en comparación con la cárcel .net.

LLVM tiene listo para usar optimización del compilador Infraestructura, que incluye muchos módulos de etapa de optimizador y gran comunidad de usuarios y desarrolladores.

También mira aquí: http://gmarkall.github.io/tutorials/llvm-cauldron-2016

PD: Si te interesa, puedo ayudarte con un compilador, contribuyendo al manual de mi proyecto en paralelo. Pero no será Jumpstart, este tema también es nuevo para mí.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top