ValueType.Equals(null)的底层实现及CLR虚拟机对其结构支持
在定义任何一个ValueType之后,它都是从System.ValueType继承过来的,默认的就继承了Equals方法和GetHashCode方法,在使用的时候,必须主意的是最好重写自定义ValueType的这两个方法,因为可能带来性能上面的严重问题或者是比较的不正确。
譬如定义下面这样的一个结构体值类型:
struct TestValueType
{
public int Myint;
public float Myfloat;
}
TestValueType V1,V2;
V1.Equals(V2);
可以翻开sscli看看ValueType的equals方法的实现:
public override bool Equals (Object obj)
{
if (null==obj) {
return false;
}
RuntimeType thisType = (RuntimeType)this.GetType();
RuntimeType thatType = (RuntimeType)obj.GetType();
if (thatType!=thisType) {
return false;
}
Object thisObj = (Object)this;
Object thisResult, thatResult;
// if there are no GC references in this object we can avoid reflection
// and do a fast memcmp
if (CanCompareBits(this))
return FastEqualsCheck(thisObj, obj);
FieldInfo[] thisFields = thisType.GetFields(BindingFlags.Instance
| BindingFlags.Public | BindingFlags.NonPublic);
for (int i=0; i<thisFields.Length; i++) {
thisResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(thisObj,false);
thatResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(obj, false);
if (thisResult == null) {
if (thatResult != null)
return false;
}
else
if (!thisResult.Equals(thatResult)) {
return false;
}
}
return true;
}
首先得到比较两个对象的Type,如果类型不一样就直接返回。
然后对于能够进行快速比较的值类型,就使用快速比较:
// if there are no GC references in this object we can avoid reflection
// and do a fast memcmp
if (CanCompareBits(this))
return FastEqualsCheck(thisObj, obj);
从上面的代码可以看到,如果不是进行快速比较的话,要使用反射来枚举出所有的成员变量然后比较,这样的开销还是比较大的。
那么,在什么情况下不会进行快速比较而是采用枚举成员变量比较的方法呢?
看看CanCompareBits方法的实现:
// Return true if the valuetype does not contain pointer and is tightly packed
FCIMPL1(FC_BOOL_RET, ValueTypeHelper::CanCompareBits, Object* obj)
{
WRAPPER_CONTRACT;
STATIC_CONTRACT_SO_TOLERANT;
_ASSERTE(obj != NULL);
MethodTable* mt = obj->GetMethodTable();
FC_RETURN_BOOL(!mt->ContainsPointers() && !mt->IsNotTightlyPacked());
}
FCIMPLEND
在最后一行里面做出的判断:如果方法体里面不包含Pointers,GC需要用到的,同时是Tightly Packed的时候,就会调用FastEqualsCheck进行快速比较。
FastEqualsCheck方法的实现如下:
FCIMPL2(FC_BOOL_RET, ValueTypeHelper::FastEqualsCheck, Object* obj1, Object* obj2)
{
WRAPPER_CONTRACT;
STATIC_CONTRACT_SO_TOLERANT;
_ASSERTE(obj1 != NULL);
_ASSERTE(obj2 != NULL);
_ASSERTE(!obj1->GetMethodTable()->ContainsPointers());
_ASSERTE(obj1->GetSize() == obj2->GetSize());
TypeHandle pTh = obj1->GetTypeHandle();
FC_RETURN_BOOL(memcmp(obj1->GetData(),obj2->GetData(),pTh.GetSize()) == 0);
}
FCIMPLEND
是调用了memcmp方法,来直接对内存进行一个bit一个bit的比较。这样就可以快很多。
接下来剩下是什么时候调用快速比较方法呢?
看看判断是否符合快速比较的判断:
// Note: This flag MUST be available even from an unrestored MethodTable –
// GcScanRoots in siginfo.cpp.
DWORD ContainsPointers()
{
LEAF_CONTRACT;
return(m_wFlags & enum_flag_ContainsPointers);
}
BOOL IsNotTightlyPacked()
{
LEAF_CONTRACT;
return (m_wFlags & enum_flag_NotTightlyPacked);
}
m_wFlags是一个DWORD来标识了很多在对象运行时候的信息,转到这两个枚举类型的定义:
enum_flag_ContainsPointers = 0x00080000,
enum_flag_NotTightlyPacked = 0x04000000,
enum_flag_NotTightlyPacked专门在m_wFlags占了一位来标识这个对象ValueType是不是能够进行bit位的比较。而且它只是在ContainsPointer为false的时候才起作用。因为如果包含对对象的引用的话就不能进行bit位的比较了。
而对于m_wFlags中个能否进行ValueType的bit位比较的这个bit的设置,也有定义:
if (IsValueClass() && (GetNumInstanceFieldBytes() != totalDeclaredFieldSize
|| HasOverLayedField()))
{
pMT->SetNotTightlyPacked();
}
到这里了,会有人说,咦,这微软不已经做的很好了么,可以bit比较的就快速比较,不行的就枚举成员变量来比较,为什么要重写呢?
真正的问题在于,CanCompareBits方法,并不能总是得到正确的结果。假设一下,在上面定义的结构体中,有两个实例a和b,
Set a.float=-0.0;
Set b.float=+0.0;
这样的比较,在逻辑上面是相等的,但是在jit优化之前,这两个0用memcmp比较的结果是true,优化之后,比较的结果是false。也就是优化了以后,这两个逻辑上面相等的变量在内存里面的表示方法是不一样的。
类似的道理,如果定义的struct里面包含了重写的equals方法,优化了之后,得到的比较结果就不一定正确了。
这ms是微软的一个bug,当然,这里只是妄加揣测,并没有做严格的测试。Have not got much time for that.
恩,这里是参考了http://blog.csdn.net/nineforever/archive/2008/10/12/3062289.aspx的这个假设才敢这么说的..........当然,在本文中还是假设。
当然,还有一种情况是CLR在build Object的内存布局的时候就考虑到这种情况避免了这个问题。就是在build一个valuetype的methodtable的时候,就考虑到了这样的一种情况,直接制定了这个bit位的值。也就是
m_wFlags对应的0x04000000这个地方的一个bit位。
来验证这个想法:
首先找到设置这个bit位的地方,直接搜索enum_flag_NotTightlyPacked这个枚举类型,得到的结果集合最小,一共三个结果,很容易就看到了设置这个值的地方:
void SetNotTightlyPacked()
{
LEAF_CONTRACT;
m_wFlags |= enum_flag_NotTightlyPacked;
}
然后寻找对这个方法的引用,得到在什么情况下设置这个bit位的判断条件:
if (IsValueClass() && (GetNumInstanceFieldBytes() != totalDeclaredFieldSize
|| HasOverLayedField()))
{
pMT->SetNotTightlyPacked();
}
e:\Projects\Rotor\sscli20\clr\src\vm\class.cpp
中间的一个判断可以不看,和这里讨论的东西关系不大,主要就是IsValueClass()的判断和HasOverLayedField()这两个方法比较可疑。
首先看看第一个方法IsValueClass的实现:
inline DWORD IsValueClass()
{
LEAF_CONTRACT;
STATIC_CONTRACT_SO_TOLERANT;
return (m_VMFlags & VMFLAG_VALUETYPE);
}
VMFLAG_VALUETYPE = 0x00800000,
仅仅校验了m_VMFlags中的VMFLAG_VALUETYPE这个位的值。那就找m_VMFlags中的VMFLAG_VALUETYPE这个值对应的bit位是在哪儿被设置的:
inline void SetValueClass()
{
LEAF_CONTRACT;
g_IBCLogger.LogEEClassCOWTableAccess(this);
FastInterlockOr(&m_VMFlags,VMFLAG_VALUETYPE);
}
然后找这个函数是在哪儿被调用的,找了半天,终于找到了这个函数被调用的地方,因为调用这个方法的地方比较多,这里就选取在build method Table的时候的调用:
// Check to see if the class is an valuetype
// these build a value type response to later ValueType.Equals methods....
if(pParentMethodTable != NULL && ((g_pEnumClass != NULL && pParentMethodTable == g_pValueTypeClass) || pParentMethodTable == g_pEnumClass))
{
SetValueClass();
// Cutttttttted
}
这个方法存在于MethodTableBuilder::BuildMethodTableThrowing,文件地址位于
e:\Projects\Rotor\sscli20\clr\src\vm\class.cpp。这里只是根据程序语言的定义,如果是从ValueType或者是枚举类型继承过来的,就判断为ValueType。
OK了,就剩下最后一个假设,在HasOverLayedField方法中做出的判断。看看它的实现:
BOOL HasOverLayedField()
{
LEAF_CONTRACT;
STATIC_CONTRACT_SO_TOLERANT;
return m_VMFlags & VMFLAG_HASOVERLAYEDFIELDS;
}
寻找引用,找到了对这个bit进行设置的方法:
inline void SetHasOverLayedFields()
{
LEAF_CONTRACT;
g_IBCLogger.LogEEClassCOWTableAccess(this);
FastInterlockOr(&m_VMFlags,VMFLAG_HASOVERLAYEDFIELDS);
}
在MethodTableBuilder::HandleExplicitLayout中找到了对这个方法的调用:
if(!explicitClassTrust.IsNonOverLayed())
{
SetHasOverLayedFields();
}
HandleExplicitLayout这个方法是在build一个实例的MethodTable的时候,用来处理额外build method中未考虑的情况。
这里,使用了一个trust等级来标识是不是需要设置这个值。转到IsNonOverLayed的定义中:
BOOL IsNonOverLayed()
{
LEAF_CONTRACT;
return m_trust >= kNonOverLayed;
}
查看kNonOverLayed的定义,以上所有的问题都得到了解释:
E:\Rotor\sscli20\clr\src\vm\class.h
Enum TrustLevel:
|
|
What's guaranteed. |
What the loader does |
Knoe |
0 |
no guarantees at all |
Type refuses to load at all. |
Klegal |
1 |
guarantees no objref <-> scalar overlap and no unaligned objref |
T Type loads but field access won't verify |
kVerifiable |
2 |
guarantees no objref <-> objref overlap and all guarantees above |
Type loads and field access will verify |
kNonOverLayed |
3 |
guarantees no overlap at all and all guarantees above |
Type loads, field access verifies and Equals() may be optimized if structure is tightly packed |
在上面的程序中,使用的是kNonOverLayed,这里,考虑到了Equals方法被优化了之后会出现的情况。
到这里,上面的所有假设,经过一段漫长的查找实现过程,终于得到了证实。
Tuesday, March 03, 2009 20:42:49
posted on 2009-03-03 21:01 lbq1221119 阅读(2376) 评论(1) 编辑 收藏 举报