缓慢的“属性获取”和“函数”过程

Cri*_*use 5 excel vba

我遇到了一个令人讨厌的 VBA 错误,它使Property Get过程调用变得非常慢。这很可能是由于最近的 Office 更新(我有 Office365)造成的。它仅影响 32 位 CPU 上的 Excel。

错误

考虑一个Class1仅使用代码调用的类:

Option Explicit

Public Property Get Something() As Long: End Property
Run Code Online (Sandbox Code Playgroud)

基本上,一个Get返回零的属性。

运行以下代码时(在标准 .bas 模块中):

Public Sub TestSpeed()
    Const iterations As Long = 10000
    Dim i As Long
    Dim t As Double
    Dim coll As New Collection
    '
    t = Timer
    For i = 1 To iterations
        coll.Add New Class1
        CallGet coll.Item(coll.Count)
    Next i
    Debug.Print iterations & " loops took " & Round(Timer - t, 3) & " seconds"
End Sub

Sub CallGet(ByVal c As Class1)
    Dim v As Variant
    
    v = c.Something
End Sub
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,我得到了一个不错的时机:

i1

Get但是,如果我在测试中使用的属性的顶部添加一堆其他属性,那么情况就会发生变化。更新后的内容Class1可能如下所示:

Option Explicit

Public Property Get Something001() As Long: End Property
Public Property Get Something002() As Long: End Property
Public Property Get Something003() As Long: End Property
Public Property Get Something004() As Long: End Property
Public Property Get Something005() As Long: End Property
Public Property Get Something006() As Long: End Property
Public Property Get Something007() As Long: End Property
Public Property Get Something008() As Long: End Property
Public Property Get Something009() As Long: End Property
Public Property Get Something010() As Long: End Property
Public Property Get Something011() As Long: End Property
Public Property Get Something012() As Long: End Property
Public Property Get Something013() As Long: End Property
Public Property Get Something014() As Long: End Property
Public Property Get Something015() As Long: End Property
Public Property Get Something016() As Long: End Property
Public Property Get Something017() As Long: End Property
Public Property Get Something018() As Long: End Property
Public Property Get Something019() As Long: End Property
Public Property Get Something020() As Long: End Property
Public Property Get Something021() As Long: End Property
Public Property Get Something022() As Long: End Property
Public Property Get Something023() As Long: End Property
Public Property Get Something024() As Long: End Property
Public Property Get Something025() As Long: End Property
Public Property Get Something026() As Long: End Property
Public Property Get Something027() As Long: End Property
Public Property Get Something028() As Long: End Property
Public Property Get Something029() As Long: End Property
Public Property Get Something030() As Long: End Property
Public Property Get Something031() As Long: End Property
Public Property Get Something032() As Long: End Property
Public Property Get Something033() As Long: End Property
Public Property Get Something034() As Long: End Property
Public Property Get Something035() As Long: End Property
Public Property Get Something036() As Long: End Property
Public Property Get Something037() As Long: End Property
Public Property Get Something038() As Long: End Property
Public Property Get Something039() As Long: End Property
Public Property Get Something040() As Long: End Property
Public Property Get Something041() As Long: End Property
Public Property Get Something042() As Long: End Property
Public Property Get Something043() As Long: End Property
Public Property Get Something044() As Long: End Property
Public Property Get Something045() As Long: End Property
Public Property Get Something046() As Long: End Property
Public Property Get Something047() As Long: End Property
Public Property Get Something048() As Long: End Property
Public Property Get Something049() As Long: End Property
Public Property Get Something050() As Long: End Property
Public Property Get Something051() As Long: End Property
Public Property Get Something052() As Long: End Property
Public Property Get Something053() As Long: End Property
Public Property Get Something054() As Long: End Property
Public Property Get Something055() As Long: End Property
Public Property Get Something056() As Long: End Property
Public Property Get Something057() As Long: End Property
Public Property Get Something058() As Long: End Property
Public Property Get Something059() As Long: End Property
Public Property Get Something060() As Long: End Property
Public Property Get Something061() As Long: End Property
Public Property Get Something062() As Long: End Property
Public Property Get Something063() As Long: End Property
Public Property Get Something064() As Long: End Property
Public Property Get Something065() As Long: End Property
Public Property Get Something066() As Long: End Property
Public Property Get Something067() As Long: End Property
Public Property Get Something068() As Long: End Property
Public Property Get Something069() As Long: End Property
Public Property Get Something070() As Long: End Property
Public Property Get Something071() As Long: End Property
Public Property Get Something072() As Long: End Property
Public Property Get Something073() As Long: End Property
Public Property Get Something074() As Long: End Property
Public Property Get Something075() As Long: End Property
Public Property Get Something076() As Long: End Property
Public Property Get Something077() As Long: End Property
Public Property Get Something078() As Long: End Property
Public Property Get Something079() As Long: End Property
Public Property Get Something080() As Long: End Property
Public Property Get Something081() As Long: End Property
Public Property Get Something082() As Long: End Property
Public Property Get Something083() As Long: End Property
Public Property Get Something084() As Long: End Property
Public Property Get Something085() As Long: End Property
Public Property Get Something086() As Long: End Property
Public Property Get Something087() As Long: End Property
Public Property Get Something088() As Long: End Property
Public Property Get Something089() As Long: End Property
Public Property Get Something090() As Long: End Property
Public Property Get Something091() As Long: End Property
Public Property Get Something092() As Long: End Property
Public Property Get Something093() As Long: End Property
Public Property Get Something094() As Long: End Property
Public Property Get Something095() As Long: End Property
Public Property Get Something096() As Long: End Property
Public Property Get Something097() As Long: End Property
Public Property Get Something098() As Long: End Property
Public Property Get Something099() As Long: End Property
Public Property Get Something100() As Long: End Property
Public Property Get Something() As Long: End Property
Run Code Online (Sandbox Code Playgroud)

新的时机非常糟糕:
i2

笔记

通过更多测试我发现以下内容:

  1. TestSpeed如果我添加更多Get属性,但仅当我将它们添加到所使用的属性的顶部时,运行相同方法所需的时间就会增加。当然,如果我删除一些程序,时间就会减少。
  2. 它只影响我的 Excel 32 位版本。它在 Excel 64 位上运行得非常好,在 Word(32 位和 64 位)等其他应用程序中也运行得很好。我的 32 位和 64 位 Excel 都是版本 2102(内部版本 13801.21092),如版本 16.0.13801.21072
  3. 如果我通过将:替换
    Sub CallGet(ByVal c As Class1)
    为:来使用后期绑定
    Sub CallGet(ByVal c As Object)
    ,则错误消失了。当然,后期绑定比早期绑定要慢一些:
    i3

问题

任何人都可以重现上述行为吗?

为什么会出现上述行为?

除了等待微软解决这个问题之外,我还能做些什么来解决这个问题吗?

编辑#1

正如 @PeterT 在他的回答中指出的,当与大量项目一起使用时,特别是通过索引检索项目时,集合非常慢。

需要澄清的是,使用数组或 Scripting.Dictionary 时上述问题仍然存在。例如,以下代码会产生相同的问题:

Option Explicit

Public Sub TestSpeed()
    Const iterations As Long = 10000
    Dim i As Long
    Dim t As Double
    Dim arr() As Class1: ReDim arr(1 To iterations)
    '
    t = Timer
    For i = 1 To iterations
        Set arr(i) = New Class1
        CallGet arr(i)
    Next i
    Debug.Print iterations & " loops took " & Round(Timer - t, 3) & " seconds"
End Sub

Sub CallGet(ByVal c As Class1)
    Dim v As Variant
    
    v = c.Something
End Sub
Run Code Online (Sandbox Code Playgroud)

使用数组更新计时:

i4

编辑#2

Property Get显然,如果有多个程序也没关系。即使该类只有一个属性,问题仍然存在,只是我没有注意到。通过连续按 F5 运行该TestSpeed程序,我得到了以下结果:

i5

使用后期绑定,我得到了这些:

i6

编辑#3

打开多个 Excel 实例(按下 ALT 键)时,所有新实例的问题都消失了,但初始实例的问题却没有。但这只是当我在新的 Excel 文件中运行代码时。如果我只是打开已保存的已包含代码的 Excel 文件,那么在任何情况下问题仍然存在。

同样,如果我打开第一个 Excel 实例并在新的未保存文件中运行代码,那么问题就消失了。但是,当使用相同的代码打开任何已保存的文件时,问题就会显现出来。

编辑#4

事实上,该问题影响所有应用程序(Word、Outlook、PPT),但仅在保存并重新打开文件时才会显现出来。我只在这些应用程序中运行代码而不保存,因此错误地认为这只是 Excel 问题。

我还在 AutoCAD 2019 上进行了测试,即使加载保存的文件也没有问题。但是,AutoCAD 没有将 VBA 代码嵌入到文件中,而是另存为“.dvb”单独的项目文件。

保存没有代码的启用宏的文件(例如 xlsb/xlsm),然后打开并添加测试代码,效果非常好且快速 - 没有问题。但是,如果保存文件时存在任何代码模块(即使是空白),那么打开文件时添加测试代码时就会出现问题。

Cri*_*use 3

当反恶意软件扫描接口 (AMSI) 代理实时扫描代码时,VBA 代码运行速度会降低。

AMSI 引擎自 2018 年起成为 Office 的一部分:
Office VBA + AMSI:揭开恶意宏的面纱 - Microsoft 安全博客

“扫描”会将宏速度减慢到几乎无法使用的阶段,并影响诸如调用对象FunctionProperty Get对象上的功能。Sub似乎不受影响,但这意味着返回值作为ByRef参数。

现在我可以有把握地说,Carbon Black正在使用此 API,并导致我公司计算机上的宏速度变慢。我在家用计算机上使用Bitdefender,但它不会引起任何问题。

我怀疑我现在(2022 年 1 月)才遇到这个问题,因为 Carbon Black 防病毒软件升级了,现在允许软件挂钩此功能。

部分 Office 64 位 PC 不受影响的原因可能与安装的反恶意软件软件尚未提供此架构的引擎有关。

解决方案

  1. 停止使用导致问题的特定防病毒软件 - 在我的公司中这并不是一个选择
  2. 使用具有多种选项的宏运行时扫描范围策略

注意:此策略设置仅适用于 Office 的订阅版本,例如 Microsoft 365 企业应用版。

  1. 禁用 AMSI 引擎GPS:禁用命令 (gpsearch.azurewebsites.net)

  • 我详细阅读了问题以了解您的问题,直到我发现您并不是在寻找答案。想象一下有多少像我这样的人阅读了这个问题(没有任何此类问题),有多少人认为他们解决的问题非常重要。你或我都误解了设置赏金的原因。https://stackoverflow.com/help/bounty (2认同)