Check alle échte Black Friday-deals Ook zo moe van nepaanbiedingen? Wij laten alleen échte deals zien

[C#] Dubbele types door runtime compilation

Pagina: 1
Acties:

  • Gimmeabrake
  • Registratie: December 2008
  • Laatst online: 19-11 21:37
Voor een hobbyproject ben ik bezig met runtime compilation dmv de CSharpCodeProvider class in het .Net framework. Dit voor een high-performance parser voor wiskundige formules (dus bijv. "x^2+x+1"). De run-time compiled code verhoogt in vele gevallen de snelheid van een herberekening (als bijv. een variable zoals "x" verandert) met tientallen procenten ten opzichte van de oorspronkelijk gegenereerde expression tree.

De class die ik at runtime compileer extend een abstract class. Ik gebruik hiervoor een template file. Dwz, gegeven een
C#:
1
abstract class Node
genereert het programma een
C#:
1
class CompiledNode : Node
met daarin gedeeltelijk dynamisch gegenereerde content.

De CompiledNode werkt als een tierelier, alleen vroeg ik mij af wat er zou gebeuren als ik een tweede CompiledNode compileer. Er bestaan op dat moment immers at runtime immers 2 types met dezelfde naam in dezelfde namespace, maar een verschillende implementatie. Nieuwsgierig schreef ik snel wat testcode om een tweede CompiledNode te genereren met een andere formule erachter en verhip, het werkt gewoon. Ik heb even wat keywords op google zitten intypen, maar ik vind hier geen informatie over. Aangezien ik niet de meest technische informaticus ben (lees: ik weet niet zo heel veel van compilers, intermediate languages en dergelijke), vraag ik mij af of een medetweakers mij kan uitleggen waarom dit goed gaat?

Dus, waarom kun je door middel van run-time compilation een type met exact dezelfde naam in exact dezelfde namespace twee keer declareren met verschillende implementaties zonder dat C# over zijn nek gaat?

  • jip_86
  • Registratie: Juli 2004
  • Laatst online: 23-11 16:07
Maar is het ook bruikbaar? Uit onderstaande lijkt dit niet zo te zijn.

stackoverflow.com/questions/13272877/csharpcodeprovider-and-resolution-of-dynamic-assemblies

  • Gimmeabrake
  • Registratie: December 2008
  • Laatst online: 19-11 21:37
Zoals ik al zei, het is bruikbaar. In tegenstelling tot de code achter door jou genoemde link wordt er bij mij na de compilatie meteen een object geïnitialized in plaats van het later op te halen aan de hand van de naam met een regel zoals de volgende (quote uit je link):
C#:
1
_compilerResults.CompiledAssembly.GetType("ExMod.Engine.ScriptHost");


Daardoor voorkom ik dat het foute type wordt opgehaald. Tevens is het in mijn setup niet wenselijk om later nog eens een nieuwe instance van een eerder gecompileerde class te initializen. Mijn vraag blijft echter bestaan: waarom is dit mogelijk? Hoe onderscheidt C# de gecompileerde classes?

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
Kun je met een paar regels (demonstratie) code tonen wat je precies doet? De essentie ervan dus? Want ik heb een vermoeden dat 't of met scoping te maken heeft, of zelfs dat je twee verschillende instances van de CSharpCodeProvider gebruikt ofzo wat natuurlijk al een hoop zou verklaren. Daarbij,en dit is giswerk want zo "diep" ken ik de .Net runtime niet, het zou me niets verbazen als je een CompiledNode en CompiledNode`1 hebt ofzo waarbij, toevallig(?), gewoon de laatste resolved ofzo. Intern bestaan die namen natuurlijk helemaal niet; dat is meer "meta data" t.b.v. reflectie e.d; als je dus een referentie ophaalt met GetType krijg je gewoon een "pointer" naar die laatste gecompileerde versie. Denk ik. Gok ik :P

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


  • Gimmeabrake
  • Registratie: December 2008
  • Laatst online: 19-11 21:37
Een voorbeeld waaruit ik concludeer dat het schijnt te werken.
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Variable x = environment.GetVariable("x");

Node normalNode = factory.ParseExpression("3*x");
Node compiledNode = compileFactory.CompileNode(normalNode);
normalNode.Dispose();

Node normalNode2 = factory.ParseExpression("x-3");
Node compiledNode2 = compileFactory.CompileNode(normalNode2);
normalNode2.Dispose();
            
Console.WriteLine("\nset x = 3");
x.Value = 3;
Console.WriteLine("\t3*x = {0}", compiledNode.Value);
Console.WriteLine("\tx-3 = {0}", compiledNode2.Value);

Console.WriteLine("\nset x = 5.5");
x.Value = 5.5;
Console.WriteLine("\t3*x = {0}", compiledNode.Value);
Console.WriteLine("\tx-3 = {0}", compiledNode2.Value);
geeft als resultaat
code:
1
2
3
4
5
6
7
set x = 3
        3*x = 9
        x-3 = 0

set x = 5.5
        3*x = 16,5
        x-3 = 2,5


Dan de compilatiecode zelf, omdat er gesuggereerd wordt dat ik meerdere providers gebruik lijkt me dit allemaal relevante code. Sorry voor de lange post. :P
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    public class CompileNodeFactory
    {
        CSharpCodeProvider compiler;
        CompilerParameters compilerParameters;
        [...knip...]
        public CompileNodeFactory(MathieEnvironment environment)
        {
            //[knipperdeknip, init compiler enzo]
        }
        
        public Node CompileNode(Node node)
        {
            [... knip...]
            CompileNodeTemplate template = new CompileNodeTemplate(node.ToCSharp(), variables);
            string code = template.TransformText();

            CompilerResults results = compiler.CompileAssemblyFromSource(compilerParameters, code);
            if (results.Errors.Count == 0)
            {
                Assembly compiledAssembly = results.CompiledAssembly;
                foreach (Type type in compiledAssembly.GetTypes())
                {
                    if (type.IsSubclassOf(typeof(Node)))
                    {
                        object[] constructorParameters = new object[] { node.Expression, variableDictionary };
                        return ((Node)Activator.CreateInstance(type, constructorParameters));
                    }
                }
            }
            [... knip ...]
        }
    }
RobIII schreef op maandag 28 januari 2013 @ 21:26:
Intern bestaan die namen natuurlijk helemaal niet; dat is meer "meta data" t.b.v. reflectie e.d; als je dus een referentie ophaalt met GetType krijg je gewoon een "pointer" naar die laatste gecompileerde versie. Denk ik. Gok ik :P
*Zoiets* is ook mijn gok :+ maar ik ben wel nieuwsgierig naar een exacte uitleg, vandaar de topic. :)

  • RobertMe
  • Registratie: Maart 2009
  • Laatst online: 06:35
Je compileert (logischerwijs) naar twee verschillende assemblies. Daardoor is de assembly fully qualified name anders (deze is Namespace.Type, Assembly, Version=versie, nog wat dingen). Het type wat je compileert haal je rechtstreeks uit de assembly (met GetTypes()), dus is het gewoon een ander type. Ik verwacht dat hetzelfde gebeurd als je een project hebt met meerdere assemblies en 2 assemblies referencen naar een andere versie van eenzelfde project(/assembly). In beide assemblies wordt het type geinstantieerd op basis van de naam van het type, maar ook de assembly naam/versie in zit.

Edit:
Wat bedoel je trouwens met dat dit sneller is dan een expression tree? Bedoel je hiermee het omschrijven van de string naar een (Linq) Expression? Deze Expression kun je dan weer omzetten naar een Lambda (Expression<TDelegate>) en die Expression<TDelegate> kun je dan weer Compile()en naar een TDelegate die je daarna gewoon kunt Invoke()en.

Voorbeeld:
C#:
1
2
3
4
ParameterExpression param = Expression.Parameter(typeof(int));
Expression expr = Expression.AddChecked(param, Expression.Constant(5));
Expression<Func<int, int>> lambda = Expression.Lambda<Func<int, int>>(expr, param);
Func<int, int> func = lambda.Compile();


Deze "func" kun je daarna gewoon aanroepen met func(1) (of func.Invoke() the reflection way) wat 6 als resultaat zal geven. Deze func zal gok ik ook net zo snel zijn als een zelf gegenereerde class (/code), omdat de MSIL ongeveer hetzelfde zal(/behoord te) zijn.

[ Voor 43% gewijzigd door RobertMe op 28-01-2013 22:11 ]


  • Gimmeabrake
  • Registratie: December 2008
  • Laatst online: 19-11 21:37
RobertMe schreef op maandag 28 januari 2013 @ 22:01:
Daardoor is de assembly qualified name anders (deze is Namespace.Type, Assembly, Version=versie, nog wat dingen).
Danku. Dus, als ik het goed begrijp levert elke run-time compilatie een geheel nieuwe assembly op die een eigen "signature" heeft, en kan deze in vrede bestaan naast een andere assembly, ookal bestaan in de assemblies types met conflicterende namen. Dat er een aparte assembly ontstaat, blijkt natuurlijk al enigzins uit deze regel:
C#:
1
Assembly compiledAssembly = results.CompiledAssembly;


Nu ik met enige zekerheid weet dat dit zo gaat werken ga ik experimenteren met het schalen van de compilaties. Het programma moet er geen problemen mee hebben om op den duur duizenden compilaties te hebben uitgevoerd. Ik ben op Google al het een en ander hierover tegengekomen (natuurlijk ook veel tegenstrijdige uitspraken :9 ). Zoals ik het nu zie moet ik me, als er problemen optreden bij het schalen, gaan verdiepen in het unloaden van assemblies en/of het indien mogelijk combineren van meerdere classes in een assembly (door bijv. de classnamen te nummeren binnen 1 assembly: "CompiledNode1", "CompiledNode2", etc.). Als ik nog gekke/onverklaarbare dingen tegenkom, post ik ze! :)

  • Grijze Vos
  • Registratie: December 2002
  • Laatst online: 28-02 22:17
Kun je niet veel beter expression trees hiervoor gebruiken?

Op zoek naar een nieuwe collega, .NET webdev, voornamelijk productontwikkeling. DM voor meer info


  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 06-09 17:51
Grijze Vos schreef op maandag 28 januari 2013 @ 23:01:
Kun je niet veel beter expression trees hiervoor gebruiken?
Moet je nog steeds met de hand een parser schrijven om een expressie string om te zetten naar een volledige expression tree. Beter zou denk ik zijn om gewoon een project als NCalc uit de kast te trekken en dat te gebruiken: http://ncalc.codeplex.com/

  • Grijze Vos
  • Registratie: December 2002
  • Laatst online: 28-02 22:17
Daar zijn parser generators voor te vinden hoor. ;)
(Overigens, weer een beetje beter?)

Op zoek naar een nieuwe collega, .NET webdev, voornamelijk productontwikkeling. DM voor meer info


  • Gimmeabrake
  • Registratie: December 2008
  • Laatst online: 19-11 21:37
Grijze Vos schreef op maandag 28 januari 2013 @ 23:01:
Kun je niet veel beter expression trees hiervoor gebruiken?
Nee. De gecompileerde node wordt gegenereerd uit een expression tree. Zie mijn toelichting in de eerste alinea van mijn startpost:
gerrymeistah schreef op maandag 28 januari 2013 @ 20:40:
De run-time compiled code verhoogt in vele gevallen de snelheid van een herberekening (als bijv. een variable zoals "x" verandert) met tientallen procenten ten opzichte van de oorspronkelijk gegenereerde expression tree.
R4gnax schreef op maandag 28 januari 2013 @ 23:20:
Beter zou denk ik zijn om gewoon een project als NCalc uit de kast te trekken en dat te gebruiken: http://ncalc.codeplex.com/
Ik weet dat er al diverse expression parsers bestaan, maar dit is een vrijetijdsproject, ik probeer hierbij nieuwe dingen te leren en op mijn studie geleerde algoritmes (in vakken als "numerical mathematics") in de praktijk te brengen. Vergelijk het maar met een beginnend programmeur die de zoveelste rekenmachine/kladblok schrijft om te leren programmeren, maar dan voor iemand op een iets verder gevorderd niveau. Overigens hebben veel andere expression parsers een ander doel voor ogen, het doel van deze parser is een en al performance. Een parser die er geen problemen mee heeft om bijv. meerdere grafieken real-time te animeren aan de hand van een veranderende variabel. De parser is voor mij dus het doel, niet een middel om iets anders te bereiken. Als student heb ik nu nog de tijd/luxe om in mijn vrije tijd aan leuke projecten te werken, en dit project is tot nu toe zeer leerzaam voor mij gebleken. :)

  • FlowinG
  • Registratie: Maart 2003
  • Laatst online: 23-11 09:20
Mocht je toch veel tijd over hebben, kijk dan eens naar het Roselyn project: dat is een .NET compiler as a service. Waarschijnlijk zal deze compiler in de toekomst de huidige (in C++ ? geïmplementeerde) compiler vervangen. Het project is nu in de CTP fase, in afwachting van zijn final release. Echt vet om mee te experimenteren en volgens mij past dat perfect in je huidige project :)

MSDN: Microsoft “Roslyn” CTP

  • RobertMe
  • Registratie: Maart 2009
  • Laatst online: 06:35
gerrymeistah schreef op maandag 28 januari 2013 @ 22:36:
Nu ik met enige zekerheid weet dat dit zo gaat werken ga ik experimenteren met het schalen van de compilaties. Het programma moet er geen problemen mee hebben om op den duur duizenden compilaties te hebben uitgevoerd. Ik ben op Google al het een en ander hierover tegengekomen (natuurlijk ook veel tegenstrijdige uitspraken :9 ). Zoals ik het nu zie moet ik me, als er problemen optreden bij het schalen, gaan verdiepen in het unloaden van assemblies en/of het indien mogelijk combineren van meerdere classes in een assembly (door bijv. de classnamen te nummeren binnen 1 assembly: "CompiledNode1", "CompiledNode2", etc.). Als ik nog gekke/onverklaarbare dingen tegenkom, post ik ze! :)
Om meerdere classes in een assembly te loaden moet je er rekening mee houden dat je dat in een keer moet doen, je kunt niet later nog andere classes toevoegen aan de al bestaande assembly. Assemblies unloaden is ook niet zo makkelijk als het lijkt. Dit kan namelijk ook "helemaal niet" IIRC. Het enige wat je kunt doen is een los AppDomain unloaden, maar als je meerdere (meer dan 1) AppDomains hebt loop je daar weer tegen problemen aan dat alles geseriliseerd moet kunnen worden. Al zal dat met de losse ints (& evt. floats en decimals) geen probleem mogen zijn.

  • Gimmeabrake
  • Registratie: December 2008
  • Laatst online: 19-11 21:37
FlowinG schreef op dinsdag 29 januari 2013 @ 00:00:
Mocht je toch veel tijd over hebben, kijk dan eens naar het Roselyn project: dat is een .NET compiler as a service.MSDN: Microsoft “Roslyn” CTP
Zeer interessant! Ga er zeker naar kijken. Ben benieuwd of er performance verschillen zichtbaar gaan zijn in de 2 compilatiemethodes!
RobertMe schreef op dinsdag 29 januari 2013 @ 08:46:
[...]
Om meerdere classes in een assembly te loaden moet je er rekening mee houden dat je dat in een keer moet doen, je kunt niet later nog andere classes toevoegen aan de al bestaande assembly. Assemblies unloaden is ook niet zo makkelijk als het lijkt. Dit kan namelijk ook "helemaal niet" IIRC. Het enige wat je kunt doen is een los AppDomain unloaden, maar als je meerdere (meer dan 1) AppDomains hebt loop je daar weer tegen problemen aan dat alles geseriliseerd moet kunnen worden. Al zal dat met de losse ints (& evt. floats en decimals) geen probleem mogen zijn.
Dit had ik inderdaad ook al gelezen. Het lijkt me dat die serialization flink wat overhead gaat toevoegen terwijl ik de run-time compilation juist gebruik om performance te verhogen. Dit lijkt dus niet the way to go. Ik ga nog wat meer lezen en eens kijken of Roslyn evt. oplossing kan bieden. Bedankt voor alle feedback tot nu toe! :)
Pagina: 1