ValueTypes and perfomance

Jan 22, 2011 at 1:45 AM
Edited Jan 22, 2011 at 1:47 AM

Hi!

A have a question and dynamic binding on ValueTypes and associated perfomance issues. For example, we have the following hypothetical script code:

var g = 2;
for i = 0 to 10^6 do g = i + 10;

This code contains long-running loop with binary operation as loop body. ^ is a binary POW operation (10^6 = 1000000). Script translator produces the loop expression tree with Expression.Loop method and emits dynamic binding for i increment and addition between variable i and constant 10.

Each iteration of the loop wraps value type i to DynamicMetaObject. But DynamicMetaObject.Value property have object data type, therefore, DLR produces boxing operation in every iteration in the loop. After loop execution, managed heap will contain one million boxed integers. I think that GC will not be happy about this.

Is there any valuetype-specific strategy for dynamic binding?

Thank you.

Coordinator
Jan 22, 2011 at 3:43 AM

First off you say the value type I is being wrapped in a DynamicMetaObject – this isn’t really true, we’ll create a DMO if there’s not a cached rule. If there is a cached rule the produced rule should just check the values and return the new object w/ no need for DMO to bee involed. Because the returned object is an int it gets boxed into a boxed int, and there’s all of your boxed ints. If we were creating a DMO every time your performance would be much worse.

But you’re otherwise right – the boxing is still happening. The DLR doesn’t have any infrastructure for this really but I did play around w/ doing this in IronPython some what. In this case the optimization is actually really simple – you know the constant bounds of the loop and you know i is never assigned to during the loop. So you can promote i to a real integer in your expression tree and avoid the boxing there. In Python this was even more difficult – for our for loops we had to check for range/xrange, make sure they were really the range/xrange functions, and then and only then could we start doing this analysis. From there your addition in g ends up being a CallSite<Func<int, int, object>>.

Now the question is what are your language semantics for overflow of integers? Do you create BigIntegers or doubles? Do you throw an OverflowException? Or will you just wrap to negative integers like C#? If you’re wrapping to negative integers you’re nearly done. Now you just need to recognize that a CallSite of int + int always returns int, and hey there’s no dynamic typing anyway, so just get rid of the call site and inline the add. If you throw overflow exceptions you’re also nearly done – you just need to do a checked add instead of a normal add.

If you overflow to BigIntegers/double you’ll need to add some range analysis to your compiler. In this case you know the maximum value of i, and you know that g has a maximum value of I’s maximum value + 10. So now you know g’s maximum value. And you can therefore promote g to an int.

Obviously for more complex code this breaks down – there may be other assignments to i or g which break this analysis.

Unfortunately I didn’t find doing this on the AST was particularly straight forward. But I may have also been working w/ a much more dynamic language and I couldn’t control it’s semantics.

From: DjZAZ [email removed]
Sent: Friday, January 21, 2011 6:45 PM
To: Dino Viehland
Subject: ValueTypes and perfomance [dlr:242821]

From: DjZAZ

Hi!

A have a question and dynamic binding on ValueTypes and associated perfomance issues. For example, we have the following hypotetical script code:

var g = 2;
for i = 0 to 10^6 do g = i + 10;

This code contains long-running loop with binary operation as loop body. ^ is a binary POW operation (10^6 = 1000000). Script translator produces the loop expression tree with Expression.Loop method and emits dynamic binding for i increment and addition between variable i and constant 10.

Each iteration of the loop wraps value type i to DynamicMetaObject. But DynamicMetaObject.Value property have object data type, therefore, DLR produces boxing operation in every iteration in the loop. After loop execution, managed heap will contain one million boxed integers. I think that GC will not be happy about this.

Is there any valuetype-specific strategy for dynamic binding?

Thank you.

Jan 22, 2011 at 5:27 AM

Optimization technique that you describe is applicable when type of variable can be resolved at compile time. Is this right? If yes, then I cannot emit call site of Func<int, int> because I cannot determine type of the loop variable at compile time.

Jan 22, 2011 at 5:42 AM

I analyze SymPL example from DLR sources. In this example, binary operation binding is represented by the following code(ETGen.cs file, 596 line)

return Expression.Dynamic(
                scope.GetRuntime().GetBinaryOperationBinder(expr.Operation),
                typeof(object),
                AnalyzeExpr(expr.Left, scope),
                AnalyzeExpr(expr.Right, scope)
            );

The result of the dynamic expression is object, therefore boxing is performed in each binary operation. How to correct this code to avoid boxing (in case, when left and right expressions are not integer literals)?

Jan 22, 2011 at 8:44 AM

I don't know if it's relevant, but I'll give you the rundown on how IronJS (https://github.com/fholm/IronJS) solves this problem. IronJS uses a 16-bytes wide NaN-tagged custom struct for boxing values instead of wrapping everything as System.Object. The struct looks like this (F#):

type [<NoComparison>] [<StructLayout(LayoutKind.Explicit)>] BoxedValue =
  struct 
    //Reference Types
    [<FieldOffset(0)>] val mutable Clr : System.Object
    [<FieldOffset(0)>] val mutable Object : CommonObject
    [<FieldOffset(0)>] val mutable Array : ArrayObject
    [<FieldOffset(0)>] val mutable Func : FunctionObject
    [<FieldOffset(0)>] val mutable String : System.String
    [<FieldOffset(0)>] val mutable Scope : BoxedValue array

    //Value Types
    [<FieldOffset(8)>] val mutable Bool : bool
    [<FieldOffset(8)>] val mutable Number : double

    //Tag and Marker
    [<FieldOffset(12)>] val mutable Tag : uint32
    [<FieldOffset(14)>] val mutable Marker : uint16
  end

This has a few advantages, mainly that type checking is close to instantaneous using the .Tag and .Marker fields (just requiring one int comparison of either .Tag or .Marker depending on what type I'm looking for). There is also no dynamic casting/type checking when unboxing due to the overlaid fields you can read out any boxed value/object without a cast or type tests. Last due to the way the .Tag field values are laid out you can check inheritance by a simple >= int comparison. For example if I box a FunctionObject with:

let bv = new BoxedValue(); 
bv.Func = func; // Some FunctionObject
bv.Tag = TypeTags.Function

I could check if the value in bv is a CommonObject by doing a simple "if bv.Tag >= TypeTags.Object then ..." since FunctionObject inherits from CommonObject the Tag for FunctionObject is an integer value higher then for CommonObject. This leads to a lot faster type checking over the board then boxing everything as System.Object and relying on the built in "is" type checks. 

The one major down side to using this struct is that it's 16 bytes wide instead of the 8 bytes a boxed reference takes up (on x86-64), so there is some extra memory overhead, So, that was a bit of background on how IronJS structures boxed values behind the scenes, now I'll explain how a statement like "return a + b;" actually is compiled.

Taking the loop from another post in this thread and translating it into JavaScript would make it look something like this:

var g = 2;
var to = Math.pow(10, 6);

for(var i = 0; i < to; ++i) {
	g = i + 10;
}

When IronJS parses this it can deduce that both "g" and "i" ever are assigned numbers, but "to" is the result of a function call (Math.pow) and since JavaScript is dynamically typed we can not know if it actually returns a number or not. This means we can statically type g and i as double (JavaScript uses doubles for all numbers) compile the "g = i + 10;" using the standard Expression.Add, giving an expression tree like this:

Expression.Assign(
  Expression.Field(Variables["g"], "Number"), 
  Expression.Add(
    Expression.Field(Variables["i"], "Number"),
    Expression.Constant(10.0)));

Now, the tricky part comes at the "i < to" operation, since we can't know the type of "to" by statically analyzing the sources and the type of "i" is already known to be double we need some way of checking if "to" also is a number and take a shortcut if that is the case. The expression tree ends up looking like this (in pseudo/f# code):

if Variables["to"].Tag = TypeTags.Number then
    //Use the built in Expression.LessThan operation for to.Number and i.Number

else
    //Call the dynamic comparison operator

This is where the fast type checking comes in, adding minimal overhead to the < operation. Note that the expression trees/examples here are not directly extracted from the IronJS sources but are just to give a high level view of what I do in IronJS. If you have any more questions don't hesitate to ask :)

Jan 22, 2011 at 10:16 AM

Thanks, fholm, this is a good solution. But it is not oriented on DLR features, such as Expression.Dynamic and call site binders. It is strange that the dynamic extensions in .NET 4.0 and DLR not conver this fundamental issue. Basic type system in many programming languages contains primitive types, such as integers and floats, but DLR doesn't provide a simple way to solve these problems. The structure in your example allows to fix boxing problems.... and kill object-oriented methodology, because all primitive types are represented in one code entity. All runtime operations in this situation are represented by static methods and we will return to.. classic C.

public static BoxedValue Add(BoxedValue left, BoxedValue right)
{
  switch(left.Tag)
  {
    case Number: return new BoxedValue{Number = left.Number + right.Number};
    //other switch cases
  }
}

Now, back to perfomance. What you can say about perfomance of solution based on BoxedValue structure. The size of this structure is not equal to machine word and CLR should emits assembler code that copies block of memory insted of using registers. Of course, this is a better than allocation of thousands boxed integers and garbage collection. But now we replace the perfomance troubles from GC and memory manager to processor. Tell me that I wrong)

I just want to find a simple and object-oriented solution of my task.

Jan 22, 2011 at 11:18 AM
Edited Jan 22, 2011 at 11:25 AM

First it'd like to clarify that IronJS jumps through a multitude of hoops to avoid creating new BoxedValue structs everywhere, for example it calculates the amounts of simultaneous temporary BoxedValue structs each function body needs and pre-allocates an array of BoxedValue structs and uses those as temporary storage. This would not be possible if you boxed everything as System.Object since there is no way to re-use an already boxed "Object" for a new value.

For example, assume you have an add operation: "x = a + b" where both variables have unknown types. Now just using the dynamic binders it would look something like the the Sympl example:

return Expression.Assign(x, Expression.Dynamic(
    scope.GetRuntime().GetBinaryOperationBinder(expr.Operation),
    typeof(object),
    AnalyzeExpr(expr.Left, scope),
    AnalyzeExpr(expr.Right, scope)
));

Using the way I previously described for IronJS, both a and b being BoxedValue structs the resulting expression tree would look like this:

/*Assuming:
var a = Expr.Variable(typeof(BoxedValue), "a");
var b = Expr.Variable(typeof(BoxedValue), "b");
var x = Expr.Variable(typeof(BoxedValue), "x");
*/

Expr.IfThenElse(
  Expr.AndAlso(
    Expr.Equal(
      Expr.Field(a, "Tag"),
      Expr.Constant(TypeTags.Number, typeof(uint))
    ),
    Expr.Equal(
      Expr.Field(b, "Tag"),
      Expr.Constant(TypeTags.Number, typeof(uint))
    )
  ),
  Expr.Block(
    Expr.Assign(
      Expr.Field(x, "Number"),
      Expr.Add(Expr.Field(a, "Number"), Expr.Field(b, "Number"))
    ),
    Expr.Assign(
      Expr.Field(x, "Tag"),
      Expr.Constant(TypeTags.Number, typeof(uint))
    )
  ),
  Expr.Block(
    Expr.Assign(x, Expr.Call(null, typeof(DynamicOperators).GetMethod("Add"), a, b))
  )
);

If you'd want to use an Expression.Dynamic/Call Site binder instead of the DynamicOperators.Add method as the fall-back that is also possible (and IronJS does in some cases). But, let's compare performance between using the IronJS or dynamic binders. With a Expression.Dynamic and a call site you will in the best case scenario have at least one method call (the compiled call site handler) and two type checks on the boxed values. Using the IronJS in the best case there will be two integer comparison (comparing .Tag of each BoxedValue to TypeTags.Number) and then a native Add instruction.

And about the CPU performance, on 64bit CPUs it seems to be able to copy one 16 byte value without hitting main memory. But also the fact that I don't copy BoxedValue struct all over the place, about 95% of them are pre-allocated and re-used through out each function.

Jan 22, 2011 at 12:11 PM

The first, Expression.Dynamic is not applicable because CallSiteBinder-derived classes accept DynamicMetaObject(in fallback method) that uses boxing for structures and we will return to my first post.

The second, about 95 percents. How you obtain this value? As I know, structures are passed by value, not by reference, therefore, each calling of native Add method forces bit-to-bit copy of each BoxedValue-typed parameter and return value.

var b1 = new BoxedValue { Number = 10};
var b2 = new BoxedValue();
b2 = b1; //shallow copy
b2.Number = 30; //b1.Number still is 10

The third, how you preallocate all values of BoxedValue? If you use typed version of List or Dictionary then each read/write access to collection entails bit-to-bit copy, otherwise, there is boxing again.

Jan 22, 2011 at 1:47 PM

> "The first, Expression.Dynamic is not applicable because CallSiteBinder-derived classes accept DynamicMetaObject(in fallback method) that uses boxing for structures and we will return to my first post."

This is true, but the point of using the BoxedValue struct is to allow for really really fast type checks and inline the common case which bypasses the whole problem of the boxing in the CallSiteBinder - you won't be able to optimize both the common and rare cases (I've been developing IronJS for more then a year, believe me - I've tried.)

> "The second, about 95 percents. How you obtain this value? As I know, structures are passed by value, not by reference, therefore, each calling of native Add method forces bit-to-bit copy of each BoxedValue-typed parameter and return value."
> "The third, how you preallocate all values of BoxedValue? If you use typed version of List or Dictionary then each read/write access to collection entails bit-to-bit copy, otherwise, there is boxing again "

The pre-allocation and the high rate of non-copying is done by calculating all the temporary BoxedValue structs needed inside a function body and creating a BoxedValue[] that has the exact length. All local variables are also stored in a BoxedValue[] where each variable is represented as an index into that array that is calculated at compile time, allowing me both very fast access to variables and allows me to easily implement closures (the BoxedValue[] is a reference type itself, even though it contains a bunch of struct - it's very fast to pass around and index into.).

For calls to dynamic operator method, for example DynamicOperators.Add(BoxedValue a, BoxedValue b) there are three ways to go about this: 1) Take the speed hit of copying the full 16 bytes into Add, which is not as slow as you seem to think. 2) Change the signature of Add to (BoxedValue& a, BoxedValue& b) to pass in references instead or 3) Create an inline LambdaExpression that gets passed directly to an InvokeExpression which causes the DLR to inline it, have two parameters to the LambdaExpression which are of the type BoxedValue& which allows you to work directly with references to the two values without copying them.

In some parts I go even one step further, for example in loops, I call a special version of .Add doesn't do any copying - it has the following signature: "void Add(BoxedValue& a, BoxedValue& b, BoxedValue& result)" where the third parameter is a reference to the target location where the result of applying Add to a and b should be stored, so assuming an operation like this: "var result = x + y + z;" where we don't know the type of x, y , z the logic goes like this:

  1. Create an array that holds all local variables, indexes being this: x = 0, y = 1, z = 2, result = 3
  2. Make sure that x, y and z are all numbers by checking the BoxedValue .Tag field
  3. If they all are numbers, apply the built in Expression.Add to them, locals[3] = locals[0].Number + locals[1].Number + locals[2].Number;
  4. If they are not numbers, We're going to need a place to store the result of x + y before we can add + z to it, make sure a pre-allocated index in the temporary array exists
  5. Apply DynamicOperators.FastAdd(&locals[0], &locals[1], &tempValues[FREE_TEMP_INDEX]) to calculate x + y and store it in tempValues[FREE_TEMP_INDEX]
  6. Apply DynamicOperators.FastAdd(&tempValues[FREE_TEMP_INDEX], &locals[2], &locals[3]) to calculate the last part and store it in result

 


Coordinator
Jan 22, 2011 at 5:46 PM

Correct, that’s a compile time optimization. If you want to do runtime optimizations you can also do that – you could hoist the entire loop into a call site (with a custom binder which isn’t a standard action) and generate unique code for the loop that way which specializes for ints.

From: DjZAZ [email removed]
Sent: Friday, January 21, 2011 10:27 PM
To: Dino Viehland
Subject: Re: ValueTypes and perfomance [dlr:242821]

From: DjZAZ

Optimization technique that you describe is applicable when type of variable can be resolved at compile time. Is this right? If yes, then I cannot emit call site of Func<int, int> because I cannot determine type of the loop variable at compile time.

Coordinator
Jan 22, 2011 at 5:46 PM

Yeah, SymPL is simple and doesn’t do any complex optimizations like this.

From: DjZAZ [email removed]
Sent: Friday, January 21, 2011 10:42 PM
To: Dino Viehland
Subject: Re: ValueTypes and perfomance [dlr:242821]

From: DjZAZ

I analyze SymPL example from DLR sources. In this example, binary operation binding is represented by the following code(ETGen.cs file, 596 line)

return Expression.Dynamic(
                scope.GetRuntime().GetBinaryOperationBinder(expr.Operation),
                typeof(object),
                AnalyzeExpr(expr.Left, scope),
                AnalyzeExpr(expr.Right, scope)
            );

The result of the dynamic expression is object, therefore boxing is performed in each binary operation. How to correct this code to avoid boxing (in case, when left and right expressions are not integer literals)?

Coordinator
Jan 22, 2011 at 5:46 PM

This is definitely yet another good approach – I’ve heard about someone in Microsoft Research doing the same thing and of course it mirrors classic VB and COM w/ their variants.

From: fholm [email removed]
Sent: Saturday, January 22, 2011 1:45 AM
To: Dino Viehland
Subject: Re: ValueTypes and perfomance [dlr:242821]

From: fholm

I don't know if it's relevant, but I'll give you the rundown on how IronJS (https://github.com/fholm/IronJS) solves this problem. IronJS uses a 16-bytes wide NaN-tagged custom struct for boxing values instead of wrapping everything as System.Object. The struct looks like this (F#):

type [<NoComparison>] [<StructLayout(LayoutKind.Explicit)>] BoxedValue =
  struct 
    //Reference Types
    [<FieldOffset(0)>] val mutable Clr : System.Object
    [<FieldOffset(0)>] val mutable Object : CommonObject
    [<FieldOffset(0)>] val mutable Array : ArrayObject
    [<FieldOffset(0)>] val mutable Func : FunctionObject
    [<FieldOffset(0)>] val mutable String : System.String
    [<FieldOffset(0)>] val mutable Scope : BoxedValue array
 
    //Value Types
    [<FieldOffset(8)>] val mutable Bool : bool
    [<FieldOffset(8)>] val mutable Number : double
 
    //Tag and Marker
    [<FieldOffset(12)>] val mutable Tag : uint32
    [<FieldOffset(14)>] val mutable Marker : uint16
  end

This has a few advantages, mainly that type checking is close to instantaneous using the .Tag and .Marker fields (just requiring one int comparison of either .Tag or .Marker depending on what type I'm looking for). There is also no dynamic casting/type checking when unboxing due to the overlaid fields you can read out any boxed value/object without a cast or type tests. Last due to the way the .Tag field values are laid out you can check inheritance by a simple >= int comparison. For example if I box a FunctionObject with:

let bv = new BoxedValue(); 
bv.Func = func; // Some FunctionObject
bv.Tag = TypeTags.Function

I could check if the value in bv is a CommonObject by doing a simple "if bv.Tag >= TypeTags.Object then ..." since FunctionObject inherits from CommonObject the Tag for FunctionObject is an integer value higher then for CommonObject. This leads to a lot faster type checking over the board then boxing everything as System.Object and relying on the built in "is" type checks.

The one major down side to using this struct is that it's 16 bytes wide instead of the 8 bytes a boxed reference takes up (on x86-64), so there is some extra memory overhead, So, that was a bit of background on how IronJS structures boxed values behind the scenes, now I'll explain how a statement like "return a + b;" actually is compiled.

Taking the loop from another post in this thread and translating it into JavaScript would make it look something like this:

var g = 2;
var to = Math.pow(10, 6);
 
for(var i = 0; i < to; ++i) {
       g = i + 10;
}

When IronJS parses this it can deduce that both "g" and "i" ever are assigned numbers, but "to" is the result of a function call (Math.pow) and since JavaScript is dynamically typed we can not know if it actually returns a number or not. This means we can statically type g and i as double (JavaScript uses doubles for all numbers) compile the "g = i + 10;" using the standard Expression.Add, giving an expression tree like this:

Expression.Assign(
  Expression.Field(Variables["g"], "Number"), 
  Expression.Add(
    Expression.Field(Variables["i"], "Number"),
    Expression.Constant(10.0)));

Now, the tricky part comes at the "i < to" operation, since we can't know the type of "to" by statically analyzing the sources and the type of "i" is already known to be double we need some way of checking if "to" also is a number and take a shortcut if that is the case. The expression tree ends up looking like this (in pseudo/f# code):

if Variables["to"].Tag = TypeTags.Number then
    //Use the built in Expression.LessThan operation for to.Number and i.Number
 
else
    //Call the dynamic comparison operator

This is where the fast type checking comes in, adding minimal overhead to the < operation. Note that the expression trees/examples here are not directly extracted from the IronJS sources but are just to give a high level view of what I do in IronJS. If you have any more questions don't hesitate to ask :)

Coordinator
Jan 22, 2011 at 5:53 PM

You shouldn’t worry about the performance of being boxed into a DynamicMetaObject – that will only happen to perform the binding and once the binding is performed you won’t hit that source of boxing again. The larger problem would be that if you called another language with these objects they wouldn’t know what to do with them. But you could always use only custom binders when passing in a BoxedValue and fall back to a standard binder w/ the unboxing done.

From: DjZAZ [email removed]
Sent: Saturday, January 22, 2011 5:11 AM
To: Dino Viehland
Subject: Re: ValueTypes and perfomance [dlr:242821]

From: DjZAZ

The first, Expression.Dynamic is not applicable because CallSiteBinder-derived classes accept DynamicMetaObject(in fallback method) that uses boxing for structures and we will return to my first post.

The second, about 95 percents. How you obtain this value? As I know, structures are passed by value, not by reference, therefore, each calling of native Add method forces bit-to-bit copy of each BoxedValue-typed parameter and return value.

var b1 = new BoxedValue { Number = 10};
var b2 = new BoxedValue();
b2 = b1; //shallow copy
b2.Number = 30; //b1.Number still is 10

The third, how you preallocate all values of BoxedValue? If you use typed version of List or Dictionary then each read/write access to collection entails bit-to-bit copy, otherwise, there is boxing again.

Jan 22, 2011 at 6:11 PM
Have you tried using MutableTuple instead of an array for the local variable storage? In your example below, it would be

var locals = new MutableTuple<BoxedValue, BoxedValue, BoxedValue, BoxedValue, BoxedValue>()
locals.Item003 = locals.Item000.Number + locals.Item001.Number + locals.Item002.Number;

This would avoid indexing (array bounds checks). DLR's MutableTuple actually isn't good fit for your case since ItemN are properties, not fields. So you can't take an address. We should probably change that. You can indeed write your own implementation.

Tomas


From: [email removed]
To: [email removed]
Date: Sat, 22 Jan 2011 06:47:10 -0800
Subject: Re: ValueTypes and perfomance [dlr:242821]

From: fholm
> "The first, Expression.Dynamic is not applicable because CallSiteBinder-derived classes accept DynamicMetaObject(in fallback method) that uses boxing for structures and we will return to my first post."
This is true, but the point of using the BoxedValue struct is to allow for really really fast type checks and inline the common case which bypasses the whole problem of the boxing in the CallSiteBinder - you won't be able to optimize both the common and rare cases (I've been developing IronJS for more then a year, believe me - I've tried.)
> "The second, about 95 percents. How you obtain this value? As I know, structures are passed by value, not by reference, therefore, each calling of native Add method forces bit-to-bit copy of each BoxedValue-typed parameter and return value."
> "The third, how you preallocate all values of BoxedValue? If you use typed version of List or Dictionary then each read/write access to collection entails bit-to-bit copy, otherwise, there is boxing again "

The pre-allocation and the high rate of non-copying is done by calculating all the temporary BoxedValue structs needed inside a function body and creating a BoxedValue[] that has the exact length. All local variables are also stored in a BoxedValue[] where each variable is represented as an index into that array that is calculated at compile time, allowing me both very fast access to variables and allows me to easily implement closures (the BoxedValue[] is a reference type itself, even though it contains a bunch of struct - it's very fast to pass around and index into.).
For calls to dynamic operator method, for example DynamicOperators.Add(BoxedValue a, BoxedValue b) there are three ways to go about this: 1) Take the speed hit of copying the full 16 bytes into Add, which is not as slow as you seem to think. 2) Change the signature of Add to (BoxedValue& a, BoxedValue& b) to pass in references instead or 3) Create an inline LambdaExpression that gets passed directly to an InvokeExpression which causes the DLR to inline it, have two parameters to the LambdaExpression which are of the type BoxedValue& which allows you to work directly with references to the two values without copying them.
In some parts I go even one step further, for example in loops, I call a special version of .Add doesn't do any copying - it has the following signature: "void Add(BoxedValue& a, BoxedValue& b, BoxedValue& result)" where the third parameter is a reference to the target location where the result of applying Add to a and b should be stored, so assuming an operation like this: "var result = x + y + z;" where we don't know the type of x, y , z the logic goes like this:
  1. Create an array that holds all local variables, indexes being this: x = 0, y = 1, z = 2, result = 3
  2. Make sure that x, y and z are all numbers by checking the BoxedValue .Tag field
  3. If they all are numbers, apply the built in Expression.Add to them, locals[3] = locals[0].Number + locals[1].Number + locals[2].Number;
  4. If they are not numbers, We're going to need a place to store the result of x + y before we can add + z to it, make sure a pre-allocated index in the temporary array exists
  5. Apply DynamicOperators.FastAdd(&locals[0], &locals[1], &tempValues[FREE_TEMP_INDEX]) to calculate x + y and store it in tempValues[FREE_TEMP_INDEX]
  6. Apply DynamicOperators.FastAdd(&tempValues[FREE_TEMP_INDEX], &locals[2], &locals[3]) to calculate the last part and store it in result



Read the full discussion online.
To add a post to this discussion, reply to this email (dlr@discussions.codeplex.com)
To start a new discussion for this project, email dlr@discussions.codeplex.com
You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.
Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com
Jan 22, 2011 at 6:45 PM

TomasMatousek: I've tried using a reference type with fields instead (my own version of MutableTuple basically), a few things came up:

  • The speed difference between locals.Item0 and locals[0] is not huge.
  • It makes it impossible to statically type "locals" object inside closure contexts and when passing it around. This forces me to cast the "locals" object when I access it which offsets the speed increase from not having array bounds checking.

So in short, you could use a MutableTuple (with Fields instead of Properties) but I've found the advantages of using an array outweigh the array bounds checking.