性能替代Scripting.Dictionary

时间:2017-10-30 16:21:53

标签: excel-vba dictionary vba excel

我正在使用几个按钮在Excel-VBA中编写管理器。

其中一个是使用另一个Excel文件(我称之为T)生成一个标签作为输入。

T的一些属性:

  • ~90MB尺寸
  • ~350K
  • 包含过去14个月(无序)的销售数据。
  • 相关专栏:
    • year/month
    • 总钱
    • 卖方名称
    • 家庭产品
    • 客户名称
  • 没有id列(如:cod-client,cod-vendor等)

主要关系:

  • 卖家向许多客户销售许多产品

我正在生成一个新的Excel标签,其中包含卖家分组的最后T的{​​{1}}数据。

重要说明:

  • year/month是唯一可用的输入/来源。
  • 如果两个或多个卖家向同一个客户销售同一产品,T应计入所有卖家。

这已经足够了,现在您知道我的已编码

我的代码有效,,大​​约需要4分钟的运行时间。

我已经使用较小的源(不大于total-money)编码了一些其他按钮,这些按钮在5秒内运行。

考虑2MB大小,可以接受4分钟的运行时

但我并不为此感到骄傲,至少现在还没有。

我的代码主要基于T来映射来自Scripting.Dictionary的数据,然后我使用T将分组数据设置为新创建的标签。

我不确定,但这是我的想法:

  • 如果for each key in obj ... next keyN中的总密钥,我需要在汇总Scripting.Dictionary之前检查obj.Exists(str)。它将运行total-money字符串比较以返回N
  • 同样,当我执行false时,它会运行maximun N字符串。
我希望自己的想法出错。但如果我没有错,那么减少这个函数运行时的下一步(也是最后的希望)就是用Tries 编写我自己的类对象

我明天才会开始编码,我想要的只是一些确认,如果我是以正确的方式,或者是一些建议,如果我做错了。

你有什么建议吗?提前谢谢。

2 个答案:

答案 0 :(得分:1)

超出内存限制

简而言之:

  • 主要问题是因为我使用动态编程方法存储信息(预处理)以加快执行时间。
  • 我的代码现在在~ 13 seconds
  • 中运行

我们学到的东西很难。但我很高兴我找到了答案。

  • 使用任务管理器,我能够看到我的代码达到100%的内存使用量。
  • 我上面提到的使用Scripting.Dictionary的DP方法实际上更快达到了100%。
  • 我上面提到的使用我自己的cls_trie实现的DP方法也达到了100%,但是比第一次提高了。
  • 这解释了~4-5 min与上述尝试的~2-3 min总运行时间进行比较。
  • 在任务管理器中,我还可以看到CPU使用情况从未引用2%

解决方案很简单,我必须平衡CPU和内存使用。

  • 我使用for-loops将一些DP方法更改为简单if-conditions
  • CPU使用率现已达到~15%
  • 内存使用量现已达到~65%
  • 我知道这与每台机器的CPU和内存容量有关。但是在客户端计算机中,它现在的运行时间不超过15 seconds

我创建了一个GitHub repository with my cls_trie implementation并添加了一个带有示例用法的excel文件。

我是excel-vba世界的新手(目前正在使用它4个月)。可能有一些方法可以改进我的cls_trie实现,我向建议开放:

Option Explicit

Public Keys As Collection
Public Children As Variant
Public IsLeaf As Boolean

Public tObject As Variant
Public tValue As Variant

Public Sub Init()
    Set Keys = New Collection
    ReDim Children(0 To 255) As cls_trie
    IsLeaf = False

    Set tObject = Nothing
    tValue = 0
End Sub

Public Function GetNodeAt(index As Integer) As cls_trie
    Set GetNodeAt = Children(index)
End Function

Public Sub CreateNodeAt(index As Integer)
    Set Children(index) = New cls_trie
    Children(index).Init
End Sub

'''
'Following function will retrieve node for a given key,
'creating a entire new branch if necessary
'''
Public Function GetNode(ByRef key As Variant) As cls_trie
    Dim node As cls_trie
    Dim b() As Byte
    Dim i As Integer
    Dim pos As Integer

    b = CStr(key)
    Set node = Me

    For i = 0 To UBound(b) Step 2
        pos = b(i) Mod 256

        If (node.GetNodeAt(pos) Is Nothing) Then
            node.CreateNodeAt pos
        End If

        Set node = node.GetNodeAt(pos)
    Next

    If (node.IsLeaf) Then
        'already existed
    Else
        node.IsLeaf = True
        Keys.Add key
    End If

    Set GetNode = node
End Function

'''
'Following function will get the value for a given key
'Creating the key if necessary
'''
Public Function GetValue(ByRef key As Variant) As Variant
    Dim node As cls_trie
    Set node = GetNode(key)
    GetValue = node.tValue
End Function

'''
'Following sub will set a value to a given key
'Creating the key if necessary
'''
Public Sub SetValue(ByRef key As Variant, value As Variant)
    Dim node As cls_trie
    Set node = GetNode(key)
    node.tValue = value
End Sub

'''
'Following sub will sum up a value for a given key
'Creating the key if necessary
'''
Public Sub SumValue(ByRef key As Variant, value As Variant)
    Dim node As cls_trie
    Set node = GetNode(key)
    node.tValue = node.tValue + value
End Sub

'''
'Following function will validate if given key exists
'''
Public Function Exists(ByRef key As Variant) As Boolean
    Dim node As cls_trie
    Dim b() As Byte
    Dim i As Integer

    b = CStr(key)
    Set node = Me

    For i = 0 To UBound(b) Step 2
        Set node = node.GetNodeAt(b(i) Mod 256)

        If (node Is Nothing) Then
            Exists = False
            Exit Function
        End If
    Next

    Exists = node.IsLeaf
End Function

'''
'Following function will get another Trie from given key
'Creating both key and trie if necessary
'''
Public Function GetTrie(ByRef key As Variant) As cls_trie
    Dim node As cls_trie
    Set node = GetNode(key)

    If (node.tObject Is Nothing) Then
        Set node.tObject = New cls_trie
        node.tObject.Init
    End If

    Set GetTrie = node.tObject
End Function

您可以在上面的代码中看到:

  • 我没有实施任何删除方法,因为到目前为止我还没有这样做。但它很容易实现。
  • 我限制自己只有256个孩子,因为在这个项目中,我正在处理的文本基本上是小写和大写[a-z]字母和数字,以及两个文本被映射到同一个分支节点的概率趋于零。
  

作为一个伟大的程序员说,每个人都喜欢他自己的代码,即使其他代码太漂亮了,不值得讨厌[1]

我的结论

  • 我可能永远不会更多地使用Scripting.Dictionary,即使已经证明它可能比我的cls_trie实施更好。

谢谢大家的帮助。

答案 1 :(得分:1)

我坚信您已经找到了正确的解决方案,因为最近两年没有任何更新。

无论如何,我想提到(也许会帮助其他人)您的瓶颈不是字典或二叉树。如果有足够的RAM,即使有数百万行的内存处理速度也非常快。

瓶颈通常是从工作表中读取数据并将其写回到工作表中。这里的数组非常用人。

只需将数据从工作表中读取到变量数组中即可。 您不必立即使用该阵列。如果您更喜欢使用字典,只需将数组中的所有数据传输到字典中并使用它即可。由于此过程完全在内存中完成,因此不必担心性能下降。

完成字典中的数据处理后,将字典中的所有数据放回到数组中,然后一次将数组写入新工作表中。

Worksheets(“ New Sheet”)。Range(“ A1”)。Value = MyArray

我很确定只需要几秒钟