前略,于是就开了这项目。(群殴
于是详细情况可以到项目页看,这里就不重复贴了,欢迎大家前往围观。
昨天EP研究了一下C#4.0和VB2010的后期绑定实现,弄了两个简单的测试程序,反编译出来代码如下:
1: <STAThread> _
2: Public Shared Sub Main()
3: Dim o As Object = New ExpandoObject
4: NewLateBinding.LateCall(o, Nothing, "Test", New Object(0 - 1) {}, Nothing, Nothing, Nothing, True)
5: NewLateBinding.LateSet(o, Nothing, "NewMember", New Object() { 1 }, Nothing, Nothing)
6: End Sub
1: private static void Main(string[] args)
2: {
3: object o = new ExpandoObject();
4: if (<Main>o__SiteContainer0.<>p__Site1 == null)
5: {
6: <Main>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "Test", null, typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
7: }
8: <Main>o__SiteContainer0.<>p__Site1.Target.Invoke(<Main>o__SiteContainer0.<>p__Site1, o);
9: if (<Main>o__SiteContainer0.<>p__Site2 == null)
10: {
11: <Main>o__SiteContainer0.<>p__Site2 = CallSite<Func<CallSite, object, int, object>>.Create(Binder.SetMember(CSharpBinderFlags.None, "NewMember", typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.Constant | CSharpArgumentInfoFlags.UseCompileTimeType, null) }));
12: }
13: <Main>o__SiteContainer0.<>p__Site2.Target.Invoke(<Main>o__SiteContainer0.<>p__Site2, o, 1);
14: }
C#会给调用生成一个CallSite缓存起来,然后后续调用全部是通过这个CallSite。VB 则是全部通过 NewLateBinding 来调用,而且内部每次调用都会新建一个CallSite。
开始还以为M$脑子进水了,VB的后期绑定这样弄性能得差到一个什么程度。后来跟进CallSite的代码,发现其实CallSite里面也是有缓存机制的,内部的Delegate不会重复建立,这样的话虽然VB的性能会比C#稍差,应该也不到不能接受的地步。
这么做应该是为了兼容旧的VB6后期绑定,从代码也可以看出来,如果对象没有实现IDynamicMetaObjectProvider,就会用旧版的后期绑定方式。
这样保持兼容性感觉很不方便,VB.NET相比VB6的一大改进是Option Strict,之前写代码的时候一直开着,能减少很多编码错误,其中包括误用后期绑定。而如果要使用新的ExpendoObject和其它动态功能的话,又不能启用这个检查。
另外VB不像C#新增了dynamic关键字,要使用后期绑定得把变量声明为Object类型,使代码更容易出错。
结论:在后期绑定这方面,VB本来是前辈,不过现在反而被C#赶上了。。。该说是VB的又一个悲剧么。。。
(.NET内部的反编译代码就不贴了,各位有兴趣可以用Reflector研究一下)
——————————————–
7月11日更新:
早上突然发现上次的推理有个大漏洞,VB的NewLateBinding只会对IDynamicMetaObjectProvider使用CallSite,一般对象还是会使用老版的反射调用。这样一来多次调用的性能就远远比不上C#了。。。
之前还在想是不是GFW抽风,会不会很快就解除,现在看起来是不可能了。
这世道,官逼民反啊。 F**k GFW, F**k CCP
3.31:这次更新是@lititude的feature request,意见感谢!
VFRHelper - MKV章节制作工具(不要问我为什么叫这个名字。。。 更新日志: 1.2.4 (*)FFMS更新到2.13 (*)点击视频进度条可直接跳转到指定位置 (+)TFMOverrideEditor可使用AVS脚本作为视频 (*)其它改动略 1.2.3 (*)修复部分MKV章节文件无法加载的问题 (*)Bug修复 1.2.2 (*)FFMpegSource更新到2.0 beta 4 1.2.1 (*)Bug修复及易用性改动 1.2.0 (+)新增插件框架,章节编辑器改为插件形式 (+)新增插件:TFM OVR文件编辑器 (*)一些小改动 1.1.1 (+)新增功能:按时间跳转 1.1.0 (*)视频代码重写,速度提高 (+)FFMpegSource更新到2.0 beta 3,现在打开非avs文件不需要AviSynth了 (+)使用FFMpegSource打开文件时,在标题栏显示索引进度 (+)新增支持读取VFR MP4的Timecode (*)一些小改动及bug修复 1.0.2 (+)新增1个快捷键 (+)FFMpegSource更新到1.19 (-)使用按钮能够正常打开非AVI文件了 1.0.1 (+)新增2个快捷键 (+)快捷键现在可以自定义 (*)退出程序时会询问是否保留临时文件(如果有的话) 1.0.0 初始版本 功能: *可视化制作MKV章节文件 *支持打开TXT及XML格式的章节 *支持VFR(只支持V2的Timecode,如果是V1的话请预先转换好 *查看V2 Timecode各帧的时间(附带功能 *编辑TFM的OVR文件 支持的视频格式: AVI AVS MKV MP4 FLV MKV、MP4及FLV需要FFMS2.dll支持。 快捷键说明: 方向键左/右 跳转至上一个/下一个关键帧 Shift+方向键左/右 跳转至上一帧/下一帧(注意:跳转的时候会忽略空帧) 方向键上/下 上一个/下一个章节 空格 设置当前选中章节的时间 F12 解码速度测试(可以无视 快捷键可以自定义,使用记事本之类的工具编辑keymappings.xml即可。按键名称可查看KeyNames.htm获得。 一些注意事项: *章节文件的格式无法被改变(即只能保存为打开时候的格式) *新建章节只支持TXT格式
下载:
完整包(内附源代码)
系统需求:
.Net Framework 2.0
Windows(废话
AviSynth(可选
源代码编译需求VS2008
弄了一上午终于开始make gcc了。。。于是记录一下要点。。。
0、在/etc/profile加上以下设置可减少很多麻烦事:
export CFLAGS='-I/usr/local/include'
export CPPFLAGS='-I/usr/local/include'
export LDFLAGS='-L/usr/local/lib'
1、必须库及编译顺序:gmp –> mpfr –> polylib –> ppl –> cloog-ppl
2、必须库configure的时候全部用–enable-shared –disable-static
特例:
ppl:需要加上LDFLAGS=’-no-undefined -L/usr/local/lib’,否则编译出来的还是静态库,后面编译cloog-ppl的时候会出现一大堆undefined reference。这个错误折磨了我很久。。。
cloog-ppl:需加上–with-ppl=/usr/local/,这个是编译gcc的要求
3、必须在gcc源代码目录树的外面新建一个文件夹,并在里面configure及make(请允许我吐槽。。。这是什么鬼规定。。。
3-26更新:
(前面的写完之后又折腾了一整天,编译gcc果然非常ep。。。
4、mingw目录必须在分区根部(如D:\MingW),不然会出现很奇怪的问题。。。
5、gcc在configure的时候要显式指定1里面4个库的位置,否则stage2会出错
6、cloog-ppl也要加上ppl的ldflags,不加的话编译出来的还是静态库
7、编译libgomp的时候有可能会出现ar.exe的错误,把其它目录的libtool复制到libgomp里面覆盖掉继续make就可以。这个操作可能要重复5次以上。。。
大概就是这样了。。。另外感谢squallatf同学的大力协助,很多问题都是他解答的,让我少走很多弯路。。。
最近的项目里面用到了EF,某个功能需要做大量查询,性能很糟糕。
于是尝试把查询改成CompiledQuery,结果发现使用后性能反而下降了。祭出Profiler,结果发现:
有1/3的查询都调用了System.Data.Metadata.Edm.ObjectItemCollection.LoadAllReferencedAssemblies这个方法,时间基本全部耗在上面了。
看名字猜测,这方法似乎是用来加载查询引用的程序集的,但是理论上程序集只需要加载一次而已,感觉是EF内部的缓存机制出了问题。
Reflector反编译之,发现以下代码段:
1: ' System.Data.Objects.CompiledQuery
2: Private Function Invoke(Of TArg0 As ObjectContext, TArg1, TArg2, TArg3, TResult)(ByVal arg0 As TArg0, ByVal arg1 As TArg1, ByVal arg2 As TArg2, ByVal arg3 As TArg3) As TResult
3: EntityUtil.CheckArgumentNull(Of TArg0)(arg0, "arg0")
4: arg0.MetadataWorkspace.LoadAssemblyForType(GetType(TResult), Assembly.GetCallingAssembly)
5: Return Me.ExecuteQuery(Of TResult)(arg0, New Object() { arg1, arg2, arg3 })
6: End Function
7:
8: ' System.Data.Metadata.Edm.MetadataWorkspace
9: Friend Sub LoadAssemblyForType(ByVal type As Type, ByVal callingAssembly As Assembly)
10: Dim items As ItemCollection
11: If Me.TryGetItemCollection(DataSpace.OSpace, items) Then
12: Dim items2 As ObjectItemCollection = DirectCast(items, ObjectItemCollection)
13: If (Not items2.LoadAssemblyForType(type) AndAlso (Not callingAssembly Is Nothing)) Then
14: items2.LoadAllReferencedAssemblies(callingAssembly)
15: End If
16: End If
17: End Sub
18:
19: ' System.Data.Metadata.Edm.ObjectItemCollection
20: Friend Function LoadAssemblyForType(ByVal type As Type) As Boolean
21: Dim flag As Boolean = ObjectItemCollection.LoadAssemblyFromCache(Me, type.Assembly, False)
22: If type.IsGenericType Then
23: Dim type2 As Type
24: For Each type2 In type.GetGenericArguments
25: flag = (flag Or Me.LoadAssemblyForType(type2))
26: Next
27: End If
28: Return flag
29: End Function
30:
31:
32:
33: Private Shared Function LoadAssemblyFromCache(ByVal objectItemCollection As ObjectItemCollection, ByVal [assembly] As Assembly, ByVal loadReferencedAssemblies As Boolean) As Boolean
34: Dim flag As Boolean
35: If ObjectItemCollection.ShouldFilterAssembly([assembly].FullName) Then
36: Return False
37: End If
38:
39: ' skipped
40: End Function
41:
42: Friend Shared Function ShouldFilterAssembly(ByVal fullName As String) As Boolean
43: Return fullName.EndsWith("PublicKeyToken=b77a5c561934e089", StringComparison.OrdinalIgnoreCase)
44: End Function
b77a5c561934e089是M$程序集的PublicKeyToken,看到这里基本就明白了。CompiledQuery执行的时候会加载调用方程序集引用的所有程序集,以TResult及其泛型参数作为键来缓存(具体的流程没有深入研究,可能不太准确)。在取缓存的时候如果键类型是M$程序集里面的话就会直接跳过。
但是取缓存的代码有个bug,如果TResult和它的泛型参数全部是M$程序集里面的类型的话,LoadAssemblyForType就会返回False,导致每次查询都会调用LoadAllReferencedAssemblies,大幅拖慢速度。
项目里面的符合条件的查询有2个,刚好是1/3,分别是返回IQueryable(Of Integer)和IQueryable(Of Decimal)。知道原因之后解决方法就很简单了。新建一个容器类如PrimitiveHolder(Of T),然后把CompiledQuery里面的TResult改成容器类就可以了,如IQueryable(Of PrimitiveHolder(Of Integer))和IQueryable(Of PrimitiveHolder(Of Decimal))。
(话说这几天弄项目一直在捣鼓EF,发现问题真的很多。。。果然M$的东西第一个版本都是这样么。。。希望.Net 4.0的EF会有改进。。。
VB中建立助手类一般有两种方法,一是使用Module,另一个是用Class然后把成员全部声明为Shared。
不过这两种方法都有缺点,第一种方法由于VB的特性会污染命名空间;第二个需要显式声明Shared,如果以后要把代码转换到实例方法会很麻烦。(还有个问题是我经常忘记加,在调用成员失败的时候才想起来。。。)
最近发现了一个很简单的方法,能把两者的优点结合起来,范例:
1:
2: Namespace Utils
3: <HideModuleName()> _
4: Module Utils
5: Public Sub XXX()
6: ' ...
7: End Sub
8: End Module
9: End Module
10:
11: ' 使用方法时Import ExcelUtils的上层命名空间即可
12: Utils.XXX()
HideModuleName属性使VS的Intellisense在显示Utils命名空间的时候不显示类名,这样这个命名空间里面就只有Utils类的成员了,间接实现了静态类的效果。
刚才碰到个问题,运行SL程序后ComboBox总是显示为空白,即使设置了SelectedIndex也是一样。
弄了很久才想起来,ComboBox设置ItemsSource之后会把SelectedIndex重置为-1,所以SelectedIndex必须放在ItemsSource后面。
BlendSDK里面的交互模式很好用,不过交互的类全部从DependencyObject继承,没办法绑定很不方便。于是就弄了这个类出来:
1: Public NotInheritable Class BindingEvaluator(Of T)
2: Private Shared ReadOnly DummyProperty As DependencyProperty = DependencyProperty.RegisterAttached("Dummy", GetType(T), GetType(BindingEvaluator(Of T)), New PropertyMetadata(DependencyProperty.UnsetValue))
3:
4: Private Shared Function GetDummy(ByVal element As DependencyObject) As T
5: ' IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
6: If (element Is Nothing) Then
7: Throw New ArgumentNullException("element")
8: End If
9: Return CType(element.GetValue(BindingEvaluator(Of T).DummyProperty), T)
10: End Function
11: Private Shared Sub SetDummy(ByVal element As DependencyObject, ByVal value As T)
12: ' IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
13: If (element Is Nothing) Then
14: Throw New ArgumentNullException("element")
15: End If
16: element.SetValue(BindingEvaluator(Of T).DummyProperty, value)
17: End Sub
18:
19: Public Shared Function GetValue(ByVal source As FrameworkElement, ByVal binding As Binding) As T
20: Dim value = GetRawValue(source, binding)
21: If value Is DependencyProperty.UnsetValue Then
22: Return Nothing
23: End If
24: Return CType(value, T)
25: End Function
26:
27: Public Shared Function GetRawValue(ByVal source As FrameworkElement, ByVal binding As Binding) As Object
28: If source Is Nothing Then
29: Throw New ArgumentNullException("source", "source is nothing.")
30: End If
31: If binding Is Nothing Then
32: Throw New ArgumentNullException("binding", "binding is nothing.")
33: End If
34: Dim newBinding As New Binding() With { _
35: .BindsDirectlyToSource = binding.BindsDirectlyToSource, _
36: .Converter = binding.Converter, _
37: .ConverterCulture = binding.ConverterCulture, _
38: .ConverterParameter = binding.ConverterParameter, _
39: .Mode = BindingMode.OneTime, _
40: .Path = binding.Path _
41: }
42: If Not String.IsNullOrEmpty(binding.ElementName) Then
43: newBinding.ElementName = binding.ElementName
44: ElseIf binding.RelativeSource IsNot Nothing Then
45: newBinding.RelativeSource = binding.RelativeSource
46: ElseIf binding.Source IsNot Nothing Then
47: newBinding.Source = binding.Source
48: Else
49: ' newBinding.Source = source.DataContext
50: End If
51:
52: source.SetBinding(DummyProperty, newBinding)
53: Dim value = GetDummy(source)
54: source.ClearValue(DummyProperty)
55:
56: Return value
57: End Function
58:
59: Private Sub New()
60:
61: End Sub
62: End Class
SL4就要支持DependencyObject绑定了,期待。。。
这两天发现某服务器进程出现内存泄漏,遂抓dump调试之。
调试内存泄漏首先当然是!dumpheap -stat:
..... 71861128 52018 2913008 System.Threading.Thread 71862cf0 22130 2962628 System.Int32[] 0040a230 198 3034188 Free 71e7fcf8 680643 13612860 System.Threading.SafeCompressedStackHandle 05ac45cc 1412552 22600832 System.Net.IPEndPoint 05fb5690 675403 24314508 UDT.UDTSocket 71860b54 727921 34970436 System.String 718542d4 1350915 37825620 System.UInt16[] 05a7b37c 1350904 54036160 System.Net.IPAddress .....
一看吓了一跳,Thread应该是会被垃圾回收掉的,不可能会有这么多留在堆里面。于是看看Finalize Queue:
0:000> !finalizequeue SyncBlocks to be cleaned up: 0 MTA Interfaces to be released: 0 STA Interfaces to be released: 0 ---------------------------------- generation 0 has 16 finalizable objects (0f751b0c->0f751b4c) generation 1 has 4 finalizable objects (0f751afc->0f751b0c) generation 2 has 1719 finalizable objects (0f750020->0f751afc) Ready for finalization 1406640 objects (0f751b4c->0fcaf60c) .....
100W+个对象等待Finalize,基本可以确定Finalizer thread出问题了。切到里面看看Call stack:
0:000> ~2s eax=00000001 ebx=ffffffff ecx=00000000 edx=00000000 esi=00000000 edi=00000000 eip=77b191d5 esp=049eea00 ebp=049eea6c iopl=0 nv up ei pl nz na po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 ntdll!ZwWaitForSingleObject+0x15: 77b191d5 c20c00 ret 0Ch 0:002> k ChildEBP RetAddr 049ee9fc 77611270 ntdll!ZwWaitForSingleObject+0x15 049eea6c 776111d8 kernel32!WaitForSingleObjectEx+0xbe 049eea80 770bed85 kernel32!WaitForSingleObject+0x12 049eeaa4 771b9427 ole32!GetToSTA+0xad 049eead0 771b9b4d ole32!CRpcChannelBuffer::SwitchAptAndDispatchCall+0x132 049eebb0 770d37b4 ole32!CRpcChannelBuffer::SendReceive2+0xef 049eec28 770be868 ole32!CAptRpcChnl::SendReceive+0xaf 049eec7c 75dd1074 ole32!CCtxComChnl::SendReceive+0x95 049eec98 75dd102b rpcrt4!NdrProxySendReceive+0x49 049eeca4 75dd0146 rpcrt4!NdrpProxySendReceive+0xb 049ef0ac 75dd11ee rpcrt4!NdrClientCall2+0x18f 049ef0d0 75d45f22 rpcrt4!ObjectStublessClient+0x90 049ef0e0 770bf163 rpcrt4!ObjectStubless+0xf 049ef168 770a2b14 ole32!CObjectContext::InternalContextCallback+0x128 049ef1b8 72dc3fb7 ole32!CObjectContext::ContextCallback+0x87 049ef204 72dc4bcc mscorwks!CtxEntry::EnterContextOle32BugAware+0x2b 049ef324 72df9aeb mscorwks!CtxEntry::EnterContext+0x325 049ef358 72df9b90 mscorwks!RCWCleanupList::ReleaseRCWListInCorrectCtx+0xc4 049ef3a8 72d2befb mscorwks!RCWCleanupList::CleanupAllWrappers+0xdb 049ef3ec 72d2be0b mscorwks!SyncBlockCache::CleanupSyncBlocks+0xec
Google一番后发现,如果Finalizer thread的调用里面出现ole32!GetToSTA并且卡死的话,多半由于主线程为STA并且使用了COM,然后调用了阻塞的方法。(http://support.microsoft.com/?id=828988)
这程序在初始化之后就调用了Console.ReadLine,工作全部在其它线程进行。程序并没有直接调用COM,但是使用了Remoting和MAF。推测是里面隐式调用了COM,导致出现了这问题。。。
解决方法那篇KB里面有给出,这里就不说了。话说要不是会一点WinDBG,应该就找不出问题根源了吧。。。M$的东西还真是。。。