优化函数声明位置

Optimising Func Declaration Location

我有两个 Func<T,T2> 对象,它们将在一个将被重复调用的方法中使用(尽管这个方法只会从一个位置调用),声明 Funcs在父函数中(这样只需要实例化一次)传给子函数或者放在子函数里面(这样更接近用法)

我不太了解 Func class 或 C# 编译器优化算法的内部工作原理。

出于说明目的:

情况一:

    static void ThreadWorker(ref int current, ref int count)
    {
        bool isFinished = false;
        while (!isFinished)
        {
            int workingValue = current++;
            if (workingValue > TARGET)
            {
                isFinished = true;
            }
            else
            {
                if (EightyNineChain(workingValue))
                {
                    count++;
                }
            }
        }


    }

    private static bool EightyNineChain(int value)
    {
        Func<int, int[]> getDigits = v => v.ToString().Select(x => int.Parse(x.ToString()).ToArray();
        Func<int[], int> getDigitSquareSum = x => (int)x.Select(d => Math.Pow(d, 2)).Sum();
        //More code here
        return result;
    }

而情况 2:

    static void ThreadWorker(ref int current, ref int count)
    {
        Func<int, int[]> getDigits = v => v.ToString().Select(x => int.Parse(x.ToString()).ToArray();
        Func<int[], int> getDigitSquareSum = x => (int)x.Select(d => Math.Pow(d, 2)).Sum();
        bool isFinished = false;
        while (!isFinished)
        {
            int workingValue = current++;
            if (workingValue > TARGET)
            {
                isFinished = true;
            }
            else
            {
                if (EightyNineChain(workingValue, getDigits, getDigitSquareSum))
                {
                    count++;
                }
            }
        }


    }

    private static bool EightyNineChain(int value, Func<int,int[]> getDigits, Func<int[],int> getDigitSquareSum)
    {

        //More code here
        return result;
    }

对我来说,情况 1 使代码更清晰,因为 Func 的声明更接近它们的使用位置,因此它更易读。然而,逻辑告诉我情况 2 应该更快更有效。

我认为这里最好的方法是使用 C# 7 的本地函数。

考虑以下代码:

using System;
using System.Linq;

namespace ConsoleApp1
{
    public class Program
    {
        static void Main()
        {
            Console.WriteLine(test1(1));
            Console.WriteLine(test2(1));

            Func<int, int[]> getDigits = v => v.ToString().Select(Convert.ToInt32).ToArray();
            Func<int[], int> getDigitSquareSum = x => (int)x.Select(d => Math.Pow(d, 2)).Sum();

            Console.WriteLine(test3(1, getDigits, getDigitSquareSum));
        }

        static int test1(int value) // Use local Func<>
        {
            Func<int, int[]> getDigits = v => v.ToString().Select(Convert.ToInt32).ToArray();
            Func<int[], int> getDigitSquareSum = x => (int)x.Select(d => Math.Pow(d, 2)).Sum();

            var a = getDigits(value);
            var b = getDigitSquareSum(a);

            return b;
        }

        static int test2(int value) // Use local function.
        {
            int[] digits(int v) => v.ToString().Select(Convert.ToInt32).ToArray();
            int digitSquareSum(int[] x) => (int) x.Select(d => Math.Pow(d, 2)).Sum();

            var a = digits(value);
            var b = digitSquareSum(a);

            return b;
        }

        // Pass in Func<>
        static int test3(int value, Func<int, int[]> getDigits, Func<int[], int> getDigitSquareSum)
        {
            var a = getDigits(value);
            var b = getDigitSquareSum(a);

            return b;
        }
    }
}

这将转换为以下 IL 代码:

.class public auto ansi beforefieldinit ConsoleApp1.Program
    extends [mscorlib]System.Object
{
    .method public hidebysig specialname rtspecialname instance void .ctor () cil managed 
    {
        IL_0000: ldarg.0
        IL_0001: call instance void [mscorlib]System.Object::.ctor()
        IL_0006: ret
    }

    .method private hidebysig static void Main () cil managed 
    {
        .entrypoint
        .locals init (
            [0] class [mscorlib]System.Func`2<int32, int32[]> getDigits,
            [1] class [mscorlib]System.Func`2<int32[], int32> getDigitSquareSum
        )

        IL_0000: ldc.i4.1
        IL_0001: call int32 ConsoleApp1.Program::test1(int32)
        IL_0006: call void [mscorlib]System.Console::WriteLine(int32)
        IL_000b: ldc.i4.1
        IL_000c: call int32 ConsoleApp1.Program::test2(int32)
        IL_0011: call void [mscorlib]System.Console::WriteLine(int32)
        IL_0016: ldsfld class [mscorlib]System.Func`2<int32, int32[]> ConsoleApp1.Program/'<>c'::'<>9__0_0'
        IL_001b: dup
        IL_001c: brtrue.s IL_0035

        IL_001e: pop
        IL_001f: ldsfld class ConsoleApp1.Program/'<>c' ConsoleApp1.Program/'<>c'::'<>9'
        IL_0024: ldftn instance int32[] ConsoleApp1.Program/'<>c'::'<Main>b__0_0'(int32)
        IL_002a: newobj instance void class [mscorlib]System.Func`2<int32, int32[]>::.ctor(object,  native int)
        IL_002f: dup
        IL_0030: stsfld class [mscorlib]System.Func`2<int32, int32[]> ConsoleApp1.Program/'<>c'::'<>9__0_0'

        IL_0035: stloc.0
        IL_0036: ldsfld class [mscorlib]System.Func`2<int32[], int32> ConsoleApp1.Program/'<>c'::'<>9__0_1'
        IL_003b: dup
        IL_003c: brtrue.s IL_0055

        IL_003e: pop
        IL_003f: ldsfld class ConsoleApp1.Program/'<>c' ConsoleApp1.Program/'<>c'::'<>9'
        IL_0044: ldftn instance int32 ConsoleApp1.Program/'<>c'::'<Main>b__0_1'(int32[])
        IL_004a: newobj instance void class [mscorlib]System.Func`2<int32[], int32>::.ctor(object,  native int)
        IL_004f: dup
        IL_0050: stsfld class [mscorlib]System.Func`2<int32[], int32> ConsoleApp1.Program/'<>c'::'<>9__0_1'

        IL_0055: stloc.1
        IL_0056: ldc.i4.1
        IL_0057: ldloc.0
        IL_0058: ldloc.1
        IL_0059: call int32 ConsoleApp1.Program::test3(int32,  class [mscorlib]System.Func`2<int32, int32[]>,  class [mscorlib]System.Func`2<int32[], int32>)
        IL_005e: call void [mscorlib]System.Console::WriteLine(int32)
        IL_0063: ret
    }

    .method private hidebysig static int32 test1 (
            int32 'value'
        ) cil managed 
    {
        .locals init (
            [0] class [mscorlib]System.Func`2<int32, int32[]> getDigits,
            [1] int32[] a
        )

        IL_0000: ldsfld class [mscorlib]System.Func`2<int32, int32[]> ConsoleApp1.Program/'<>c'::'<>9__1_0'
        IL_0005: dup
        IL_0006: brtrue.s IL_001f

        IL_0008: pop
        IL_0009: ldsfld class ConsoleApp1.Program/'<>c' ConsoleApp1.Program/'<>c'::'<>9'
        IL_000e: ldftn instance int32[] ConsoleApp1.Program/'<>c'::'<test1>b__1_0'(int32)
        IL_0014: newobj instance void class [mscorlib]System.Func`2<int32, int32[]>::.ctor(object,  native int)
        IL_0019: dup
        IL_001a: stsfld class [mscorlib]System.Func`2<int32, int32[]> ConsoleApp1.Program/'<>c'::'<>9__1_0'

        IL_001f: stloc.0
        IL_0020: ldsfld class [mscorlib]System.Func`2<int32[], int32> ConsoleApp1.Program/'<>c'::'<>9__1_1'
        IL_0025: dup
        IL_0026: brtrue.s IL_003f

        IL_0028: pop
        IL_0029: ldsfld class ConsoleApp1.Program/'<>c' ConsoleApp1.Program/'<>c'::'<>9'
        IL_002e: ldftn instance int32 ConsoleApp1.Program/'<>c'::'<test1>b__1_1'(int32[])
        IL_0034: newobj instance void class [mscorlib]System.Func`2<int32[], int32>::.ctor(object,  native int)
        IL_0039: dup
        IL_003a: stsfld class [mscorlib]System.Func`2<int32[], int32> ConsoleApp1.Program/'<>c'::'<>9__1_1'

        IL_003f: ldloc.0
        IL_0040: ldarg.0
        IL_0041: callvirt instance int32[] class [mscorlib]System.Func`2<int32, int32[]>::Invoke(!0)
        IL_0046: stloc.1
        IL_0047: ldloc.1
        IL_0048: callvirt instance int32 class [mscorlib]System.Func`2<int32[], int32>::Invoke(!0)
        IL_004d: ret
    }

    .method private hidebysig static int32 test2 (
            int32 'value'
        ) cil managed 
    {
        IL_0000: ldarg.0
        IL_0001: call int32[] ConsoleApp1.Program::'<test2>g__digits2_0'(int32)
        IL_0006: call int32 ConsoleApp1.Program::'<test2>g__digitSquareSum2_1'(int32[])
        IL_000b: ret
    }

    .method private hidebysig static int32 test3 (
            int32 'value',
            class [mscorlib]System.Func`2<int32, int32[]> getDigits,
            class [mscorlib]System.Func`2<int32[], int32> getDigitSquareSum
        ) cil managed 
    {
        .locals init (
            [0] int32[] a
        )

        IL_0000: ldarg.1
        IL_0001: ldarg.0
        IL_0002: callvirt instance int32[] class [mscorlib]System.Func`2<int32, int32[]>::Invoke(!0)
        IL_0007: stloc.0
        IL_0008: ldarg.2
        IL_0009: ldloc.0
        IL_000a: callvirt instance int32 class [mscorlib]System.Func`2<int32[], int32>::Invoke(!0)
        IL_000f: ret
    }
}

查看 test1() 的 IL。它必须新建几个对象并做很多其他事情。

现在查看 test3() 的 IL,它使用传入的 Func<> 参数。这样效率更高,但请注意,它仍然需要在 Func<>.

上对 Invoke() 进行 callvirt 调用

现在看看test2(),它使用了局部函数。它只是直接调用函数而不需要 callvirtInvoke()。这显然好多了。

但是请注意,在所有方法中,函数本身的代码仅在编译时编译一次 - 即使对于 test1() 也不会在每次调用 test1() 时都完成,因此没有从那个角度看开销。