【问题标题】:VBA I/O PerformanceVBA I/O 性能
【发布时间】:2011-05-07 08:04:23
【问题描述】:

我想知道这两个代码之间是否存在性能差异:

Open strFile For Output As #fNum
For var1 = 1 to UBound(strvar1)
  For var2 = 1 to UBound(strvar2)
     For var3 = 1 to UBound(strvar3)
        For var4 = 1 to UBound(strvar4)
          Print #fNum texte
        Next var4
     Next var3
   Next var2
Next var1
Close #fNum

还有

For var1 = 1 to UBound(strvar1)
  For var2 = 1 to UBound(strvar2)
     For var3 = 1 to UBound(strvar3)
        For var4 = 1 to UBound(strvar4)
          texteTotal = texteTotal  +  texte
        Next var4
     Next var3
   Next var2
Next var1

Open strFile For Output As #fNum
    Print #fNum texteTotal 
Close #fNum

万一循环很大?

【问题讨论】:

  • texteTotal 是字符串还是整数?在 VBA 中,您可以使用 + 符号以及数字连接(组合)文本字符串。如果您担心性能,在循环中连接字符串与连接整数值非常不同。
  • texte 是一个字符串。它不是很大,但有数十万行......

标签: performance vba io


【解决方案1】:

最近遇到了这个问题,我正在将大量文本(约 100k 行)写入网络文件。由于每个打印命令都会创建 I/O 活动,因此写入文件的过程非常缓慢。但是,正如其他答案中所解释的那样,通过将新行连接到它来创建一个大字符串也被证明非常慢。

我通过将各个行写入缓冲区数组,然后将该数组连接成一个字符串来解决这个问题,然后立即将其写入文件。 根据您的示例,它将类似于:

Dim buffer() as Variant
Dim i as Long
i = 1
ReDim buffer(1 to Ubound(strvar1) * Ubound(strvar2) * Ubound(strvar3) * Ubound(strvar4)
For var1 = 1 to UBound(strvar1) 
  For var2 = 1 to UBound(strvar2)
     For var3 = 1 to UBound(strvar3)
        For var4 = 1 to UBound(strvar4)
          buffer(i) = texte
          i = i + 1
        Next var4
     Next var3
   Next var2
Next var1
Open strFile For Output As #fNum
Print #fNum Join(buffer, vbCrLf)
Close #fNum

这可以防止增量串联的开销(Join 函数随行数线性缩放,而不是像串联那样指数),以及将多行单独写入网络文件的 I/O 开销。

【讨论】:

    【解决方案2】:

    Cody 非常感谢您抽出宝贵时间。 以下是更多信息: 代码示例 #1 是当前的生产代码。更准确地说,这是来自

    的整个过程的一部分

    1) 从 DB #1 获取信息

    2)用数学公式(矩阵、向量)计算,N=BIG

    3) 将结果复制到每个 txt 文件 (10 k lignes + )。

    4) 插入数据库的 psql 查询

    5)restitution 我想知道与 psql 插入相比,复制到 txt 文件是否真的必要且成本高昂?我喜欢您构建自定义字符串类的想法,您认为它可以超越 I/O 性能吗?

    【讨论】:

      【解决方案3】:

      既然你说textetexteTotal是字符串,我有几个建议:

      1.始终使用& operator 连接字符串。

        在 VBScript 中,有两种方法可以连接(相加)两个字符串变量:& 运算符和 + 运算符。 + 运算符通常用于将两个数值相加,但为了向后兼容没有字符串的 & 运算符的旧版本 BASIC,保留该运算符。因为 & 运算符在 VBScript 中可用,所以建议您始终更喜欢使用它来连接字符串并保留 + 用于将数值相加。这不一定会提高速度,但它消除了代码中的任何歧义并明确了您的初衷。这在您使用变体的 VBScript 中尤为重要。当您在可能包含数值的字符串上使用 + 运算符时,您无法知道它是将两个数值相加还是组合两个字符串。

      2。请记住,VBScript 中的字符串连接具有巨大的开销并且非常低效。

        与 VB.NET 和 Java 不同,VBScript 没有 StringBuilder 类来帮助创建大字符串。相反,每当您反复在字符串变量的末尾添加内容时,VB 都会一遍又一遍地复制该变量。当您像在上面的代码中那样在循环中构建字符串时,这确实会降低性能,因为 VB 会不断地为新的字符串变量分配空间并执行复制。随着循环的每次迭代,连接变得越来越慢(用极客的话说,你正在处理一个 n² 算法,其中 n = 连接的数量)。如果字符串的大小超过 64K,问题会变得更糟。 VB 可以将小字符串存储在 64K 缓存中,但如果字符串变得比缓存大,性能下降得更多。为简单起见,程序员当然看不到这种事情,但是如果您关心优化,则可以理解这是在后台发生的事情。

        根据以上信息,让我们重新审视您发布的两个代码示例。你说 `texte` 是“不是很大,但有成百上千行”。这意味着您可能很容易耗尽 64K 字符串缓存中的空间,最终您甚至可能耗尽分配给脚本的 RAM 中的空间。限制会有所不同,但最终可能会出现“内存不足”错误,具体取决于字符串增长的大小。即使你现在离那个点还很远,也值得为未来考虑。如果有人稍后回过头来为脚本添加功能,他们会记得还是费心去更改字符串连接算法?可能不是。

        为防止出现任何“内存不足”错误,您只需停止将文本字符串保存在 RAM 中,而是直接将其写入文件。这在您的情况下更有意义,因为无论如何这就是您最终要对字符串执行的操作! 当您可以将值写入文件而忘记它时,为什么要为循环的每次迭代不断分配和重新分配新的字符串变量来浪费 CPU 周期和内存空间?我想说你的第一个代码示例是完成你想要的最简单和首选的方法。

        我唯一会考虑第二种方法的情况是,如果您正在处理效率极低的文件 I/O,例如通过网络连接访问磁盘。只要您正在写入本地磁盘,这里就不会有任何性能问题。 @astander 指出的另一个问题是,第一种方法会使您正在编写的文件长时间打开,从而锁定该资源。虽然这是一个重要的问题,但我认为它对您的应用程序的影响很小,因为我假设您正在创建和写入您自己的私有文件,而其他应用程序预计无法访问该文件。

        然而,尽管有我的建议,第一种方法仍然不是连接字符串的最优化和最有效的方法。 最佳 方法是 StringBuilder 类的 VBScript 实现,它在创建字符串时将字符串存储在内存中的字节数组中。定期地,当您向字符串添加文本时,连接类会为数组分配更多空间来保存额外的文本。这将比字符串连接的本机 VBScript 实现快得多,因为它执行这些重新分配的频率要低得多。此外,随着您在内存中构建的字符串变大,您的 StringBuilder 类可以将内存中字符串的内容刷新到文件中,并以空字符串重新开始。您可以使用 Francesco Balena 的 CString 类 (http://www.vbcode.com/asp/showsn.asp?theID=415) 或 Microsoft 的示例(包含一些基准测试和进一步的解释),可在此处获得:http://support.microsoft.com/kb/170964

      【讨论】:

      • Cody 非常感谢您抽出宝贵时间。以下是更多信息: 代码示例 #1 是当前的生产代码。更准确地说,这是整个过程的一部分,从 1)从 DB #1 获取信息 2)使用数学公式(矩阵、向量)进行计算,N= BIG 3)将结果复制到 txt 文件(10 k lignes + ) 每个。 4) 插入数据库的 psql 查询 5) 恢复 我想知道与 psql 插入相比,复制到 txt 文件是否真的必要且成本高昂?我喜欢你构建自定义字符串类的想法,你认为它可以超越 I/O 性能吗?
      • 那么现在的设置方式是,文本文件充当字符串连接的临时缓冲区?老实说,我几乎零经验的 Windows 编程领域是数据库,所以我不太确定 psql 插入和写入文本文件之间的延迟比较。不过,我的猜测是,数据库访问总是比任何现代计算机读取/写入文本文件的速度都要慢,除非您的数据库服务器正在缓存输入。除了在之后重新插入结果时,您仍然会受到数据库速度的瓶颈
      • ...您已完成所有计算。也许其他对 psql/数据库有更多经验的人可以在这里帮助我?自定义字符串类绝对是加速字符串连接的可行解决方案,但在您花费太多时间和精力进行基准测试和优化此代码之前,可能值得考虑将例程移植到像 VB 这样的语言有多么困难。 NET 的内置 StringBuilder 类。我怀疑您正在使用的 VBScript 代码会相当轻松地移植到 VB.NET,这比完整的 VB6 项目要容易得多。
      • 我明白,但我现在别无选择。该公司已经使用 VB 很长时间了,而且它不会很快改变......我现在可能不会花精力来定制一个字符串类。我有一个想法,即删除一个循环并减少调用后面的批处理。它可以被优化。我会尽快回复您。再次感谢您的帮助。
      【解决方案4】:

      你必须尝试一下,因为它取决于texte 的大小。

      每次执行texteTotal = texteTotal + texte,vba 都会对textTotal 进行新的复制。随着textTotal 越来越大,你的循环会变慢。

      您还冒着创建大于 vba 可以处理的字符串的风险。

      所以:

      如果您正在写入网络驱动器,并且texte 是单个字符,则第二种方法可能会更好。

      如果您正在写入快速本地磁盘,并且texte 为 64kb,并且每个数组是 1M 条目,则第一种方法会更好。

      【讨论】:

        【解决方案5】:

        我认为最大的区别是您打开文件的时间段。

        在第二种情况下,我会假设它会在更短的时间内打开,这更好,因为您应该只在所需的最短时间段内锁定资源。

        【讨论】:

        • 我明白,我对 vba 有点陌生,我想看看运行时间。你能给我一个小费吗?谢谢,我会很感激的。
        • 我认为您的第二段只有在您处理共享​​>资源时才是正确的。
        • 文件通常是共享资源。
        猜你喜欢
        • 2012-08-06
        • 2013-02-05
        • 1970-01-01
        • 1970-01-01
        • 2020-01-24
        • 2020-08-11
        • 2015-02-21
        • 1970-01-01
        • 2016-08-12
        相关资源
        最近更新 更多