VB.NET / COM服务器代码比Excel VBA代码慢

时间:2009-09-01 15:39:53

标签: vb.net excel vba com

背景

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

问题

VB.NET代码慢一点。我可以扩展一系列基于VBA的公式并进行即时计算。我可以扩展相当范围的基于VB.NET的公式,计算需要5-10秒。客户明显变慢,不可接受。

我有几种可能性:

    由于没有开关,
  1. VBA的本地编译速度更快
  2. 可以为每个UDF调用加载和卸载DLL
  3. DLL调用Excel WorksheetFunction方法并需要一个Application对象,并且创建Application对象很昂贵
  4. 从DLL调用Excel WorksheetFunction方法很昂贵
  5. 我不认为(2)是真的,因为我将调用附加到Shared New,Public New和Finalize函数中的文件,而我得到的只是:

    Shared Sub New
    Public Sub New
    Finalize
    

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

    我不认为(3)是正确的,因为文件编写显示Application对象只创建一次。

    问题

    我如何弄清楚花时间的是什么?如何在这种环境中进行分析?是否有明显的增强功能?

    在最后一个类别中,我尝试通过使其共享来减少Application对象(用于WorkSheetFunction调用)的创建数量:

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

    采取的方法

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

8 个答案:

答案 0 :(得分:3)

  

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

如果是真的,我会同意这一点。但当然,这根本不是真的,为什么我会这样开始......

您可以针对Excel SDK在C ++中编写UDF,并将其作为XLL提供。这是银行定量分析师的普遍做法;事实上,他们似乎很喜欢它,这说明他们作为一个群体很多。

我最近遇到的另一个不那么痛苦的选择是ExcelDNA,它是AFAICT,它提供了讨厌的SDK / XLL位,以便连接.NET DLL。它足够酷,它甚至可以让你加载源代码,而不是构建一个单独的DLL,这对于原型设计非常有用(它利用了CLR实际上包含编译器的事实)。我不知道性能:我没有尝试对它进行基准测试,但它确实似乎绕过了COM Interop问题,众所周知这是一个可怕的问题。

除此之外,我只能支持其他建议:尽可能少地引用您的工作簿,其内容和Excel应用程序。每次通话费用。

答案 1 :(得分:2)

我认真地假设从VB.NET到COM服务器的互操作是通过编组完成的。在VBA中,这些方法被直接调用 - 控制器以几个处理器指令的成本传递给它们,并且看起来非常快。现在,通过编组,完成了一整套额外的工作,每次调用都会遇到严重的开销。您需要严重减少呼叫次数(使每个呼叫更多地工作)或禁用编组并像使用VBA一样工作。有关如何完成后者的详细信息,请参阅this question

答案 2 :(得分:2)

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

  • VB6 COM addin 63

    C XLL 37

    Addin Express Automation VB.net 170

    Addin Express XLL VB.net 100

    ExcelDNA XLL CVB.Net 81

Managed XLL提供了相似的时间,但也使得cusom marshallers可以快速。

答案 3 :(得分:2)

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

对于非常简单的函数,通过ExcelDna调用托管函数的开销非常小,允许您每秒进行数十万次UDF调用。

答案 4 :(得分:1)

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

SpreadsheetGear for .NET可能是您的选择。它通过COM Interop比Excel快得多(看看有些客户说here)并且它支持Excel兼容计算和用户定义函数(参见this page上的自定义函数示例)。

如果您想试用,可以免费试用here

免责声明:我拥有SpreadsheetGear LLC

答案 5 :(得分:1)

我和乔有同样的经历。主要是互操作很慢。

在大多数情况下,这可以通过使用整个范围来解决,而不是单个细胞。 您可以使用.Net数组并在一次调用中将它们传入/传出excel。

e.g。

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

这解决了我见过的所有性能问题

答案 6 :(得分:0)

一个想法。而不是传递Range对象(可能是每个对Ranbe对象的调用都可以从.Net编组到Excel),将所有参数整理成基本类型,双精度,字符串,类型化数组以及必要时未键入的变量数组,并将它们传递给.Net DLL。这样你只需要编组一个变种。

- DM

答案 7 :(得分:0)

这个问题(7年)真的很晚,但是对于它的价值,我已经在投资银行中使用了5/6个单独的Excel系统,并且在我们所有的Excel系统中都看到了类似的设计模式。描述

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

所以示例句柄是

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

可以看出&#39; 103450&#39;是一个对象编号,其唯一性足以从全局范围的字典中获取对象(例如),时间戳表示创建对象的时间,USTreasuries是用户友好的描述。人们可以使用像这样的公式函数创建诸如对象

=CreateHandledObject("USTreasuries",A1:D30)

那个人会写一个分析,它接受这个句柄并在内部获取数据。它要求CreateHandledObject()标记为volatile,您必须将计算转为手动并按代码或用户执行重新计算。

问题源于工作表中无休止的编组数据。我认为这种方法可以帮助您将这个繁琐的元素减少到最低限度。