优化反射性能的总结(中)

问题回顾

上篇博客中,我介绍了优化反射的第一个步骤:用委托调用代替直接反射调用。
然而,那只是反射优化过程的开始,因为新的问题出现了:如何保存大量的委托?

如果我们将委托保存在字典集合中,会发现这种设计会浪费较多的执行时间,因为这种设计会引发三个新问题:
1. 代码的执行路径变长了。
2. 字典查找是有成本开销的。
3. 字典集合的并发读写需要锁定,会影响并发性。

再来回顾一下上次的测试结果吧:

虽然通用接口ISetValue将反射性能优化了37倍,但是最终的FastSetValue将这个数字减少到还不到7倍(在CLR4中还不到5倍)。
难道您不觉得遗憾吗?

再看看直接调用与反射调用的对比,它们的速度相差了上千倍!

能不能不使用委托?

既然委托最后引出了三个难以解决的问题,导致优化后速度比直接调用差距太远,那我们能不能不使用委托呢?

委托调用并不是优化反射的唯一方案,我们还有其它方法,
之所以委托调用能成为常见的优化方案是因为它比较简单。

假如我需要用客户端提交的数据来填充某个数据对象,考虑到代码的通用性,我会用反射写成这样:

/// <summary>
/// 从HttpRequest加载obj所需的数据
/// </summary>
/// <param name="request"></param>
/// <param name="obj"></param>
public static void LoadDataFromHttpRequest(HttpRequest request, object obj)
{
    PropertyInfo[] properties = obj.GetType().GetProperties();
    foreach( PropertyInfo p in properties ) {
        // 这里只是示意代码,假设数据处理不会有异常。
        object val = Convert.ChangeType(request[p.Name], p.PropertyType);
        p.FastSetValue(obj, val);
    }
}

如果我事先知道要加载已知的数据类型,代码会写成这样:

public static void LoadDataFromHttpRequest(HttpRequest request, OrderInfo order)
{
    // 这里只是示意代码,假设数据处理不会有异常。
    order.OrderID = int.Parse(request["OrderID"]);
    order.OrderDate = DateTime.Parse(request["OrderDate"]);
    order.SumMoney = decimal.Parse(request["SumMoney"]);
    order.Comment = request["Comment"];
    order.Finished = bool.Parse(request["Finished"]);
}

显然,第二段代码运行效率更快(尽管第一段代码调用FastSetValue优化了速度)。

大家都知道反射性能较差,直接调用性能最好,那么能不能在运行时不使用反射呢?

的确,使用反射是因为我们事先不知道要处理哪些类型的对象,因此不得不用反射, 另外,反射的代码也更通用,写一个方法可以加载所有的数据类型,可认为是一劳永逸的方法。 不过,就算我们事先不知道要处理哪些对象类型,但是只要使用反射,我们完全可以知道任何一个类型包含哪些数据成员, 还能知道这些数据成员的数据类型,这一点不用怀疑吧? 既然我们用反射可以知道所有的类型定义信息,我们是否可以参照代码生成器的思路去生成代码呢? 我们可以参照前面第二段代码,为【需要处理的类型】生成直接调用的代码,这样不就彻底解决了反射性能问题了吗? 生成代码的过程,其实也就是个字符串的拼接过程,难度并不大,只是比较复杂而已。

如果前面的答案都是肯定的,那么现在只有一个问题了:我们能在运行时执行拼接生成的字符串代码吗?

答案也是肯定的:能!

CodeDOM:在运行时编译代码

回忆一下我们编写的ASPX页面,它们并不是C#代码,它们本质上就是一个文本文件, 我们可以写入一些HTML标签,还有些标签上加了 runat="server" 属性, 我们还可以在页面中插入一些C#代码片段,尽管它们不是我们编译后的DLL文件,然而它们就是运行起来了! 要知道ASP.NET不是ASP,ASP是解释性的脚本语言,而ASP.NET是以编译方式运行的, 所以,每个ASPX页面文件最后都是运行编译后的结果。

假设我有下面一段文本(文本的内容是一段C#代码):

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace OptimizeReflection
{
    public class DemoClass
    {
        public int Id { get; set; }

        public string Name;

        public int Add(int a, int b)
        {
            return a + b;
        }
    }

    public class 用户手册
    {
        public static void Main()
        {
            // OptimizeReflection 这个类库提供了一些扩展方法,它们用于优化常见的反射场景
            // 下面是一些相关的演示示例。
            
            // 对于属性的读写操作、方法的调用操作,还提供了性能更好的强类型(泛型)版本,可参考Program.cs

            Type instanceType = typeof(DemoClass);
            PropertyInfo propertyInfo = instanceType.GetProperty("Id");
            FieldInfo fieldInfo = instanceType.GetField("Name");
            MethodInfo methodInfo = instanceType.GetMethod("Add");

            // 1. 创建实例对象
            DemoClass obj = (DemoClass)instanceType.FastNew();

            // 2. 写属性
            propertyInfo.FastSetValue(obj, 123);
            propertyInfo.FastSetValue2(obj, 123);

            // 3. 读属性
            int a = (int)propertyInfo.FastGetValue(obj);
            int b = (int)propertyInfo.FastGetValue2(obj);

            // 4. 写字段
            fieldInfo.FastSetField(obj, "Fish Li");

            // 5. 读字段
            string s = (string)fieldInfo.FastGetValue(obj);

            // 6. 调用方法
            int c = (int)methodInfo.FastInvoke(obj, 1, 2);
            int d = (int)methodInfo.FastInvoke2(obj, 3, 4);

            Console.WriteLine("a={0}; b={1}; c={2}; d={3}; s={4}", a, b, c, d, s);
        }
    }
}

您可以把上面这段文本想像成前面第二个版本的LoadDataFromHttpRequest方法,如果我们在运行时使用反射也能生成那样的代码, 现在就差把它编译成程序集了。下面的代码演示了如何将一段文本编译成程序集的过程:

string code = null;

// 1. 生成要编译的代码。(示例为了简单直接从程序集内的资源中读取)
Stream stram = typeof(CodeDOM).Assembly
            .GetManifestResourceStream("TestOptimizeReflection.用户手册.txt");
using( StreamReader sr = new StreamReader(stram) ) {
    code = sr.ReadToEnd();
}

//Console.WriteLine(code);

// 2. 设置编译参数,主要是指定将要引用哪些程序集
CompilerParameters cp = new CompilerParameters();
cp.GenerateExecutable = false;
cp.GenerateInMemory = true;
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("OptimizeReflection.dll");

// 3. 获取编译器并编译代码
// 由于我的代码使用了【自动属性】特性,所以需要 C# .3.5版本的编译器。
// 获取与CLR匹配版本的C#编译器可以这样写:CodeDomProvider.CreateProvider("CSharp")

Dictionary<string, string> dict = new Dictionary<string, string>();
dict["CompilerVersion"] = "v3.5";
dict["WarnAsError"] = "false";

CSharpCodeProvider csProvider = new CSharpCodeProvider(dict);
CompilerResults cr = csProvider.CompileAssemblyFromSource(cp, code);

// 4. 检查有没有编译错误
if( cr.Errors != null && cr.Errors.HasErrors ) {
    foreach( CompilerError error in cr.Errors )
        Console.WriteLine(error.ErrorText);

    return;
}

// 5. 获取编译结果,它是编译后的程序集
Assembly asm = cr.CompiledAssembly;

整个过程分为5个步骤,它们已用注释标识出来了,这里不再重复了。

如何调用编译结果

前面的代码把一段文本字符串编译成了程序集,现在还有最后一个问题:如何调用编译结果?

答案:有二种方法,
1. 直接调用方法。
2. 实例化程序集中的类型,以接口方式调用方法。
其实这二种方法都需要使用反射,用反射定位到要调用的类型和方法。

第一种方法要求在生成代码时,生成的类名和方法名是明确的,在调用方法时,我们有二个选择:
1. 用反射的方式调用(这里只是一次反射)。
2. 为方法生成委托(用上篇博客介绍的方法),然后基于委托调用。

第二种方法要求在生成代码时,首先要定义一个接口,保证生成的代码能实现指定的接口,
然而用反射找到要调用的类型名称,用反射或者委托调用构造方法创建类型实例,最后基于接口去调用。
我们熟悉的ASPX页面就是采用了这种方式来实现的。

这二种方法也可以这样区分:
1. 如果生成的方法是静态方法,应该选择第一种方法。
2. 如果生成的方法是实例方法,那么选择第二种方法是合理的。

对于前面的示例,我采用了第一种方法了,因为类名和方法名称都是事先确定的而且实现起来比较简单。

// 6. 找到目标方法,并调用
Type t = asm.GetType("OptimizeReflection.用户手册");
MethodInfo method = t.GetMethod("Main");
method.Invoke(null, null);



能不能不使用委托? 如何用好CodeDOM?
在这篇博客中我不知道把它们安排在哪里较为合适,算了,还是把答案留给下篇博客吧。

博客中所有代码将在后续博客中给出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
无论是工作学习,不断的总结是必不可少的。只有不断的总结,发现问题,弥补不足,才能长久的进步!!Java学习更是如此,知识点总结目录如下: 目录 一、 Java概述 3 二、 Java语法基础 5 数据类型 5 运算符号 14 语句 15 函数 15 方法重载(Overloadjing)与重写(Overriding) 16 数组 17 总结 18 三、 常见关键字 20 四、 面向对象★★★★★ 21 五、 封装(面向对象特征之一)★★★★ 23 六、 继承(面向对象特征之一)★★★★ 25 七、 接口(面向对象特征之一)★★★★ 28 八、 多态(面向对象特征之一)★★★★ 30 九、 java.lang.Object 31 十、 异常★★★★ 34 十一、 包(package) 37 十二、 多线程★★★★ 39 为什么要使用多线程 39 创建线程和启动 39 线程的生命周期 44 线程管理 45 线程同步 49 线程通信 52 线程池 58 死锁 64 线程相关类 65 十三、 同步★★★★★ 67 十四、 Lock接口 70 十五、 API 71 String字符串:★★★☆ 71 StringBuffer字符串缓冲区:★★★☆ 73 StringBuilder字符串缓冲区:★★★☆ 74 基本数据类型对象包装类★★★☆ 75 集合框架:★★★★★,用于存储数据的容器。 76 Collection接口 77 Iterator接口 78 List接口 78 Set接口 80 Map接口 81 把map集合转成set的方法 82 使用集合的技巧 83 Collections--集合工具类 83 Arrays—数组对象工具类 84 增强for循环 85 可变参数(...) 86 枚举:关键字 enum 86 自动拆装箱 86 泛型 87 System 89 Runtime 90 Math 90 .Date:日期类,月份从0—11 92 Calendar:日历类 93 十六、 IO流:用于处理设备上数据 ★★★★★ 94 IO流的概念 95 字符流与字节流 98 流对象 101 File类 102 Java.util.Properties 103 介绍IO包扩展功能的流对象 103 十七、 网络编程 110 网络基础之网络协议篇 111 UDP传输 124 TCP传输 126 十八、 反射技术 127 十九、 Ajax原理及实现步骤★★★★★ 130 Ajax概述 130 Ajax工作原理 130 Ajax实现步骤 130 详解区分请求类型: GET或POST 131 $.ajax标准写法 134 二十、 正则表达式:其实是用来操作字符串的一些规则★★★☆ 135 二十一、 设计模式★★★★★ 136 设计模式简介 136 单例设计模式:★★★★★ 156 工厂模式★★★★★ 159 抽象工厂模式★★★★★ 163 建造者模式 170 原型模式 177 适配器模式 182 桥接模式 188 过滤器模式 192 组合模式 193 装饰器模式★★★★★ 196 外观模式 201 享元模式 204 代理模式★★★★★ 208 责任链模式 212 命令模式 216 解释器模式 219 迭代器模式 222 介者模式 224 备忘录模式 226 观察者模式 230 状态模式 233 空对象模式 236 策略模式★★★★★ 238 模板模式 240 访问者模式 244 设计模式总结★★★★★ 247 二十二、 Java其他总结 248 Java JVM知识点总结 248 equals()方法和hashCode()方法 270 数据结构 273 Array方法类汇总 304 Java数组与集合小结 305 递归 309 对象的序列化 310 Java两种线程类:Thread和Runnable 315 Java锁小结 321 java.util.concurrent.locks包下常用的类 326 NIO(New IO) 327 volatile详解 337 Java 8新特性 347 Java 性能优化 362
Java是一种广泛使用的面向对象的编程语言,由Sun Microsystems公司于1995年5月正式发布。它的设计目标是“一次编写,到处运行(Write Once, Run Anywhere)”,这意味着开发者可以使用Java编写应用程序,并在支持Java的任何平台上无需重新编译即可运行,这得益于其独特的跨平台性,通过Java虚拟机(JVM)实现不同操作系统上的兼容。 Java的特点包括: 面向对象:Java全面支持面向对象的特性,如封装、继承和多态,使得代码更易于维护和扩展。 安全:Java提供了丰富的安全特性,如禁止指针运算、自动内存管理和异常处理机制,以减少程序错误和恶意攻击的可能性。 可移植性:Java字节码可以在所有安装了JVM的设备上执行,从服务器到嵌入式系统,再到移动设备和桌面应用。 健壮性与高性能:Java通过垃圾回收机制确保内存的有效管理,同时也能通过JIT编译器优化来提升运行时性能。 标准库丰富:Java拥有庞大的类库,如Java SE(Java Standard Edition)包含基础API,用于开发通用应用程序;Java EE(Java Enterprise Edition)提供企业级服务,如Web服务、EJB等;而Java ME(Java Micro Edition)则针对小型设备和嵌入式系统。 社区活跃:Java有着全球范围内庞大的开发者社区和开源项目,持续推动技术进步和创新。 多线程支持:Java内建对多线程编程的支持,使并发编程变得更加简单直接。 动态性:Java可以通过反射、注解等机制实现在运行时动态加载类和修改行为,增加了程序的灵活性。 综上所述,Java凭借其强大的特性和广泛的适用范围,在企业级应用、互联网服务、移动开发等领域均扮演着举足轻重的角色,是现代软件开发不可或缺的重要工具之一。
面试必备,java基础、java集合、JVM、Java并发、数据结构与算法、LeetCode、剑指offer、计算机网络、设计模式、MySQL、Redis Java是一种广泛使用的面向对象的编程语言,由Sun Microsystems公司于1995年5月正式发布。它的设计目标是“一次编写,到处运行(Write Once, Run Anywhere)”,这意味着开发者可以使用Java编写应用程序,并在支持Java的任何平台上无需重新编译即可运行,这得益于其独特的跨平台性,通过Java虚拟机(JVM)实现不同操作系统上的兼容。 Java的特点包括: 面向对象:Java全面支持面向对象的特性,如封装、继承和多态,使得代码更易于维护和扩展。 安全:Java提供了丰富的安全特性,如禁止指针运算、自动内存管理和异常处理机制,以减少程序错误和恶意攻击的可能性。 可移植性:Java字节码可以在所有安装了JVM的设备上执行,从服务器到嵌入式系统,再到移动设备和桌面应用。 健壮性与高性能:Java通过垃圾回收机制确保内存的有效管理,同时也能通过JIT编译器优化来提升运行时性能。 标准库丰富:Java拥有庞大的类库,如Java SE(Java Standard Edition)包含基础API,用于开发通用应用程序;Java EE(Java Enterprise Edition)提供企业级服务,如Web服务、EJB等;而Java ME(Java Micro Edition)则针对小型设备和嵌入式系统。 社区活跃:Java有着全球范围内庞大的开发者社区和开源项目,持续推动技术进步和创新。 多线程支持:Java内建对多线程编程的支持,使并发编程变得更加简单直接。 动态性:Java可以通过反射、注解等机制实现在运行时动态加载类和修改行为,增加了程序的灵活性。 综上所述,Java凭借其强大的特性和广泛的适用范围,在企业级应用、互联网服务、移动开发等领域均扮演着举足轻重的角色,是现代软件开发不可或缺的重要工具之一。
### 回答1: Matlab计算反射阵相位补偿可以通过以下步骤实现。 首先,我们需要使用Matlab的傅里叶变换函数对原始信号进行傅里叶变换。傅里叶变换可以将信号从时域转换到频域。 然后,我们可以根据频域的信号得到反射阵的频率响应。对于反射阵的相位补偿,我们需要计算并记录频率响应的相位信息。 接下来,我们可以使用Matlab的频域操作函数,例如angle函数或unwrap函数,对频率响应的相位进行计算和调整。这样可以获得反射阵需要的相位补偿。 最后,我们需要对相位补偿进行逆傅里叶变换,将其转换回时域。可以使用Matlab的逆傅里叶变换函数对相位补偿进行逆变换。 总结起来,使用Matlab计算反射阵相位补偿的步骤包括:傅里叶变换原始信号、计算反射阵频率响应、计算和调整频率响应的相位信息、逆傅里叶变换相位补偿。 ### 回答2: Matlab是一种强大的数学软件,提供了众多函数和工具箱来进行各种计算任务,包括反射阵相位补偿。 反射阵相位补偿是一种通过旋转每个天线元素的相位来调整信号的相位,以改善通信性能的技术。下面是使用Matlab计算反射阵相位补偿的一般步骤: 1. 确定反射阵的布局和天线元素的数量。这可以通过定义一个复数矩阵来表示反射阵的相位,并确定每个元素的初始相位。 2. 根据设计需求和目标来确定相位补偿的方法。常见的相位补偿方法包括最小均方误差(MMSE)、线性最小方差(LMS)和最大信噪比(MSNR)等。 3. 使用Matlab的矩阵运算功能来计算相位补偿矩阵。根据选择的相位补偿方法,可以使用线性代数运算和优化算法来求解。 4. 调整天线元素的相位。根据计算得到的相位补偿矩阵,可以通过旋转每个天线元素的相位来实现相位补偿。这可以通过将计算得到的相位补偿矩阵与初始相位矩阵相乘来实现。 5. 进行信号的传输和接收测试。通过使用相位补偿后的反射阵,可以进行通信系统的性能测试和评估。 总之,Matlab提供了丰富的数学和信号处理工具,可以很方便地计算反射阵相位补偿。使用Matlab进行反射阵相位补偿可以准确、高效地完成计算任务,并帮助改善通信系统的性能。 ### 回答3: MATLAB可以通过调用信号处理工具箱的函数来计算反射阵的相位补偿。以下为一种可能的方法: 1. 首先,将反射阵的数据存储在一个矩阵,假设该矩阵名为reflect_array。假设矩阵的行数为m,列数为n。 2. 创建一个大小为m的向量,用于存储计算得到的相位补偿值,假设该向量名为phase_compensation。 3. 使用循环遍历矩阵的每一列,对每一列进行相位补偿。 4. 对于每一列,使用fft函数进行傅里叶变换,并计算得到幅度谱和相位谱。 5. 计算幅度谱的最大值的索引,假设为max_index。 6. 对相位谱进行如下操作,计算得到相位补偿值: a. 将相位谱的值复制到一个新的向量,假设为phase_spectrum。 b. 对于max_index之外的索引,将对应位置的相位谱值加上一个常数pi。 c. 对新的相位谱进行反傅里叶变换,得到反投射阵的相位补偿值。 7. 将计算得到的相位补偿值存储到phase_compensation向量的对应位置。 8. 完成循环后,phase_compensation向量存储的就是反射阵的相位补偿值。 需要注意的是,以上仅是一种可能的方法,实际操作可能因为具体的反射阵数据形式和使用的函数而有所不同。因此,在具体应用,可能需要根据实际情况对方法进行适当调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值