Primitive type conversion in generic method without boxing
Asked Answered
H

1

4

While doing some profiling on one of our applications, I found this code:

public TOut GetValue<TIn, TOut>(Func<TIn> getter)
{
    var value = getter();

    // Do some stuff with the value

    return (TOut)Convert.ChangeType(value, typeof(TOut));
}

TIn and TOut are either int, double, or string.

This showed up in the profiling sessions as an important source of heap allocations because of the boxing when using int or double. The input value of Convert.ChangeType is boxed because the method expects an object, and the return value is boxed for the same reason.

I'm trying to optimize this code because this method is used in a high-throughput service, where this kind of allocation is prohibitive. Ideally, I would have rewritten the method as non-generic, but the API is widely used by various teams and a refactoring of such scale will take months. In the meantime I'm trying to mitigate the issue and find a way to improve the situation without changing the API contract. However I've been struggling on this for a while and have yet to find a solution.

Do you know a way, even ugly, to handle the int -> double and double -> int conversion without boxing, given the method contract? Note that I can't change the parameters but I could add a generic constraint (such as where TIn : IConvertible, but this hasn't helped me much).

Hornstein answered 4/8, 2017 at 12:53 Comment(3)
In other words you want generic ChangeType? You can use "switch/case" for all types you care and otherwise call boxed version.Capernaum
Maybe taking a look at the .NET referencesource for Convert.ChangeType will help in understanding what it does, perhaps allowing you to create a shorter/faster/more efficient approach for your specific scenario.Clydesdale
Possible duplicate: #390786Tews
H
5

If you only need to specialize a couple of conversions, I think the following works and does not incur any per-call allocations:

    private static int FromDouble(double other)
    {
        return (int)other;
    }
    private static double FromInt(int other)
    {
        return (double)other;
    }

    private static Func<double, int> di = FromDouble;
    private static Func<int, double> id = FromInt;
    public TOut GetValue<TIn, TOut>(Func<TIn> getter)
    {
        var value = getter();

        // Do some stuff with the value

        if (typeof(TIn) == typeof(int) && typeof(TOut) == typeof(double))
        {
            var gf = (Func<TIn, TOut>)(object)id;
            return gf(value);
        }else if (typeof(TIn) == typeof(double) && typeof(TOut) == typeof(int))
        {
            var gf = (Func<TIn, TOut>)(object)di;
            return gf(value);
        }

        return (TOut)Convert.ChangeType(value, typeof(TOut));
    }

There are a couple of further tweaks that could be made to this, of course.

Hunnicutt answered 4/8, 2017 at 13:42 Comment(3)
I didn't think of casting a delegate, that's smart. Thanks!Hornstein
Wow nice. It took me while to understand this code, but once I did - it clicked. The readability suffers though. Well, I guess it always does when you're optimizing stuff at this level. PS. I'm having a similar issue - saving stuff to Redis.Mariner
This is a cool trick! +1Tews

© 2022 - 2024 — McMap. All rights reserved.