none
Type conversion in CIL, a question RRS feed

  • Question

  • We have some experimental code that implements a faster form of reflection on fields in structs.

    However, there is a nagging problem.

    The delegate for the set operation we create has a signature like: void delegate FastFieldSetter (ref object target,object value);

    It all works, but only if the 'value' argument is already of the same type as the field.

    So we must call the delegate like this:

    FastFieldSetter(ref datum,(long)123);  // field type is long

    FastFieldSetter(ref datum,(double)23.56); // field type is double.

    and so on, else we get an invalid cast exception at runtime.

    Here is the CIL that does this:

                generator.Emit(OpCodes.Ldarg_0); // struct address
                generator.Emit(OpCodes.Ldarg_1); // value obj ref
                generator.Emit(OpCodes.Unbox_Any, field_type);
                generator.Emit(OpCodes.Stfld, field_info);
                generator.Emit(OpCodes.Ret);
    


    as you can see, we unbox the value (which is always an 'object') and then just execute the StFld operation.

    However, what should we do if the value is say and Int32 or an Int16 etc? I would expect we need to convert a Int16 to an Int64 after we do the Unbox, but does that mean we need to write CIL that examines the type of the value, then performs one of the many converison operations? if so then we'd need a long list of logic to handle that and I am suspecting there is a neater way.

    Does anyone have any insights into this kind of thing?

    Thx

    Cap'n



    Monday, December 14, 2009 4:43 PM

Answers


  • I Know, you may be right, I have assumed (always dangerous) that this may be more costly, since it will have to do some checking of the type to decide which conversion to do, by doing that ahead of time I have assumed we may get a slightly better speed.

    I guess I should try this though...

    Cap'n


    One thing to consider:

    It's nearly always, always better to try to use the built-in facilities for anything of this sort, and then, if (and only if) you see a real performance problem, look into trying to optimize it.

    Take this scenario:  Using Convert.ChangeType vs. your own FastFieldSetter - I suspect you'll find that Convert.ChangeType, if you profile it accurately, is probably nearly the same speed as your own implementation.  It may actually be faster, depending on how you can use it, since you are adding a lot of boxing overhead in your implementation.

    The core framework has been pretty heavily optimized, and usually works very well.  Don't make extra work for yourself - it ususally does not pay off, and in the long run, it can cause strange bugs and maintainability nightmares to creep into your codebase.


    Reed Copsey, Jr. - http://reedcopsey.com
    • Marked as answer by eryang Monday, December 21, 2009 2:23 AM
    Tuesday, December 15, 2009 5:39 PM
    Moderator

All replies

  • This is a very tricky thing to get right.

    The problem is that you can't unbox and cast in a single operation.  Eric Lippert blogged about this in detail.

    Frankly, what you have now, since it's doing a box and unbox, is not going to be a quick operation.  I suspect you could get similar or better performance boost by using an Expression Tree to generate a generics-based version of the field setter (so you don't have any boxing). 


    Reed Copsey, Jr. - http://reedcopsey.com
    Monday, December 14, 2009 5:26 PM
    Moderator
  • Unfortunately, you have to write switch/case on value type to perform such coercions. See Convert class, for example. It explicitly examines the type of source value and then dispatches conversions accordingly. Seems like there's no better way. Your delegate may use Convert class to perform necessary conversions for you, but you will get some performance penalty.

    If you want it to be really fast, you have to pass properly typed values to your FastFieldSetter. And, of course, try to avoid boxing/unboxing (for example, generate generic FastFieldSetter<T>(ref structure, T value)).
    Monday, December 14, 2009 8:01 PM
  • Unfortunately, you have to write switch/case on value type to perform such coercions. See Convert class, for example. It explicitly examines the type of source value and then dispatches conversions accordingly. Seems like there's no better way. Your delegate may use Convert class to perform necessary conversions for you, but you will get some performance penalty.

    If you want it to be really fast, you have to pass properly typed values to your FastFieldSetter. And, of course, try to avoid boxing/unboxing (for example, generate generic FastFieldSetter<T>(ref structure, T value)).

    Yes, this is what I am seeing. I have just made a tweak, we dynamically call 'Convert.ToXXX' now, this was easy to code, because we can get a method info to that method very easy, we just look up a method named "To" + Type.GetTypeCode(arg_type).ToString(). we then embed a call to that in the IL. Because all of the Convert methods have very systematic names it was easy and had no switch !

    Of couse the call to the conversion carries a cost and it is measurable.

    However I have an intriguing idea that may allow me to dispense with all conversion calls and boxing/unboxing but I need to review this and see if it will hold water, if it does I may see a jump in performance, it will rely on some other system code I wrote that tells me things about a type that .Net cannot (or rather does not) do.

    Right now, with the conversion in place and apparently solid, no crashes with any arg type passed in (e.g. pass a Double in when field is Int64) I can set a field approx 10 times faster than standard reflection 'SetValue' which is not bad I guess.

    I wonder if MS plan to eve allow us to embed IL right into our own code, this would allow us to 'inline' stuff rather than have to invoke via a delegate...???

    Cap'n

    Monday, December 14, 2009 8:31 PM
  • we just look up a method named "To" + Type.GetTypeCode(arg_type).ToString(). we then embed a call to that in the IL. Because all of the Convert methods have very systematic names it was easy and had no switch !
    Why not just use Convert.ChangeType()?


    There is one more helper class that may be useful. Did you consider to use TypedReference? This class can encapsulate a reference to a structure field (value-typed). I know nothing about its performance, but it may be simpler than what you're currently doing.
    • Edited by Alexey R. _ Tuesday, December 15, 2009 11:12 AM
    Tuesday, December 15, 2009 10:47 AM
  • we just look up a method named "To" + Type.GetTypeCode(arg_type).ToString(). we then embed a call to that in the IL. Because all of the Convert methods have very systematic names it was easy and had no switch !
    Why not just use Convert.ChangeType()?

    I Know, you may be right, I have assumed (always dangerous) that this may be more costly, since it will have to do some checking of the type to decide which conversion to do, by doing that ahead of time I have assumed we may get a slightly better speed.

    I guess I should try this though...

    Cap'n

    Tuesday, December 15, 2009 11:04 AM

  • I Know, you may be right, I have assumed (always dangerous) that this may be more costly, since it will have to do some checking of the type to decide which conversion to do, by doing that ahead of time I have assumed we may get a slightly better speed.

    I guess I should try this though...

    Cap'n


    One thing to consider:

    It's nearly always, always better to try to use the built-in facilities for anything of this sort, and then, if (and only if) you see a real performance problem, look into trying to optimize it.

    Take this scenario:  Using Convert.ChangeType vs. your own FastFieldSetter - I suspect you'll find that Convert.ChangeType, if you profile it accurately, is probably nearly the same speed as your own implementation.  It may actually be faster, depending on how you can use it, since you are adding a lot of boxing overhead in your implementation.

    The core framework has been pretty heavily optimized, and usually works very well.  Don't make extra work for yourself - it ususally does not pay off, and in the long run, it can cause strange bugs and maintainability nightmares to creep into your codebase.


    Reed Copsey, Jr. - http://reedcopsey.com
    • Marked as answer by eryang Monday, December 21, 2009 2:23 AM
    Tuesday, December 15, 2009 5:39 PM
    Moderator