VB.NET / COM Server代码方式比Excel VBA代码慢

背景

我有一个客户需要Excel VBA代码,生成的公式值移动到VB.NET。 他在提供财务分析业务,在这种情况下,作为一个Excel加载项交付。 我已经将VBA转换成在单独的DLL中运行的VB.NET代码。 该DLL被编译为一个COM服务器,因为,可调用Excel的.NET UDF必须是。 到目前为止,这么好:Excel单元格有“= foo(Range1,Range2,…)”,VB.NET Com服务器的UDF被调用,并且单元格获得一个与VBA代码值相匹配的值。

问题

VB.NET代码是慢的。 我可以扩展一系列基于VBA的公式并进行即时计算。 我可以延伸一个可比范围的基于VB.NET的公式,计算需要5-10秒。 客户显然较慢,不能接受。

有几种可能性发生在我身上:

  1. VBA本地编译速度更快,因为没有交换机
  2. 可以为每个UDF调用加载和卸载该DLL
  3. 该DLL调用Excel WorksheetFunction方法并需要一个Application对象,并且创buildApplication对象的开销很大
  4. 从DLL调用Excel WorksheetFunction方法是昂贵的

我不认为(2)是真实的,因为我把调用追加到共享新,公共新和Finalize函数中的文件,我得到的是:

Shared Sub New Public Sub New Finalize 

当我打开电子表格时,反复拉伸公式范围,然后closures电子表格。

我不认为(3)是真的,因为文件写入显示Application对象只创build一次。

这个问题

我如何弄清楚什么是花时间? 如何在这个环境中进行configuration? 有明显的增强吗?

在最后一个类别中,我试图通过将Shared对象(用于WorkSheetFunction调用)来减less一个Application对象的创build次数:

 <Guid("1ECB17BB-444F-4a26-BC3B-B1D6F07D670E")> _ <ClassInterface(ClassInterfaceType.AutoDual)> _ <ComVisible(True)> _ <ProgId("Library.Class")> _ Public Class MyClass Private Shared Appp As Application ' Very annoying 

采取的方法

我试图通过重写我自己来减less对Excelmath函数的依赖。 我已经取代了Min,Max,Average,Stdev,Small,Percentile,Skew,Kurtosis等等。 我的UDF代码调用Excel到更less。 不可避免的调用似乎将范围作为参数,并将其转换为.NET数组供内部使用。

该DLL被编译为一个COM服务器,因为,可调用Excel的.NET UDF必须是

我认为,如果真的有一个表演塞子的话。 但是,当然,这是不正确的,为什么我会以这种方式开始呢?

首先,您可以使用C ++编写UDF,并将其作为XLL来交付。 银行中的量化分析师普遍采用这种做法。 实际上他们似乎很喜欢,这就说明他们是一个团队。

另一个不太痛苦的select,我最近刚刚遇到, ExcelDNA ,AFAICT,提供了一个方法来挂钩你的.NET DLL的讨厌的SDK / XLL位。 它足够酷,它甚至可以让你加载源代码,而不是build立一个单独的DLL,这对于原型(它利用了CLR实际上包含编译器的事实)很好。 我不知道性能:我没有试图去衡量它的性能,但是它似乎解决了COM Interop问题,这个问题是众所周知的。

除此之外,我只能赞同其他build议:尽可能less地引用您的工作簿,其内容和Excel应用程序。 每个通话费用。

我认真地认为,从VB.NET到COM服务器的互操作是通过编组完成的。 在VBA中,这些方法是直接调用的 – 控制权以几条处理器指令为代价传入,而且看起来非常快。 现在,编组完成了一整套额外的工作,每次通话都会遇到严重的开销。 您需要严格减less呼叫次数(使每个呼叫可以做更多的工作),或者禁用编组,并像VBA一样工作。 看到这个问题有关如何可能完成后者的细节。

我最近使用各种产品/方法将移动数据从Excel转换为.NET。 我所尝试的所有.NET方法都比VBA和VB6慢,但最好的方法是能够使用XLL接口,这比自动化接口提供了更好的结果。 基准得到了合理的优化(传输范围到arrays等)结果是(毫秒为我的基准)

  • VB6 COM插件63

    C XLL 37

    Addin Express自动化VB.net 170

    Addin Express XLL VB.net 100

    ExcelDNA XLL CVB.Net 81

pipe理XLL给了可比较的时间,但也可以使cusom marshallers可以快速。

在CodePlex上,ExcelDna还有更多性能: http ://exceldna.codeplex.com/Wiki/View.aspx?title=ExcelDna%20Performance。

对于非常简单的函数,通过ExcelDna调用托pipe函数的开销非常小,可以让您每秒执行数十万次UDF调用。

基于使用Excel通过COM Interop的许多经验,我的猜测是它是上下文切换和/或从Excel内部数据结构到.NET对象的数据映射。

SpreadsheetGear for .NET可能是您的一个select。 它比Excel通过COM Interop快得多(请参阅某些客户在这里说的),它支持Excel兼容的计算和用户定义的函数(请参阅本页上的自定义函数示例)。

如果您想试用,可以在这里下载免费试用版。

免责声明:我自己的SpreadsheetGear LLC

我有和Joe一样的经历。 这主要是互操作性很慢。

在大多数情况下,这可以通过使用整个范围而不是单个单元来解决。 你通过使用.Net数组来完成这个工作,并在一次调用中将它们传递给/从excel中传递。

例如

 Dim values(10,10) As object Dim r As Excel.Range = Me.Range("A1") r = r.Resize(UBound(values, 1), UBound(values,2)) values = r.Value For ii = 0 To UBound(values,1) For jj = 0 To UBound(values,2) values(ii,jj) = CType(values(ii,jj), Double)*2 Next Next r.Value = values 

这已经解决了我所看到的所有性能问题

一个想法。 而不是传递Range对象(可能是每个调用Ranbe对象都可以从.Net到Excel编组),将所有参数整理为基本types,双精度,string,types数组以及必要的非typesvariables数组,并将它们传递到.Net DLL中。 这样你只需要一个变种。

– DM

这个问题(7年)真的迟了,但是为了什么值得,我已经在投资银行工作了5/6个独立的Excel系统,并且在我所描述的所有Excel系统中都看到了类似的devise模式。

是的,他们有单元格块,其中包含相关数据,例如政府债券价格表,但并不总是通过这个单元格块。 相反,他们将创build一个驻留在全局可访问的内存中的对象,并标有句柄。 该对象包含单元格内容的副本,因此在分析代码中更容易访问。

所以一个例子将是

 'USTreasuries(103450|2016-07-25T15:33)' 

在这里可以看到“103450”是一个对象编号,它的唯一性足以从全局范围的字典(比如说)中获取对象,时间戳表示何时创build对象,USTreasuries是用户友好的描述。 一个会创build像这样的公式函数的对象

 =CreateHandledObject("USTreasuries",A1:D30) 

人会写一个分析接受这个句柄,并获得内部的数据。 它需要将CreateHandledObject()标记为volatile,并且必须将计算转为手动并通过代码或用户执行重新计算。

您遇到的问题源自工作表中无尽的编组数据。 我认为这种方法可以帮助您将这个麻烦的元素减less到最低限度。