【问题标题】:Slow 'Property Get' and 'Function' procedures缓慢的 \'Property Get\' 和 \'Function\' 过程
【发布时间】:2022-10-05 18:58:44
【问题描述】:

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

错误

考虑一个名为Class1 的类,只有代码:

Option Explicit

Public Property Get Something() As Long: End Property

基本上,一个返回零的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

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

但是,如果我在用于测试的属性的顶部添加一堆其他 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

新的时机非常糟糕:

笔记

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

  1. 如果我添加更多Get 属性,运行相同TestSpeed 方法所需的时间会增加,但前提是我将它们添加到所用属性的顶部。当然,如果我删除一些程序,时间会减少。
  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)
    错误消失了。当然后期绑定比早期绑定慢一点:

    问题

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

    为什么会发生上述行为?

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

    编辑#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
    

    使用数组更新时序:

    编辑#2

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

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

    编辑#3

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

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

    编辑#4

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

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

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

  • 唔。我无法在 32 位中重现。 i.stack.imgur.com/aLuqX.png。 Excel 365,版本 16.0.13801.21072(可能落后于您的几个版本)。我也许可以稍后在单独的安装中进行测试。
  • @BigBen我刚刚注意到您的版本与我完全相同(也更新了问题)。这使我认为该问题可能与其他已安装的软件有关。
  • 有趣 - 就像在插件中?那会比较烦人。
  • @BigBen 不确定。我所有的同事都有相同的版本和相同的问题,所以可能是插件或其他一些软件,如防病毒软件。
  • @BigBen 我已禁用所有插件,甚至在安全模式下运行 Excel,但问题仍然存在。绝对烦人。

标签: excel vba


【解决方案1】:

我在 2013-32 年使用具有 100 个属性的空类 v 测试了您的示例,并且在时间上只有很小的差异。我只能假设与您的特定设置有关的事情。

但是,我想说即使在旧系统中,您的 0.45 秒也很慢,原因是您对大型集合的特殊使用。两种改进方法——

  1. 直观地对抗大型集合,使用键而不是索引来检索项目要快得多,使用键填充只会稍微慢一些。引用 col.Item(1) 很快,但更大的索引逐渐变慢,似乎在内部循环集合每次都找到给定的索引......

     For i = 1 To iterations
        ' coll.Add New Class1
        ' CallGet coll.Item(coll.Count)
         coll.Add New Class1, CStr(i)
         CallGet coll.Item(CStr(i))
     Next i
    

    我希望在这个测试中使用 Key 比你的 0.45s 快几倍。但在实际使用中要好得多,因为这里的大部分时间都是在集合变大时填充集合,而不是从集合中检索项目。

    1. 而不是一个集合使用一个数组......

       ReDim arrClass(1 To iterations) As Class1
       For i = 1 To iterations
           Set arrClass(i) = New Class1
           Call CallGet(arrClass(CStr(i)))
       Next
      

    这可能再次快两倍。尽管更好地测试创建+存储对象引用并分别检索它们,但它们是不同的过程。

    但是,无论我理解的速度有多快,差异都是微不足道的,直到用空类和 100 个属性计算测试的差异!

【讨论】:

  • 谢谢!我很抱歉误导你。我只是在试图指出问题时随机使用该集合。数组或 Scripting.Dictionary 也会出现同样的问题。正如你所说,我从不按索引从集合中检索项目,因为这真的很慢(幕后的链表)。我将更新问题以消除未来读者的任何疑虑。
  • 不需要道歉。我意识到收集的使用并不是您的特定问题的主要原因,因为我试图在我的最后评论中阐明。
  • 不管怎么说,还是要谢谢你!在同一家公司内,具有相同 Excel 版本的 VDI 上不存在该问题,所以现在我很确定是系统问题,而不是 Excel/Office。 +1
  • 我似乎因为我的回答而获得了你的 100 赏金,这不是一个真正的答案,不确定是否发生了!实际上,对于您从支持工程师那里得到的答案,这是为您保留 100 分的罕见帖子之一:)
  • 是的,我已将赏金授予你。我只是发布赏金以引起人们注意这个问题很严重。积分会丢失,因为我当然不能奖励自己。无论如何,我认为您的回答对于不知道 Collection 按索引检索项目有多慢的其他读者也很有用。再次感谢您的反馈!
【解决方案2】:

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

自 2018 年以来,AMSI 引擎成为 Office 的一部分:
Office VBA + AMSI: Parting the veil on malicious macros - Microsoft Security Blog

“扫描”将宏减慢到几乎无法使用的阶段并影响功能,例如在对象上调用 FunctionProperty GetSub 似乎没有受到影响,但这意味着将值作为 ByRef 参数返回。

现在我可以肯定地说,Carbon Black 正在使用这个 API,并导致宏在我公司的计算机上变慢。我在家用电脑上使用Bitdefender,但它不会引起任何问题。

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

某些具有 Office 64 位的 PC 不受影响的原因可能与安装的反恶意软件有关,但尚未为该架构提供引擎。

解决方案

  1. 停止使用导致问题的特定防病毒软件 - 在我的公司中并不是一个真正的选择
  2. 使用具有多种选项的Macro Runtime Scan Scope 策略

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

    1. 禁用 AMSI 引擎GPS: Disable commands (gpsearch.azurewebsites.net)

【讨论】:

  • 所以你对一个你已经自己回答的问题设置了赏金,以便更多的人关注这个问题?想法是什么?
  • @FunThomas 如赏金描述中所述,只是为了提高认识。它扰乱了我公司 50 多人的工作近一个月。在我们与一位微软工程师交谈之前,再多的谷歌搜索都没有帮助。试想一下,您在 2 个月内遇到了同样的问题——也许您会记得看到这个问题,并且即使谷歌搜索/搜索没有找到我的答案。也许您甚至可以将其添加为书签,以确保您可以快速找到它。
  • 我阅读了这个问题的详细信息以了解您的问题,直到我认为您不是在寻找答案。想象一下有多少像我这样的人阅读了这个问题(没有任何这样的问题),有多少人认为他们解决的问题非常重要。你或我都误解了设置赏金的原因。 stackoverflow.com/help/bounty
  • @FunThomas 抱歉浪费您的时间。我无法删除赏金,所以我添加了一个粗体的第一部分来说明我不是在寻找答案。我的意图是好的,但现在我后悔增加了赏金。
  • 您在 #3 中建议“禁用 AMSI 引擎” - 这是否解决了您发布的问题?如果是这样,为什么这有帮助?如果不是,考虑到您的“作为对象”比早期绑定“作为类”工作得更好,您是否尝试过Allow VBA to load typelib references by path from untrusted intranet locations -
猜你喜欢
  • 1970-01-01
  • 2017-07-14
  • 2020-06-10
  • 2015-03-29
  • 2016-08-16
  • 2014-11-05
  • 1970-01-01
  • 2022-11-16
  • 1970-01-01
相关资源
最近更新 更多