Attribute操作的性能优化方式
Attribute是.NET平台上提供的一种元编程能力,可以通过标记的方式来修饰各种成员。无论是组件设计,语言之间互通,还是最普通的框架使 用,现在已经都离不开Attribute了。迫于Attribute的功能的重要性(Kent Beck认为NUnit比早期JUnit设计的好,一个主要方面便是利用了Attribute),Java语言也在5.0版本中引入了与 Attribute类似的Annotation概念。不过Attribute说到底也是一种反射操作,平时正常使用不会带来问题,但是密集的调用还是对性 能有一定影响的。这次我们就来总结看看我们究竟可以如何回避Attribute操作的一些性能问题。
源码:http://www.jinhusns.com/Products/Download/?type=xcj
假设我们有一个Attribute,它定义在一个类型上:
01.
[AttributeUsage(AttributeTargets.Class,
02.
AllowMultiple =
true
,
03.
Inherited =
true
)]
04.
public
class
TestAttribute : Attribute
05.
{
06.
public
TestAttribute(
string
prop)
07.
{
08.
this
.Prop = prop;
09.
}
10.
11.
public
TestAttribute() { }
12.
13.
public
string
Prop {
get
;
set
; }
14.
}
15.
16.
[Test(
"Hello World"
)]
17.
[Test(Prop =
"Hello World"
)]
18.
public
class
SomeClass { }
那么,如果我们需要获得SomeClass类型上所标记的TestAttribute,我们一般会使用Type对象的GetCustomAttributes方法。那么在其中又发生了什么呢?
通过.NET Reflector来追踪其中实现,会发现这些逻辑最终是由CustomAttribute的GetCustomAttributes方法完成的,感兴趣 的朋友们可以找到那个最复杂的重载。由于实现有些复杂,我没有看懂完整的逻辑,但从关键的代码上可以看出,它其实是使用了 Activator.CreateInstance方法创建对象,并且使用反射对Attribute对象的属性进行设置。于是我便打算了解一下这些反射操 作占整个GetCustomAttributes方法的多大比重:
01.
CodeTimer.Time(
"GetCustomAttributes"
, 1000 * 100, () =>
02.
{
03.
var attributes =
typeof
(SomeClass).GetCustomAttributes(
typeof
(TestAttribute),
true
);
04.
});
05.
06.
CodeTimer.Time(
"Reflection"
, 1000 * 100, () =>
07.
{
08.
var a1 = (TestAttribute)Activator.CreateInstance(
typeof
(TestAttribute),
"Hello World"
);
09.
var a2 = (TestAttribute)Activator.CreateInstance(
typeof
(TestAttribute));
10.
typeof
(TestAttribute).GetProperty(
"Prop"
).SetValue(a2,
"Hello World"
,
null
);
11.
});
结果如下:
GetCustomAttributes
Time Elapsed: 2,091ms
CPU Cycles: 5,032,765,488
Gen 0: 43
Gen 1: 0
Gen 2: 0
Reflection
Time Elapsed: 527ms
CPU Cycles: 1,269,399,624
Gen 0: 40
Gen 1: 0
Gen 2: 0
可以看出,虽然GetCustomAttributes方法中使用了反射进行对象的创建和属性设置,但是它的大部分开销还是用于获取一些元数据的,
它们占据了3/4的时间,而反射的开销其实只占了1/4左右。这就有些令人奇怪了,既然是静态的元数据,为什么.NET
Framework不对这些数据进行缓存,而是每次再去取一次呢?即便是我们不应该缓存最后得到的Attribute对象,但是用于构造对象的“信息”是完全可以缓存下来的。
事实上,经由上次heros同学指出,.NET Framework事实上已经给出了足够的信息,那便是CustomAttributeData的
GetCustomAttributes方法,它返回的是IList<CustomAttributeData>对象,其中包含了构造
Attribute所需要的全部信息。换句话说,我完全可以根据一个CustomAttributeData来“快速构建”Attribute对象:
01.
public
class
AttributeFactory
02.
{
03.
public
AttributeFactory(CustomAttributeData data)
04.
{
05.
this
.Data = data;
06.
07.
var ctorInvoker =
new
ConstructorInvoker(data.Constructor);
08.
var ctorArgs = data.ConstructorArguments.Select(a => a.Value).ToArray();
09.
this
.m_attributeCreator = () => ctorInvoker.Invoke(ctorArgs);
10.
11.
this
.m_propertySetters =
new
List<Action<
object
>>();
12.
foreach
(var arg
in
data.NamedArguments)
13.
{
14.
var property = (PropertyInfo)arg.MemberInfo;
15.
var propertyAccessor =
new
PropertyAccessor(property);
16.
var value = arg.TypedValue.Value;
17.
this
.m_propertySetters.Add(o => propertyAccessor.SetValue(o, value));
18.
}
19.
}
20.
21.
public
CustomAttributeData Data {
get
;
private
set
; }
22.
23.
private
Func<
object
> m_attributeCreator;
24.
private
List<Action<
object
>> m_propertySetters;
25.
26.
public
Attribute Create()
27.
{
28.
var attribute =
this
.m_attributeCreator();
29.
30.
foreach
(var setter
in
this
.m_propertySetters)
31.
{
32.
setter(attribute);
33.
}
34.
35.
return
(Attribute)attribute;
36.
}
37.
}
AttributeFactory利用了FastReflectionLib,将ConstructorInfo和PropertyInfo封装成性能很高的ConstructorInvoker和PropertyAccessor对象,这样使用起来便有数量级的性能提高。我们再来进行一番测试:
var factories = CustomAttributeData.GetCustomAttributes(typeof(SomeClass))
.Where(d => d.Constructor.DeclaringType == typeof(TestAttribute))
.Select(d => new AttributeFactory(d)).ToList();
CodeTimer.Time("GetCustomAttributes", 1000 * 100, () =>
{
var attributes = typeof(SomeClass).GetCustomAttributes(typeof(TestAttribute), true);
});
CodeTimer.Time("AttributeFactory", 1000 * 100, () => factories.ForEach(f => f.Create()));
结果如下:
GetCustomAttributes
Time Elapsed: 2,131ms
CPU Cycles: 5,136,848,904
Gen 0: 43
Gen 1: 43
Gen 2: 0
Attribute Factory
Time Elapsed: 18ms
CPU Cycles: 44,235,564
Gen 0: 4
Gen 1: 4
Gen 2: 0
在这里,我们先获得SomeClass中所有定义过的CustomAttributeData对象,然后根据其Constructor的类型来判断
哪些是用于构造TestAttribute对象的,然后用它们来构造AttributeFactory。在实际使用过程
中,AttributeFactory实例可以缓存下来,并反复使用。这样的话,我们即可以每次得到新的Attribute对象,又可以避免
GetCustomAttributes方法所带来的莫名其妙的开销。
事实上,我们完全可以利用这个方法,来实现一个性能更高的GetCustomAttributesEx方法,它的行为可以和.NET自带的
GetCustomAttributes完全一致,但是性能可以快上无数——可能是100倍。不过,这个方法虽然不难编写,但比较麻烦。因为
CustomAttributeData只能用于获得“直接定义”在某个成员上的数据,而实际情况是,我们往往还必须根据某个Attribute上标记的
AttributeUsage的AllowMultiple和Inherited属性来决定是否要遍历整个继承链。只有这般,我们才能百分之百地重现
GetCustomAttribute方法的行为。
不过我们在这里有个优势,那便是“静态”。一旦“静态”,我们便可以为某个特定的场景,用“肉眼”判断出特定的处理方式,这样便不需要一个非常通用
的GetCustomAttributeEx方法了。例如在实际使用过程中,我们可以可以发现某个Attribute的Inherited属性为
false,那么我们便可以免去遍历继承链的麻烦。
最后还有两点可能值得一提:
除了Type,Assembly等成员自带的GetCustomAttributes方法之外,Attribute类也有些静态
GetCustomAttributes方法可用于获取Attribute对象。但是,通过.NET
Reflector,我们可以发现,Attribute类中的静态方法,最终还是委托给各自的实例方法,因此不会有性能提高。唯一区别对待的是
ParameterInfo——不过我没搞懂为什么那么复杂,感兴趣的朋友可以自行探索一番。
如果仅仅是判断一个成员是否定义了某个特定类型的Attribute对象,那么可以使用Attribute.IsDefined静态方法。它的性能
比GetCustomAttributes后再判断数组的Length要高效许多倍。不过个人认为这点倒并不是非常重要,因为这原本就是个静态的信息,即
便是我们使用较慢的GetCustomAttributes方法来进行判断,也可以把最终的true或false结果进行缓存,这自然也不会有性能问题
了。
我们之所以要反复调用GetCustomAttributes方法,就是因为每次得到的Attribute对象都是新建的,因此在某些场景下可能无法缓存它们。不过现在已经有了现在更快的做法,在这方面自然也就不会有太大问题了。
Attribute操作的性能优化方式的更多相关文章
- 前端性能优化--为什么DOM操作慢? 浅谈DOM的操作以及性能优化问题-重绘重排 为什么要减少DOM操作 为什么要减少操作DOM
前端性能优化--为什么DOM操作慢? 作为一个前端,不能不考虑性能问题.对于大多数前端来说,性能优化的方法可能包括以下这些: 减少HTTP请求(合并css.js,雪碧图/base64图片) 压缩( ...
- 常见 Web 性能优化方式
这篇文章是我阅读 Web Performance 101 之后的进行的粗糙的翻译作为笔记,英语还行的童鞋可以直接看原文. 这篇文章主要介绍了现代 web 加载性能(注意不涉及代码算法等),学习为什么加 ...
- DOM操作的性能优化
DOM操作的真正问题在于 每次操作都会出发布局的改变.DOM树的修改和渲染. React解决了大面积的DOM操作的性能问题,实现了一个虚拟DOM,即virtual DOM,这个我们一条条讲. 所以关于 ...
- 关于DOM操作的性能优化
最著名的有关用js操作dom的观点是:js和dom是独立的小岛,用桥实现两者的联系,但桥很窄,要过路费,所以我们要尽最大可能减少过桥的次数.下面代码演示了用js操作dom的innerHTML,且一下修 ...
- react 实用的性能优化方式
react 组件渲染分为初始化渲染和更新渲染,当我们更新某个组件的时候,只是想关键路径上组件的render,但react的默认做法是调用所以组件的reder,再生成虚拟dom进行对比,如不变则不进行更 ...
- 学习 CLR 源码:连续内存块数据操作的性能优化
目录 C# 原语类型 1,利用 Buffer 优化数组性能 2,BinaryPrimitives 细粒度操作字节数组 提高代码安全性 3,BitConverter.MemoryMarshal 4,Ma ...
- 关于DOM的操作以及性能优化问题-重绘重排
写在前面: 大家都知道DOM的操作很昂贵. 然后贵在什么地方呢? 一.访问DOM元素 二.修改DOM引起的重绘重排 一.访问DOM 像书上的比喻:把DOM和JavaScript(这里指ECMScri ...
- 【redis常用的键值操作及性能优化】
服务端 启动redis服务 { // -a:指定密码 -h:指定主机 -p:指定端口 } //让redis 服务中断崩溃 //保存和关闭 //后台备份 //设置登录密码 //redis-benchma ...
- web开发常见性能优化方式
经常使用的高并发. 高性能web,数据库server. 1.html 静态化 : 如新闻频道更新的非常快,都是通过cms静态生成(门户,信息公布类型的站点,交互性高的如猫扑的大杂烩也是静态化,实时静 ...
随机推荐
- C++常见笔试面试要点以及常见问题
1. C++常见笔试面试要点: C++语言相关: (1) 虚函数(多态)的内部实现 (2) 智能指针用过哪些?shared_ptr和unique_ptr用的时候需要注意什么?shared_ptr的实现 ...
- StrategyPattern (策略模式)
/** * 策略模式 * @author TMAC-J * 根据环境的不同选择不同的策略,把策略用接口抽象出来 */ public class StrategyPattern { interface ...
- JQuery实现表格的增加行和删除行
利用JQuery实现datatables插件的增加和删除行操作 在学习过程中遇到了这个利用JQuery对表格行的增加和删除,特记录下来以供初学者参考. 下面是主要的代码: <meta http- ...
- BPM SharePoint解决方案分享
一.需求分析 SharePoint作为微软推出的协同类平台产品,为客户提供了门户.内容.文档.流程.社区.搜索.BI等一系列的解决方案,然而其流程功能由于设计理念差异,不能完全满足客户的需求,主要原因 ...
- iOS从零开始学习直播之1.播放
对于直播来说,客户端主要做两件事情,推流和播放.今天先讲播放. 播放流程 1.拉流:服务器已有直播内容,从指定地址进行拉取的过程.其实就是向服务器请求数据. 2.解码:对视屏数据进行解压缩. 3. ...
- git
CMD命令:git initgit add . [添加文件至暂存区]git commit -m '描述性语句 随意写即可'git branch gh-pages [创建仓库分支]git checkou ...
- [原创]ubuntu16.04LTS使用细节
如何给自己安装的应用创建桌面图标 拿php开发神器phpstorm为例,找到可执行文件所在路径. 这里是/home/haive/PhpStorm/bin/phpstorm.sh 打开dash,搜索&q ...
- 【初探Spring】------Spring IOC(二):初始化过程---简介
首先我们先来看看如下一段代码 ClassPathResource resource = new ClassPathResource("bean.xml"); DefaultList ...
- Thinking in Unity3D
关于<Thinking in Unity3D> 笔者在研究和使用Unity3D的过程中,获得了一些Unity3D方面的信息,同时也感叹Unity3D设计之精妙.不得不说,笔者最近几年的引擎 ...
- Java垃圾回收
垃圾收集算法 引用计数 堆中的每个对象都有一个引用计数,当对象被引用时引用计数加1,当对象的引用被重新赋值或超出有效区域时引用计数减1,当一个对象被回收后,它所引用的对象的引用计算减1.当一个对象的引 ...