System.Linq.Expressions运行时代码生成缓慢原因及优化方法
向量点积泛型表达式树函数运行缓慢的原因分析与优化
问题背景
尝试实现一个泛型方法,通过表达式树生成计算向量点积的函数,但生成的函数运行耗时极长。原实现代码如下:
public static Func<T[], T[], T> GetVectorMultiplyFunction<T>() where T : struct { ParameterExpression first = Expression.Parameter(typeof(T[]), "first" ); ParameterExpression second = Expression.Parameter(typeof(T[]), "second"); ParameterExpression result = Expression.Parameter(typeof(T) , "result"); ParameterExpression index = Expression.Parameter(typeof(int), "index" ); LabelTarget label = Expression.Label(typeof(T)); BlockExpression block = Expression.Block( new[] { result, index }, Expression.Assign( result, Expression.Constant(0) ), Expression.Assign( index , Expression.Constant(0) ), Expression.Loop( Expression.IfThenElse( Expression.LessThan( index, Expression.ArrayLength( first ) ), Expression.Block( Expression.AddAssign( result, Expression.Multiply( Expression.ArrayIndex( first, index ), Expression.ArrayIndex( second, index ) ) ), Expression.Increment( index ) ), Expression.Break( label, result ) ), label ) ); return Expression .Lambda<Func<T[], T[], T>>( block, first, second ) .Compile(); }
测试代码片段:
[Test] public void GetVectorMultiplyFunctionReturnsFunctionForLong() { var first = new long[] { 1L, 2L, 3L }; var second = new long[] { 2L, 2L, 2L }; var expected = 1L * 2L + 2L * 2L + 3L * 2L; var func = CodeGeneration.GetVectorMultiplyFunction<long>(); var actual = func(first, second); Assert.AreEqual(expected, actual); }
运行缓慢的原因分析
- 装箱与拆箱开销:
Expression.Constant(0)生成的是int类型常量,当T为long、float等其他值类型时,每次赋值和运算都会触发装箱/拆箱操作,这是性能损耗的核心原因。 - 循环边界重复计算:
Expression.ArrayLength(first)在每次循环条件判断中都会重新计算数组长度,而数组长度在函数执行期间是固定的,重复计算浪费不必要的CPU资源。 - 缺少数组长度校验:原代码未检查两个输入数组的长度是否一致,不仅存在逻辑错误(数组长度不同时会导致越界或计算错误),也会让JIT难以进行优化。
优化方案
针对上述问题,优化后的实现如下:
public static Func<T[], T[], T> GetVectorMultiplyFunction<T>() where T : struct { ParameterExpression first = Expression.Parameter(typeof(T[]), "first"); ParameterExpression second = Expression.Parameter(typeof(T[]), "second"); // 定义局部变量 ParameterExpression result = Expression.Parameter(typeof(T), "result"); ParameterExpression index = Expression.Parameter(typeof(int), "index"); ParameterExpression length = Expression.Parameter(typeof(int), "length"); LabelTarget label = Expression.Label(typeof(T)); BlockExpression block = Expression.Block( new[] { result, index, length }, // 校验数组长度是否一致 Expression.IfThen( Expression.NotEqual(Expression.ArrayLength(first), Expression.ArrayLength(second)), Expression.Throw(Expression.New(typeof(ArgumentException).GetConstructor(new[] { typeof(string) }), Expression.Constant("数组长度必须一致"))) ), // 初始化变量,用Default避免装箱 Expression.Assign(length, Expression.ArrayLength(first)), Expression.Assign(result, Expression.Default(typeof(T))), Expression.Assign(index, Expression.Constant(0)), // 优化循环结构:先判断条件再执行循环体 Expression.Loop( Expression.IfThenElse( Expression.LessThan(index, length), Expression.Block( Expression.AddAssign(result, Expression.Multiply(Expression.ArrayIndex(first, index), Expression.ArrayIndex(second, index))), Expression.Increment(index) ), Expression.Break(label, result) ), label ) ); return Expression.Lambda<Func<T[], T[], T>>(block, first, second).Compile(); }
优化点说明
- 避免装箱拆箱:使用
Expression.Default(typeof(T))代替Expression.Constant(0),直接生成对应值类型的默认值(如long的0L、float的0f),完全消除装箱开销。 - 缓存数组长度:将数组长度提前计算并存储到局部变量
length中,循环条件判断时直接使用该变量,避免重复计算数组长度。 - 添加参数校验:在函数开头检查两个数组的长度是否一致,抛出明确的异常,同时让JIT能更高效地优化后续循环逻辑。
- 结构更清晰:调整代码结构,让表达式树生成的IL更接近手写的高效C#代码,便于JIT编译器进行优化。
内容的提问来源于stack exchange,提问作者Akbar Ergashev
相关产品推荐
相关产品推荐

