Domanda

Ho intenzione di creare un servizio Web che esegue un gran numero di calcoli specificati manualmente più velocemente possibile e ho esplorato l'uso di DLR.

Scusa se questo è lungo ma sentiti libero di sfogliare e ottenere la GIST generale.

Ho usato la libreria IronPython in quanto rende i calcoli molto facili da specificare. Il mio laptop Works offre un'esibizione di circa 400.000 calcoli al secondo facendo quanto segue:

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.");

Dove DynamicValue è una classe che restituisce numeri casuali da un array pre-costruito (seminato e costruito in fase di esecuzione).

Quando creo una classe DLR per fare la stessa cosa, ottengo prestazioni molto più elevate (~ 10.000.000 di calcoli al secondo). La classe è la seguente:

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;
        }
    }
}

ed è chiamato/testato allo stesso modo facendo quanto segue:

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;

Dove AR1 e AR2 sono pre-costruzioni, array di numeri casuali seminati.

La velocità è eccezionale in questo modo, ma non è facile specificare il calcolo. Fondamentalmente stavo cercando di creare il mio Lexer & Parser, mentre Ironpython ha tutto ciò di cui ho bisogno già lì.

Avrei pensato di poter ottenere prestazioni molto migliori da IronPython poiché è implementato in cima al DLR e potrei fare con meglio di quello che sto ottenendo.

Il mio esempio sta facendo al meglio il motore IronPython? È possibile ottenere prestazioni significativamente migliori da esso?

(Modifica) Lo stesso del primo esempio ma con il ciclo in C#, impostando le variabili e chiama la funzione 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);
}

Dove Pys è un screptscope di PY e Args1 è una serie pre-costruita di doppi casuali. Questo esempio esegue più lento rispetto all'esecuzione del ciclo nel codice Python e passando in tutti gli array.

È stato utile?

Soluzione

Il commento di Delnan ti porta ad alcuni dei problemi qui. Ma diventerò solo specifico su quali sono le differenze qui. Nella versione C# hai eliminato una quantità significativa delle chiamate dinamiche che hai nella versione Python. Per cominciare il tuo loop è digitato su INT e sembra che AR1 e AR2 siano array fortemente tipizzati. Quindi nella versione C# le uniche operazioni dinamiche che hai sono la chiamata a OBJ.Add (che è 1 operazione in C#) e potenzialmente l'assegnazione ai risultati se non è digitato su oggetto che sembra improbabile. Nota anche che tutto questo codice è gratuito.

Nella versione di Python hai prima l'allocazione dell'elenco - questo sembra anche essere durante il tuo timer dove come in C# non sembra. Quindi hai la chiamata dinamica alla gamma, per fortuna che accade solo una volta. Ma questo crea di nuovo un elenco gigantesco in memoria: il suggerimento di Xrange di Delnan è un miglioramento qui. Quindi hai il contatore del loop I che viene inscatolato su un oggetto per ogni iterazione attraverso il loop. Quindi hai la chiamata a B.GetValue () che in realtà è 2 invocatiosn dinamico - prima un membro Ottieni per ottenere il metodo "getValue" e quindi un invoca su quell'oggetto metodo legato. Questo sta di nuovo creando un nuovo oggetto per ogni iterazione del loop. Quindi hai il risultato di B.GetValue () che potrebbe essere un altro valore che è inscatolato su ogni iterazione. Quindi aggiungi 1 a quel risultato e hai un'altra operazione di boxe su ogni iterazione. Infine lo memorizzi nel tuo elenco che è ancora un'altra operazione dinamica: penso che questa operazione finale debba bloccare per garantire che l'elenco rimanga coerente (di nuovo, il suggerimento di Delnan di utilizzare una comprensione dell'elenco migliora questo).

Quindi in sintesi durante il ciclo abbiamo:

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

Quindi fondamentalmente il comportamento dinamico di Python ha un costo vs C#. Se vuoi il meglio di entrambi i mondi, puoi provare a bilanciare ciò che fai in C# vs quello che fai in Python. Ad esempio, è possibile scrivere il ciclo in C# e fargli chiamare un delegato che è una funzione Python (puoi fare l'ambito.getvariable> per ottenere una funzione fuori dall'ambito come delegato). Potresti anche considerare di allocare un array .NET per i risultati se è necessario ottenere ogni ultimo bit di prestazioni in quanto potrebbe ridurre il set di lavoro e la copia GC non mantenendo un mucchio di valori in scatola.

Per fare il delegato potresti far scrivere all'utente:

def computeValue(value):
    return value + 1

Quindi nel codice C# che faresti:

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

Ora puoi fare:

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

Altri suggerimenti

Se sei preoccupato per la velocità di calcolo, è meglio guardare basso livello Specifiche al calcolo? Python e C# sono lingue di alto livello e il suo runtime di implementazione può trascorrere molto tempo per il lavoro sotto copertura.

Guarda su questa libreria di avvolgimento LLVM: http://www.llvmpy.org

  • Installalo usando: pip install llvmpy ply
  • o su Debian Linux: apt install python-llvmpy python-ply

Devi ancora scrivere un piccolo compilatore (puoi usare Biblioteca di Ply) e legalo con le chiamate JIT LLVM (vedi motore di esecuzione LLVM), ma questo approccio può essere più efficace (codice generato molto più vicino al codice CPU reale) e multi piattaforma Rispetto alla prigione .NET.

LLVM è pronto per l'uso Ottimizzazione del compilatore infrastruttura, tra cui molti moduli di fase di ottimizzatore e grandi utenti e comunità di sviluppatori.

Guarda anche qui: http://gmarkall.github.io/tutorials/llvm-cauldron-2016

PS: se ti interessa, posso aiutarti con un compilatore, contribuendo al manuale del mio progetto in parallelo. Ma non sarà di partenza, questo tema è nuovo per me.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top