Excel 互操作 - 效率和性能

Posted

技术标签:

【中文标题】Excel 互操作 - 效率和性能【英文标题】:Excel Interop - Efficiency and performance 【发布时间】:2010-09-26 06:18:20 【问题描述】:

我想知道我可以做些什么来提高 Excel 自动化的性能,因为如果您在工作表中有很多事情要做,它可能会很慢...

以下是我自己发现的一些:

ExcelApp.ScreenUpdating = false -- 关闭屏幕重绘

ExcelApp.Calculation = Excel.XlCalculation.xlCalculationManual -- 关闭计算引擎,以便 Excel 在单元格值更改时不会自动重新计算(完成后重新打开)

减少对Worksheet.Cells.Item(row, col)Worksheet.Range 的调用——我必须轮询数百个单元格才能找到我需要的单元格。实施一些单元格位置缓存,将执行时间从约 40 秒减少到约 5 秒。

什么样的互操作调用会严重影响性能并且应该避免?您还能做些什么来避免进行不必要的处理?

【问题讨论】:

+1 我在使用 Excel Interop 时也遇到了性能问题,而且我学到了一些新东西:ExcelApp.Calculation = Excel.XlCalculation.xlCalculationManual。谢谢! =) 感谢您在问题中分享您当前的发现,非常有用。 这里有一些其他可能的选项***.com/documentation/excel-vba/1107/… 【参考方案1】:

当使用 C# 或 VB.Net 获取或设置范围时,计算范围的总大小是多少,然后获取一个大型二维对象数组...

//get values
object[,] objectArray = shtName.get_Range("A1:Z100").Value2;
iFace = Convert.ToInt32(objectArray[1,1]);

//set values
object[,] objectArray = new object[3,1] "A""B""C";
rngName.Value2 = objectArray;

请注意,了解 Excel 存储的数据类型(文本或数字)很重要,因为当您将类型从对象数组转换回时,它不会自动为您执行此操作。如果您无法事先确定数据的类型,请根据需要添加测试以验证数据。

【讨论】:

+1 用于使用 2D 对象数组。还有 shtName.UsedRange.get_Value(XlRangeValueDataType.XlRangeValueDefault) 可以被类型转换为二维对象数组,并一次检索所有单元格的值。 @Will Marcouiller:是的,但是使用 UsedRange 属性的问题在于它会对性能产生负面影响。我不确定它是否比使用单元格/偏移方法快得多。 这很好,但是如何使用这种优化的技术将图像添加到 xls 中呢?在我的应用程序中,图像插入是一个瓶颈。每个生成的 XLSX 文件至少有 300-400 张图像。当前解决方案调用worksheet.Shapes.AddPicture()方法的次数与图片数量一样多。这真的很慢。 如果您需要额外的功能(如格式化),使用 COM 对象执行的操作很糟糕,您可以从这个答案尝试 EPPlus - ***.com/questions/6583136/… @JeffFischer OP “工作表中发生了很多事情”。 EPPlus 的建议与我们正在谈论的内容脱节。【参考方案2】:

这适用于任何想知道从数据库结果集中填充 excel 工作表的最佳方法的人。这绝不是一个完整的列表,但它确实列出了一些选项。

尝试在旧的 Pentium 4 3GHz 机器上填充具有 155 列和 4200 条记录的 Excel 工作表时的一些性能数据,包括从最慢到最快的顺序从不超过 10 秒的数据检索时间...

    一次一个单元格 - 不到 11 分钟

    通过转换为 html 来填充数据集 + 将 html 保存到磁盘 + 将 html 加载到 excel 中并将工作表保存为 xls/xlsx - 5 分钟

    一次一列 - 4 分钟

    使用 SQL 2005 中已弃用的 sp_makewebtask 过程创建 HTML 文件 - 9 秒 + 然后在 excel 中加载 html 文件并保存为 XLS/XLSX - 大约 2 分钟。

    将 .Net 数据集转换为 ADO RecordSet 并使用 WorkSheet.Range[].CopyFromRecordset 函数填充 excel - 45 秒!

我最终使用了选项 5。希望这会有所帮助。

【讨论】:

hmmm 很好的混合方法(5) 但是,您是否测试过第六种方法,使用工作簿的 OLEDbConnection 并将工作表填充为表格?这种方法的局限性包括需要事先知道每列的架构(以防止 excel 进行错误的类型转换)。 @AnonymousType - 我必须承认我没有尝试将工作表填充为表格。我仍然希望 Microsoft 为 .Net 开发人员提供“CopyFromDataSet”功能:-)。 这将是一个非常好的 VSTO 功能。【参考方案3】:

如果您要轮询多个单元格的值,您可以一举获得存储在变体数组中的范围内的所有单元格值:

Dim CellVals() as Variant
CellVals = Range("A1:B1000").Value

这里有一个权衡,就您获得值的范围大小而言。我猜如果你需要一千个或更多的单元格值,这可能比循环不同的单元格并轮询值要快。

【讨论】:

-1,没有冒犯乔恩,但是这怎么会得到这么多的支持呢?问题是关于 Excel 互操作而不是 VBA/VB6。很确定 Variant 甚至不作为 COM 互操作的类型存在(使用对象)。 Range.Value 也适用于 COM。因此,如果它的 VBA/.NET 示例在这里有点无关紧要,并且不会改变答案。我同意如果 Jon 改变样本会更好。 更不用说这个答案比接受的答案早一年了,而且这个答案只是稍微好一点,因为它有反向操作。【参考方案4】:

尽可能使用 excels 内置功能,例如:不要在整列中搜索给定字符串,而是使用 GUI 中通过 Ctrl-F 提供的find 命令:

Set Found = Cells.Find(What:=SearchString, LookIn:=xlValues, _
    SearchOrder:=xlByRows, SearchDirection:=xlNext, _
    MatchCase:=False, SearchFormat:=False)

If Not Found Is Nothing Then
    Found.Activate
    (...)
EndIf

如果你想对一些列表进行排序,使用 excel sort 命令,不要在 VBA 中手动操作:

Selection.Sort Key1:=Range("A1"), Order1:=xlAscending, Header:=xlGuess, _
    OrderCustom:=1, MatchCase:=False, Orientation:=xlTopToBottom, _
    DataOption1:=xlSortNormal

【讨论】:

这是 VBA,不是互操作。 @bukko 这个想法是一样的,而且非常好。就像使用 SQL 数据库时,您应该使用 Query 来执行您的任务(或 SP),而不是执行多个查询。【参考方案5】:

性能还很大程度上取决于您如何自动化 Excel。 VBA 比 COM 自动化快,比 .NET 自动化快。而且通常早期(编译时)绑定也比后期绑定更快。

如果您遇到严重的性能问题,您可以考虑将代码的关键部分移至 VBA 模块并从您的 COM/.NET 自动化代码中调用该代码。

如果您使用 .NET,您还应该使用 Microsoft 提供的优化的主互操作程序集,而不是使用定制的互操作程序集。

【讨论】:

都是真的。但是,如果您遵循不进行大量小的 Range 对象调用来设置 Value2 属性的建议,而只是传入一个 Object 数组或获取一个,那么您实际上不需要使用 VBA。【参考方案6】:

正如匿名类型所说:读/写大范围块对性能非常重要。

如果 COM-Interop 开销仍然太大,您可能希望切换到使用 XLL 接口,这是最快的 Excel 接口。

虽然 XLL 接口主要面向 C++ 用户,但 XL DNA 和 Addin Express 都提供 .NET 到 XLL 的桥接功能,这比 COM-Interop 快得多。

【讨论】:

【参考方案7】:

您可以在 VBA 中做的另一件大事是使用 Option Explicit 并尽可能避免使用 Variants。变体在 VBA 中并非 100% 可以避免,但它们会使解释器在运行时做更多的工作并浪费内存。

当我开始在 Excel 中使用 VBA 时,我发现这篇文章非常有帮助。http://www.ozgrid.com/VBA/SpeedingUpVBACode.htm

还有这本书

http://www.amazon.com/VB-VBA-Nutshell-Language-OReilly/dp/1565923588

类似于

 app.ScreenUpdates = false //and
 app.Calculation = xlCalculationManual

你也可以设置

 app.EnableEvents = false //Prevent Excel events
 app.Interactive = false  //Prevent user clicks and keystrokes

虽然它们似乎没有前两个那么大。

与将范围值设置为数组类似,如果您处理的数据主要是表格,每行的每一行都有相同的公式,您可以为您的公式使用 R1C1 公式表示法并将整个列设置为等于公式字符串在一次调用中设置整个事情。

app.ReferenceStyle = xlR1C1
app.ActiveSheet.Columns(2) = "=SUBSTITUTE(C[-1],"foo","bar")"

此外,使用 ExcelDNA 和 .NET(或 C 中的硬方法)创建 XLL 插件也是使 UDF 在多个线程上运行的唯一方法。 (请参阅 Excel DNA 的 ExcelFunction 属性的 IsThreadSafe 属性。)

在完全过渡到 Excel DNA 之前,我还尝试在 .NET 中创建 COM 可见库以在 VBA 项目中引用。繁重的文本处理比 VBA 快一点,使用包装的 .NET List 类而不是 VBA 的 Collection,但 Excel DNA 更好。

【讨论】:

以上是关于Excel 互操作 - 效率和性能的主要内容,如果未能解决你的问题,请参考以下文章

使用互操作将 datagridview 导出到 excel

C# -Excel 互操作性

Excel 互操作货币格式

Excel 互操作从右到左文档

Windows 服务未启动 - Excel - 邮件互操作

需要澄清正确清理 Excel 互操作对象的答案