优化函数声明位置
Optimising Func Declaration Location
我有两个 Func<T,T2>
对象,它们将在一个将被重复调用的方法中使用(尽管这个方法只会从一个位置调用),声明 Func
s在父函数中(这样只需要实例化一次)传给子函数或者放在子函数里面(这样更接近用法)
我不太了解 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()
,它使用了局部函数。它只是直接调用函数而不需要 callvirt
或 Invoke()
。这显然好多了。
但是请注意,在所有方法中,函数本身的代码仅在编译时编译一次 - 即使对于 test1()
也不会在每次调用 test1()
时都完成,因此没有从那个角度看开销。
我有两个 Func<T,T2>
对象,它们将在一个将被重复调用的方法中使用(尽管这个方法只会从一个位置调用),声明 Func
s在父函数中(这样只需要实例化一次)传给子函数或者放在子函数里面(这样更接近用法)
我不太了解 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()
,它使用了局部函数。它只是直接调用函数而不需要 callvirt
或 Invoke()
。这显然好多了。
但是请注意,在所有方法中,函数本身的代码仅在编译时编译一次 - 即使对于 test1()
也不会在每次调用 test1()
时都完成,因此没有从那个角度看开销。