Bringing The System.Text.StringBuilder Up To Lightning Speed In The VBA

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
6
down vote

favorite












After having to swap the feature rich System.Text.StringBuilder for the very fast but very vanilla A lightning-fast StringBuilder, I decided it was time create a StringBuilder class of my own.



The concept is quite simple: As text is appended to the encapsulating class it is added to an Array. Whenever a method is called that needs to reference all the data, the Array of data is joined and appended to an internal System.Text.StringBuilder.



Speed



The main reason the I choose to use System.Text.StringBuilder and not try and replicate its functionality was the speed of its Replace method. Without a doubt, the StringBuilder.Replace() performance is amazing (+10x faster than VBA.Replace() and 5 MBs of data). The other reason is that no one in their right mind...would do all this just to format strings.



I cho0se not to add any speed timings between the technique the I am using and that of A lightning-fast StringBuilder to my post because they would be highly subjective. At worse my implementation is less than 10% slower; At best it is over 30% faster.
Note: The performance of this class can be optimized by changing the size of the String Array Buffer.



Memory Usage and Capacity



All the StringBuilders mentioned here are memory hogs. The main problem is that when the data expands beyond the StringBuilders capacity, the StringBuilders doubles its buffer size. Unchecked, this limits the actual capacity to theoretically just over %50 percent of its potential.
With some coercing both the System.Text.StringBuilder capacity can be increased. The trick is that you must ensure that the capacity is always greater than maximum data length.



Without modification my StringBuilder class capacity is roughly 500 MB of data on a 10 year old system using with 6 GB of memory running Office 360 32 bit. Having successfully appended a +1 GB string to the System.Text.StringBuilder, I know that this can be increased. Still this is a far cry from the 2 GB string size limit of 32 bit VBA.



Error Handling



What? What "Error Handling"? Exactly!!



I unsuccessfully tried to implement a System, by which, the length of both the Buffer strings and the actual StringBuilder string length would be tracked and EnsureCapacity() would be used to maximize the potential storage space. This System included Error Handlers which would trigger the data to be compacted when the size of the data in the Buffer throw an error. In any case, it didn't work so I gutted it.



References



This code is based off of both vba_stringbuilder:Class - and the actual System.Text.StringBuilder: Documentation. Most of the descriptions below are excerpts from that Documentation.



enter image description here



enter image description here



StringBuilder:Class



Option Explicit
' Based off vba_stringbuilder:Class - http://blog.livedoor.jp/midorityo/archives/50744656.html
Private Type Members
AppendsBufferSize As Long
Builder As Object
index As Long
End Type
Private AppendsBuffer() As String
Private m As Members

Private Sub Class_Initialize()
Const DEFAULT_APPENDSBUFFER_SIZE = 10000
Set m.Builder = CreateObject("System.Text.StringBuilder")
setAppendsBufferSize DEFAULT_APPENDSBUFFER_SIZE
m.index = -1
End Sub

Public Property Get Capacity() As Long
Capacity = m.Builder.Capacity
End Property

Public Property Let Capacity(ByVal Length As Long)
m.Builder.Capacity = Length
End Property

Public Property Get Length() As Long
Length = m.Builder.Length
End Property

Public Property Let Length(ByVal lLength As Long)
m.Builder.Length = lLength
End Property

Public Function MaxCapacity() As Long
MaxCapacity = m.Builder.MaxCapacity
End Function

Public Function Append(ByRef Text As String, Optional repeatCount As Long = 0) As StringBuilder
m.index = m.index + 1
AppendsBuffer(m.index) = Text
If repeatCount > 0 Then Me.Append Text, (repeatCount - 1)
If m.index = m.AppendsBufferSize Then Compact
Set Append = Me
End Function

Public Function AppendFormat(ByVal format As Variant, ParamArray Args() As Variant) As StringBuilder
Dim newArgs() As Variant
Dim j As Long
Me.Compact

If IsArray(Args(0)) Then
newArgs = Args(0)
Else
ReDim newArgs(UBound(Args))
For j = 0 To UBound(Args)
newArgs(j) = Args(j)
Next
End If

m.Builder.AppendFormat_4 format, newArgs
Set AppendFormat = Me
End Function

Public Function InsertFormat(ByVal format As Variant, startIndex As Long, repeatCount As Long, ParamArray Args() As Variant) As StringBuilder
Dim newArgs() As Variant, sb As New StringBuilder
Dim j As Long
If IsArray(Args(0)) Then
newArgs = Args(0)
Else
ReDim newArgs(UBound(Args))
For j = 0 To UBound(Args)
newArgs(j) = Args(j)
Next
End If

sb.AppendFormat format, newArgs
Me.Insert startIndex, sb.ToString
Set InsertFormat = Me
End Function

Public Function AppendLine(Optional ByRef Text As String, Optional repeatCount As Long = 0) As StringBuilder
m.index = m.index + 1
AppendsBuffer(m.index) = Text & vbNewLine
If repeatCount > 0 Then Me.AppendLine Text, (repeatCount - 1)
If m.index = m.AppendsBufferSize Then Compact
Set AppendLine = Me
End Function

Public Sub Clear()
Me.Length = 0
End Sub

Public Sub Compact()
If m.index > -1 Then
m.Builder.Append_3 Join(AppendsBuffer, vbNullString)
m.index = -1
End If
ReDim AppendsBuffer(m.AppendsBufferSize)
End Sub

Public Function EnsureCapacity(ByVal Length As Long) As StringBuilder
m.Builder.EnsureCapacity Length
Set EnsureCapacity = Me
End Function

Public Function Insert(startIndex As Long, Text As String, Optional repeatCount As Long = 0) As StringBuilder
Me.Compact
m.Builder.Insert_2 startIndex, Text
If repeatCount > 0 Then Me.Insert startIndex, Text, (repeatCount - 1)
Set Insert = Me
End Function

Public Function Remove(ByVal startIndex As Long, ByVal Length As Long) As StringBuilder
Me.Compact
m.Builder.Remove startIndex, Length
Set Remove = Me
End Function

Public Function Replace(oldValue As String, newValue As String, Optional startIndex As Long = -1, Optional repeatCount As Long = 0) As StringBuilder
Me.Compact

If startIndex > -1 And repeatCount > 0 Then
m.Builder.Replace_2 oldValue, newValue, startIndex, repeatCount
Else
m.Builder.Replace oldValue, newValue
End If
Set Replace = Me
End Function

Public Sub setAppendsBufferSize(ByVal Length As Long)
m.AppendsBufferSize = Length
Me.Compact
End Sub

Function ToString(Optional ByVal startIndex As Long = -1, Optional ByVal Length As Long = 0) As String
Me.Compact

If startIndex > -1 And Length > 0 Then
ToString = m.Builder.ToString_2(startIndex, Length)
Else
ToString = m.Builder.ToString
End If
End Function


AppendFormat - Test



Sub TestStringBuilderAppendFormat()
Dim sb As New StringBuilder
'Test AppendFormat Numbers: https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings
sb.AppendLine "Using Standard Numeric Format Strings - Currency (""C"") Format Specifier"
sb.AppendFormat "0:C2", 123.456
sb.AppendLine "Currency With Alignment Arguments"
sb.AppendLine " Beginning Balance Ending Balance"
sb.AppendFormat " 0,-28:C21,14:C2", 16305.32, 18794.16
sb.AppendLine
sb.AppendLine "The Decimal (""D"") Format Specifier"
sb.AppendFormat "0:D", 12345
sb.AppendLine
sb.AppendLine "8 Digit Format Specifier"
sb.AppendFormat "0:D8", 12345
sb.AppendLine
sb.AppendLine "The Percent (""P"") Format Specifier"
sb.AppendFormat "0:P", 0.2468013
sb.AppendLine "8 Digit Format Specifier"
sb.AppendFormat "0:P8", 0.2468013
sb.AppendLine
sb.AppendLine "Custom Tests" & vbNewLine & String(50, "*")

sb.AppendLine "AppendFormat: Dates"
sb.AppendFormat "Day ## 0:dd, Day Name 0:dddd, Month ## 0:MM, Month Name 0:MMMM, YYYY 0:yyyy", Date
sb.InsertFormat "Date 0", 0, 0, "Formats: "

sb.AppendLine "AppendFormat: ParamArray"
sb.AppendFormat "Jack 0 Jill 1 up 2 hill 3 fetch 4 pail 5 water 6 fell 7 And 8 his 9 And 10 came 11 after", "and", "Went", "the", "To", "a", "of", "Jack", "down", "broke", "crown,", "Jill", "tumbling"

sb.AppendLine "AppendFormat: Array"
sb.AppendFormat "Jack 0 Jill 1 up 2 hill 3 fetch 4 pail 5 water 6 fell 7 And 8 his 9 And 10 came 11 after", Array("and", "Went", "the", "To", "a", "of", "Jack", "down", "broke", "crown,", "Jill", "tumbling")
Debug.Print sb.ToString

End Sub


Output:




Date Formats: Using Standard Numeric Format Strings - Currency ("C") Format Specifier
$123.46Currency With Alignment Arguments
Beginning Balance Ending Balance
$16,305.32 $18,794.16
The Decimal ("D") Format Specifier
12345
8 Digit Format Specifier
00012345
The Percent ("P") Format Specifier
24.68 %8 Digit Format Specifier
24.68013000 %
Custom Tests
**************************************************
AppendFormat: Dates
Day ## 07, Day Name Thursday, Month ## 06, Month Name June, YYYY 2018AppendFormat: ParamArray
Jack and Jill Went up the hill To fetch a pail of water Jack fell down And broke his crown, And Jill came tumbling afterAppendFormat: Array
Jack and Jill Went up the hill To fetch a pail of water Jack fell down And broke his crown, And Jill came tumbling after


I found this project interesting enough that I created a new account after deleting mine 1 week ago. I am looking for anything that I might have missed or any errors in my implementation, any ideas on how to maximize the capacity, and any strategies for handling errors.







share|improve this question















  • 1




    Nice to have you back, and nice question!
    – Phrancis
    Jun 8 at 0:18










  • @Phrancis thanks. Good to be back.
    – TinMan
    Jun 11 at 11:23
















up vote
6
down vote

favorite












After having to swap the feature rich System.Text.StringBuilder for the very fast but very vanilla A lightning-fast StringBuilder, I decided it was time create a StringBuilder class of my own.



The concept is quite simple: As text is appended to the encapsulating class it is added to an Array. Whenever a method is called that needs to reference all the data, the Array of data is joined and appended to an internal System.Text.StringBuilder.



Speed



The main reason the I choose to use System.Text.StringBuilder and not try and replicate its functionality was the speed of its Replace method. Without a doubt, the StringBuilder.Replace() performance is amazing (+10x faster than VBA.Replace() and 5 MBs of data). The other reason is that no one in their right mind...would do all this just to format strings.



I cho0se not to add any speed timings between the technique the I am using and that of A lightning-fast StringBuilder to my post because they would be highly subjective. At worse my implementation is less than 10% slower; At best it is over 30% faster.
Note: The performance of this class can be optimized by changing the size of the String Array Buffer.



Memory Usage and Capacity



All the StringBuilders mentioned here are memory hogs. The main problem is that when the data expands beyond the StringBuilders capacity, the StringBuilders doubles its buffer size. Unchecked, this limits the actual capacity to theoretically just over %50 percent of its potential.
With some coercing both the System.Text.StringBuilder capacity can be increased. The trick is that you must ensure that the capacity is always greater than maximum data length.



Without modification my StringBuilder class capacity is roughly 500 MB of data on a 10 year old system using with 6 GB of memory running Office 360 32 bit. Having successfully appended a +1 GB string to the System.Text.StringBuilder, I know that this can be increased. Still this is a far cry from the 2 GB string size limit of 32 bit VBA.



Error Handling



What? What "Error Handling"? Exactly!!



I unsuccessfully tried to implement a System, by which, the length of both the Buffer strings and the actual StringBuilder string length would be tracked and EnsureCapacity() would be used to maximize the potential storage space. This System included Error Handlers which would trigger the data to be compacted when the size of the data in the Buffer throw an error. In any case, it didn't work so I gutted it.



References



This code is based off of both vba_stringbuilder:Class - and the actual System.Text.StringBuilder: Documentation. Most of the descriptions below are excerpts from that Documentation.



enter image description here



enter image description here



StringBuilder:Class



Option Explicit
' Based off vba_stringbuilder:Class - http://blog.livedoor.jp/midorityo/archives/50744656.html
Private Type Members
AppendsBufferSize As Long
Builder As Object
index As Long
End Type
Private AppendsBuffer() As String
Private m As Members

Private Sub Class_Initialize()
Const DEFAULT_APPENDSBUFFER_SIZE = 10000
Set m.Builder = CreateObject("System.Text.StringBuilder")
setAppendsBufferSize DEFAULT_APPENDSBUFFER_SIZE
m.index = -1
End Sub

Public Property Get Capacity() As Long
Capacity = m.Builder.Capacity
End Property

Public Property Let Capacity(ByVal Length As Long)
m.Builder.Capacity = Length
End Property

Public Property Get Length() As Long
Length = m.Builder.Length
End Property

Public Property Let Length(ByVal lLength As Long)
m.Builder.Length = lLength
End Property

Public Function MaxCapacity() As Long
MaxCapacity = m.Builder.MaxCapacity
End Function

Public Function Append(ByRef Text As String, Optional repeatCount As Long = 0) As StringBuilder
m.index = m.index + 1
AppendsBuffer(m.index) = Text
If repeatCount > 0 Then Me.Append Text, (repeatCount - 1)
If m.index = m.AppendsBufferSize Then Compact
Set Append = Me
End Function

Public Function AppendFormat(ByVal format As Variant, ParamArray Args() As Variant) As StringBuilder
Dim newArgs() As Variant
Dim j As Long
Me.Compact

If IsArray(Args(0)) Then
newArgs = Args(0)
Else
ReDim newArgs(UBound(Args))
For j = 0 To UBound(Args)
newArgs(j) = Args(j)
Next
End If

m.Builder.AppendFormat_4 format, newArgs
Set AppendFormat = Me
End Function

Public Function InsertFormat(ByVal format As Variant, startIndex As Long, repeatCount As Long, ParamArray Args() As Variant) As StringBuilder
Dim newArgs() As Variant, sb As New StringBuilder
Dim j As Long
If IsArray(Args(0)) Then
newArgs = Args(0)
Else
ReDim newArgs(UBound(Args))
For j = 0 To UBound(Args)
newArgs(j) = Args(j)
Next
End If

sb.AppendFormat format, newArgs
Me.Insert startIndex, sb.ToString
Set InsertFormat = Me
End Function

Public Function AppendLine(Optional ByRef Text As String, Optional repeatCount As Long = 0) As StringBuilder
m.index = m.index + 1
AppendsBuffer(m.index) = Text & vbNewLine
If repeatCount > 0 Then Me.AppendLine Text, (repeatCount - 1)
If m.index = m.AppendsBufferSize Then Compact
Set AppendLine = Me
End Function

Public Sub Clear()
Me.Length = 0
End Sub

Public Sub Compact()
If m.index > -1 Then
m.Builder.Append_3 Join(AppendsBuffer, vbNullString)
m.index = -1
End If
ReDim AppendsBuffer(m.AppendsBufferSize)
End Sub

Public Function EnsureCapacity(ByVal Length As Long) As StringBuilder
m.Builder.EnsureCapacity Length
Set EnsureCapacity = Me
End Function

Public Function Insert(startIndex As Long, Text As String, Optional repeatCount As Long = 0) As StringBuilder
Me.Compact
m.Builder.Insert_2 startIndex, Text
If repeatCount > 0 Then Me.Insert startIndex, Text, (repeatCount - 1)
Set Insert = Me
End Function

Public Function Remove(ByVal startIndex As Long, ByVal Length As Long) As StringBuilder
Me.Compact
m.Builder.Remove startIndex, Length
Set Remove = Me
End Function

Public Function Replace(oldValue As String, newValue As String, Optional startIndex As Long = -1, Optional repeatCount As Long = 0) As StringBuilder
Me.Compact

If startIndex > -1 And repeatCount > 0 Then
m.Builder.Replace_2 oldValue, newValue, startIndex, repeatCount
Else
m.Builder.Replace oldValue, newValue
End If
Set Replace = Me
End Function

Public Sub setAppendsBufferSize(ByVal Length As Long)
m.AppendsBufferSize = Length
Me.Compact
End Sub

Function ToString(Optional ByVal startIndex As Long = -1, Optional ByVal Length As Long = 0) As String
Me.Compact

If startIndex > -1 And Length > 0 Then
ToString = m.Builder.ToString_2(startIndex, Length)
Else
ToString = m.Builder.ToString
End If
End Function


AppendFormat - Test



Sub TestStringBuilderAppendFormat()
Dim sb As New StringBuilder
'Test AppendFormat Numbers: https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings
sb.AppendLine "Using Standard Numeric Format Strings - Currency (""C"") Format Specifier"
sb.AppendFormat "0:C2", 123.456
sb.AppendLine "Currency With Alignment Arguments"
sb.AppendLine " Beginning Balance Ending Balance"
sb.AppendFormat " 0,-28:C21,14:C2", 16305.32, 18794.16
sb.AppendLine
sb.AppendLine "The Decimal (""D"") Format Specifier"
sb.AppendFormat "0:D", 12345
sb.AppendLine
sb.AppendLine "8 Digit Format Specifier"
sb.AppendFormat "0:D8", 12345
sb.AppendLine
sb.AppendLine "The Percent (""P"") Format Specifier"
sb.AppendFormat "0:P", 0.2468013
sb.AppendLine "8 Digit Format Specifier"
sb.AppendFormat "0:P8", 0.2468013
sb.AppendLine
sb.AppendLine "Custom Tests" & vbNewLine & String(50, "*")

sb.AppendLine "AppendFormat: Dates"
sb.AppendFormat "Day ## 0:dd, Day Name 0:dddd, Month ## 0:MM, Month Name 0:MMMM, YYYY 0:yyyy", Date
sb.InsertFormat "Date 0", 0, 0, "Formats: "

sb.AppendLine "AppendFormat: ParamArray"
sb.AppendFormat "Jack 0 Jill 1 up 2 hill 3 fetch 4 pail 5 water 6 fell 7 And 8 his 9 And 10 came 11 after", "and", "Went", "the", "To", "a", "of", "Jack", "down", "broke", "crown,", "Jill", "tumbling"

sb.AppendLine "AppendFormat: Array"
sb.AppendFormat "Jack 0 Jill 1 up 2 hill 3 fetch 4 pail 5 water 6 fell 7 And 8 his 9 And 10 came 11 after", Array("and", "Went", "the", "To", "a", "of", "Jack", "down", "broke", "crown,", "Jill", "tumbling")
Debug.Print sb.ToString

End Sub


Output:




Date Formats: Using Standard Numeric Format Strings - Currency ("C") Format Specifier
$123.46Currency With Alignment Arguments
Beginning Balance Ending Balance
$16,305.32 $18,794.16
The Decimal ("D") Format Specifier
12345
8 Digit Format Specifier
00012345
The Percent ("P") Format Specifier
24.68 %8 Digit Format Specifier
24.68013000 %
Custom Tests
**************************************************
AppendFormat: Dates
Day ## 07, Day Name Thursday, Month ## 06, Month Name June, YYYY 2018AppendFormat: ParamArray
Jack and Jill Went up the hill To fetch a pail of water Jack fell down And broke his crown, And Jill came tumbling afterAppendFormat: Array
Jack and Jill Went up the hill To fetch a pail of water Jack fell down And broke his crown, And Jill came tumbling after


I found this project interesting enough that I created a new account after deleting mine 1 week ago. I am looking for anything that I might have missed or any errors in my implementation, any ideas on how to maximize the capacity, and any strategies for handling errors.







share|improve this question















  • 1




    Nice to have you back, and nice question!
    – Phrancis
    Jun 8 at 0:18










  • @Phrancis thanks. Good to be back.
    – TinMan
    Jun 11 at 11:23












up vote
6
down vote

favorite









up vote
6
down vote

favorite











After having to swap the feature rich System.Text.StringBuilder for the very fast but very vanilla A lightning-fast StringBuilder, I decided it was time create a StringBuilder class of my own.



The concept is quite simple: As text is appended to the encapsulating class it is added to an Array. Whenever a method is called that needs to reference all the data, the Array of data is joined and appended to an internal System.Text.StringBuilder.



Speed



The main reason the I choose to use System.Text.StringBuilder and not try and replicate its functionality was the speed of its Replace method. Without a doubt, the StringBuilder.Replace() performance is amazing (+10x faster than VBA.Replace() and 5 MBs of data). The other reason is that no one in their right mind...would do all this just to format strings.



I cho0se not to add any speed timings between the technique the I am using and that of A lightning-fast StringBuilder to my post because they would be highly subjective. At worse my implementation is less than 10% slower; At best it is over 30% faster.
Note: The performance of this class can be optimized by changing the size of the String Array Buffer.



Memory Usage and Capacity



All the StringBuilders mentioned here are memory hogs. The main problem is that when the data expands beyond the StringBuilders capacity, the StringBuilders doubles its buffer size. Unchecked, this limits the actual capacity to theoretically just over %50 percent of its potential.
With some coercing both the System.Text.StringBuilder capacity can be increased. The trick is that you must ensure that the capacity is always greater than maximum data length.



Without modification my StringBuilder class capacity is roughly 500 MB of data on a 10 year old system using with 6 GB of memory running Office 360 32 bit. Having successfully appended a +1 GB string to the System.Text.StringBuilder, I know that this can be increased. Still this is a far cry from the 2 GB string size limit of 32 bit VBA.



Error Handling



What? What "Error Handling"? Exactly!!



I unsuccessfully tried to implement a System, by which, the length of both the Buffer strings and the actual StringBuilder string length would be tracked and EnsureCapacity() would be used to maximize the potential storage space. This System included Error Handlers which would trigger the data to be compacted when the size of the data in the Buffer throw an error. In any case, it didn't work so I gutted it.



References



This code is based off of both vba_stringbuilder:Class - and the actual System.Text.StringBuilder: Documentation. Most of the descriptions below are excerpts from that Documentation.



enter image description here



enter image description here



StringBuilder:Class



Option Explicit
' Based off vba_stringbuilder:Class - http://blog.livedoor.jp/midorityo/archives/50744656.html
Private Type Members
AppendsBufferSize As Long
Builder As Object
index As Long
End Type
Private AppendsBuffer() As String
Private m As Members

Private Sub Class_Initialize()
Const DEFAULT_APPENDSBUFFER_SIZE = 10000
Set m.Builder = CreateObject("System.Text.StringBuilder")
setAppendsBufferSize DEFAULT_APPENDSBUFFER_SIZE
m.index = -1
End Sub

Public Property Get Capacity() As Long
Capacity = m.Builder.Capacity
End Property

Public Property Let Capacity(ByVal Length As Long)
m.Builder.Capacity = Length
End Property

Public Property Get Length() As Long
Length = m.Builder.Length
End Property

Public Property Let Length(ByVal lLength As Long)
m.Builder.Length = lLength
End Property

Public Function MaxCapacity() As Long
MaxCapacity = m.Builder.MaxCapacity
End Function

Public Function Append(ByRef Text As String, Optional repeatCount As Long = 0) As StringBuilder
m.index = m.index + 1
AppendsBuffer(m.index) = Text
If repeatCount > 0 Then Me.Append Text, (repeatCount - 1)
If m.index = m.AppendsBufferSize Then Compact
Set Append = Me
End Function

Public Function AppendFormat(ByVal format As Variant, ParamArray Args() As Variant) As StringBuilder
Dim newArgs() As Variant
Dim j As Long
Me.Compact

If IsArray(Args(0)) Then
newArgs = Args(0)
Else
ReDim newArgs(UBound(Args))
For j = 0 To UBound(Args)
newArgs(j) = Args(j)
Next
End If

m.Builder.AppendFormat_4 format, newArgs
Set AppendFormat = Me
End Function

Public Function InsertFormat(ByVal format As Variant, startIndex As Long, repeatCount As Long, ParamArray Args() As Variant) As StringBuilder
Dim newArgs() As Variant, sb As New StringBuilder
Dim j As Long
If IsArray(Args(0)) Then
newArgs = Args(0)
Else
ReDim newArgs(UBound(Args))
For j = 0 To UBound(Args)
newArgs(j) = Args(j)
Next
End If

sb.AppendFormat format, newArgs
Me.Insert startIndex, sb.ToString
Set InsertFormat = Me
End Function

Public Function AppendLine(Optional ByRef Text As String, Optional repeatCount As Long = 0) As StringBuilder
m.index = m.index + 1
AppendsBuffer(m.index) = Text & vbNewLine
If repeatCount > 0 Then Me.AppendLine Text, (repeatCount - 1)
If m.index = m.AppendsBufferSize Then Compact
Set AppendLine = Me
End Function

Public Sub Clear()
Me.Length = 0
End Sub

Public Sub Compact()
If m.index > -1 Then
m.Builder.Append_3 Join(AppendsBuffer, vbNullString)
m.index = -1
End If
ReDim AppendsBuffer(m.AppendsBufferSize)
End Sub

Public Function EnsureCapacity(ByVal Length As Long) As StringBuilder
m.Builder.EnsureCapacity Length
Set EnsureCapacity = Me
End Function

Public Function Insert(startIndex As Long, Text As String, Optional repeatCount As Long = 0) As StringBuilder
Me.Compact
m.Builder.Insert_2 startIndex, Text
If repeatCount > 0 Then Me.Insert startIndex, Text, (repeatCount - 1)
Set Insert = Me
End Function

Public Function Remove(ByVal startIndex As Long, ByVal Length As Long) As StringBuilder
Me.Compact
m.Builder.Remove startIndex, Length
Set Remove = Me
End Function

Public Function Replace(oldValue As String, newValue As String, Optional startIndex As Long = -1, Optional repeatCount As Long = 0) As StringBuilder
Me.Compact

If startIndex > -1 And repeatCount > 0 Then
m.Builder.Replace_2 oldValue, newValue, startIndex, repeatCount
Else
m.Builder.Replace oldValue, newValue
End If
Set Replace = Me
End Function

Public Sub setAppendsBufferSize(ByVal Length As Long)
m.AppendsBufferSize = Length
Me.Compact
End Sub

Function ToString(Optional ByVal startIndex As Long = -1, Optional ByVal Length As Long = 0) As String
Me.Compact

If startIndex > -1 And Length > 0 Then
ToString = m.Builder.ToString_2(startIndex, Length)
Else
ToString = m.Builder.ToString
End If
End Function


AppendFormat - Test



Sub TestStringBuilderAppendFormat()
Dim sb As New StringBuilder
'Test AppendFormat Numbers: https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings
sb.AppendLine "Using Standard Numeric Format Strings - Currency (""C"") Format Specifier"
sb.AppendFormat "0:C2", 123.456
sb.AppendLine "Currency With Alignment Arguments"
sb.AppendLine " Beginning Balance Ending Balance"
sb.AppendFormat " 0,-28:C21,14:C2", 16305.32, 18794.16
sb.AppendLine
sb.AppendLine "The Decimal (""D"") Format Specifier"
sb.AppendFormat "0:D", 12345
sb.AppendLine
sb.AppendLine "8 Digit Format Specifier"
sb.AppendFormat "0:D8", 12345
sb.AppendLine
sb.AppendLine "The Percent (""P"") Format Specifier"
sb.AppendFormat "0:P", 0.2468013
sb.AppendLine "8 Digit Format Specifier"
sb.AppendFormat "0:P8", 0.2468013
sb.AppendLine
sb.AppendLine "Custom Tests" & vbNewLine & String(50, "*")

sb.AppendLine "AppendFormat: Dates"
sb.AppendFormat "Day ## 0:dd, Day Name 0:dddd, Month ## 0:MM, Month Name 0:MMMM, YYYY 0:yyyy", Date
sb.InsertFormat "Date 0", 0, 0, "Formats: "

sb.AppendLine "AppendFormat: ParamArray"
sb.AppendFormat "Jack 0 Jill 1 up 2 hill 3 fetch 4 pail 5 water 6 fell 7 And 8 his 9 And 10 came 11 after", "and", "Went", "the", "To", "a", "of", "Jack", "down", "broke", "crown,", "Jill", "tumbling"

sb.AppendLine "AppendFormat: Array"
sb.AppendFormat "Jack 0 Jill 1 up 2 hill 3 fetch 4 pail 5 water 6 fell 7 And 8 his 9 And 10 came 11 after", Array("and", "Went", "the", "To", "a", "of", "Jack", "down", "broke", "crown,", "Jill", "tumbling")
Debug.Print sb.ToString

End Sub


Output:




Date Formats: Using Standard Numeric Format Strings - Currency ("C") Format Specifier
$123.46Currency With Alignment Arguments
Beginning Balance Ending Balance
$16,305.32 $18,794.16
The Decimal ("D") Format Specifier
12345
8 Digit Format Specifier
00012345
The Percent ("P") Format Specifier
24.68 %8 Digit Format Specifier
24.68013000 %
Custom Tests
**************************************************
AppendFormat: Dates
Day ## 07, Day Name Thursday, Month ## 06, Month Name June, YYYY 2018AppendFormat: ParamArray
Jack and Jill Went up the hill To fetch a pail of water Jack fell down And broke his crown, And Jill came tumbling afterAppendFormat: Array
Jack and Jill Went up the hill To fetch a pail of water Jack fell down And broke his crown, And Jill came tumbling after


I found this project interesting enough that I created a new account after deleting mine 1 week ago. I am looking for anything that I might have missed or any errors in my implementation, any ideas on how to maximize the capacity, and any strategies for handling errors.







share|improve this question











After having to swap the feature rich System.Text.StringBuilder for the very fast but very vanilla A lightning-fast StringBuilder, I decided it was time create a StringBuilder class of my own.



The concept is quite simple: As text is appended to the encapsulating class it is added to an Array. Whenever a method is called that needs to reference all the data, the Array of data is joined and appended to an internal System.Text.StringBuilder.



Speed



The main reason the I choose to use System.Text.StringBuilder and not try and replicate its functionality was the speed of its Replace method. Without a doubt, the StringBuilder.Replace() performance is amazing (+10x faster than VBA.Replace() and 5 MBs of data). The other reason is that no one in their right mind...would do all this just to format strings.



I cho0se not to add any speed timings between the technique the I am using and that of A lightning-fast StringBuilder to my post because they would be highly subjective. At worse my implementation is less than 10% slower; At best it is over 30% faster.
Note: The performance of this class can be optimized by changing the size of the String Array Buffer.



Memory Usage and Capacity



All the StringBuilders mentioned here are memory hogs. The main problem is that when the data expands beyond the StringBuilders capacity, the StringBuilders doubles its buffer size. Unchecked, this limits the actual capacity to theoretically just over %50 percent of its potential.
With some coercing both the System.Text.StringBuilder capacity can be increased. The trick is that you must ensure that the capacity is always greater than maximum data length.



Without modification my StringBuilder class capacity is roughly 500 MB of data on a 10 year old system using with 6 GB of memory running Office 360 32 bit. Having successfully appended a +1 GB string to the System.Text.StringBuilder, I know that this can be increased. Still this is a far cry from the 2 GB string size limit of 32 bit VBA.



Error Handling



What? What "Error Handling"? Exactly!!



I unsuccessfully tried to implement a System, by which, the length of both the Buffer strings and the actual StringBuilder string length would be tracked and EnsureCapacity() would be used to maximize the potential storage space. This System included Error Handlers which would trigger the data to be compacted when the size of the data in the Buffer throw an error. In any case, it didn't work so I gutted it.



References



This code is based off of both vba_stringbuilder:Class - and the actual System.Text.StringBuilder: Documentation. Most of the descriptions below are excerpts from that Documentation.



enter image description here



enter image description here



StringBuilder:Class



Option Explicit
' Based off vba_stringbuilder:Class - http://blog.livedoor.jp/midorityo/archives/50744656.html
Private Type Members
AppendsBufferSize As Long
Builder As Object
index As Long
End Type
Private AppendsBuffer() As String
Private m As Members

Private Sub Class_Initialize()
Const DEFAULT_APPENDSBUFFER_SIZE = 10000
Set m.Builder = CreateObject("System.Text.StringBuilder")
setAppendsBufferSize DEFAULT_APPENDSBUFFER_SIZE
m.index = -1
End Sub

Public Property Get Capacity() As Long
Capacity = m.Builder.Capacity
End Property

Public Property Let Capacity(ByVal Length As Long)
m.Builder.Capacity = Length
End Property

Public Property Get Length() As Long
Length = m.Builder.Length
End Property

Public Property Let Length(ByVal lLength As Long)
m.Builder.Length = lLength
End Property

Public Function MaxCapacity() As Long
MaxCapacity = m.Builder.MaxCapacity
End Function

Public Function Append(ByRef Text As String, Optional repeatCount As Long = 0) As StringBuilder
m.index = m.index + 1
AppendsBuffer(m.index) = Text
If repeatCount > 0 Then Me.Append Text, (repeatCount - 1)
If m.index = m.AppendsBufferSize Then Compact
Set Append = Me
End Function

Public Function AppendFormat(ByVal format As Variant, ParamArray Args() As Variant) As StringBuilder
Dim newArgs() As Variant
Dim j As Long
Me.Compact

If IsArray(Args(0)) Then
newArgs = Args(0)
Else
ReDim newArgs(UBound(Args))
For j = 0 To UBound(Args)
newArgs(j) = Args(j)
Next
End If

m.Builder.AppendFormat_4 format, newArgs
Set AppendFormat = Me
End Function

Public Function InsertFormat(ByVal format As Variant, startIndex As Long, repeatCount As Long, ParamArray Args() As Variant) As StringBuilder
Dim newArgs() As Variant, sb As New StringBuilder
Dim j As Long
If IsArray(Args(0)) Then
newArgs = Args(0)
Else
ReDim newArgs(UBound(Args))
For j = 0 To UBound(Args)
newArgs(j) = Args(j)
Next
End If

sb.AppendFormat format, newArgs
Me.Insert startIndex, sb.ToString
Set InsertFormat = Me
End Function

Public Function AppendLine(Optional ByRef Text As String, Optional repeatCount As Long = 0) As StringBuilder
m.index = m.index + 1
AppendsBuffer(m.index) = Text & vbNewLine
If repeatCount > 0 Then Me.AppendLine Text, (repeatCount - 1)
If m.index = m.AppendsBufferSize Then Compact
Set AppendLine = Me
End Function

Public Sub Clear()
Me.Length = 0
End Sub

Public Sub Compact()
If m.index > -1 Then
m.Builder.Append_3 Join(AppendsBuffer, vbNullString)
m.index = -1
End If
ReDim AppendsBuffer(m.AppendsBufferSize)
End Sub

Public Function EnsureCapacity(ByVal Length As Long) As StringBuilder
m.Builder.EnsureCapacity Length
Set EnsureCapacity = Me
End Function

Public Function Insert(startIndex As Long, Text As String, Optional repeatCount As Long = 0) As StringBuilder
Me.Compact
m.Builder.Insert_2 startIndex, Text
If repeatCount > 0 Then Me.Insert startIndex, Text, (repeatCount - 1)
Set Insert = Me
End Function

Public Function Remove(ByVal startIndex As Long, ByVal Length As Long) As StringBuilder
Me.Compact
m.Builder.Remove startIndex, Length
Set Remove = Me
End Function

Public Function Replace(oldValue As String, newValue As String, Optional startIndex As Long = -1, Optional repeatCount As Long = 0) As StringBuilder
Me.Compact

If startIndex > -1 And repeatCount > 0 Then
m.Builder.Replace_2 oldValue, newValue, startIndex, repeatCount
Else
m.Builder.Replace oldValue, newValue
End If
Set Replace = Me
End Function

Public Sub setAppendsBufferSize(ByVal Length As Long)
m.AppendsBufferSize = Length
Me.Compact
End Sub

Function ToString(Optional ByVal startIndex As Long = -1, Optional ByVal Length As Long = 0) As String
Me.Compact

If startIndex > -1 And Length > 0 Then
ToString = m.Builder.ToString_2(startIndex, Length)
Else
ToString = m.Builder.ToString
End If
End Function


AppendFormat - Test



Sub TestStringBuilderAppendFormat()
Dim sb As New StringBuilder
'Test AppendFormat Numbers: https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings
sb.AppendLine "Using Standard Numeric Format Strings - Currency (""C"") Format Specifier"
sb.AppendFormat "0:C2", 123.456
sb.AppendLine "Currency With Alignment Arguments"
sb.AppendLine " Beginning Balance Ending Balance"
sb.AppendFormat " 0,-28:C21,14:C2", 16305.32, 18794.16
sb.AppendLine
sb.AppendLine "The Decimal (""D"") Format Specifier"
sb.AppendFormat "0:D", 12345
sb.AppendLine
sb.AppendLine "8 Digit Format Specifier"
sb.AppendFormat "0:D8", 12345
sb.AppendLine
sb.AppendLine "The Percent (""P"") Format Specifier"
sb.AppendFormat "0:P", 0.2468013
sb.AppendLine "8 Digit Format Specifier"
sb.AppendFormat "0:P8", 0.2468013
sb.AppendLine
sb.AppendLine "Custom Tests" & vbNewLine & String(50, "*")

sb.AppendLine "AppendFormat: Dates"
sb.AppendFormat "Day ## 0:dd, Day Name 0:dddd, Month ## 0:MM, Month Name 0:MMMM, YYYY 0:yyyy", Date
sb.InsertFormat "Date 0", 0, 0, "Formats: "

sb.AppendLine "AppendFormat: ParamArray"
sb.AppendFormat "Jack 0 Jill 1 up 2 hill 3 fetch 4 pail 5 water 6 fell 7 And 8 his 9 And 10 came 11 after", "and", "Went", "the", "To", "a", "of", "Jack", "down", "broke", "crown,", "Jill", "tumbling"

sb.AppendLine "AppendFormat: Array"
sb.AppendFormat "Jack 0 Jill 1 up 2 hill 3 fetch 4 pail 5 water 6 fell 7 And 8 his 9 And 10 came 11 after", Array("and", "Went", "the", "To", "a", "of", "Jack", "down", "broke", "crown,", "Jill", "tumbling")
Debug.Print sb.ToString

End Sub


Output:




Date Formats: Using Standard Numeric Format Strings - Currency ("C") Format Specifier
$123.46Currency With Alignment Arguments
Beginning Balance Ending Balance
$16,305.32 $18,794.16
The Decimal ("D") Format Specifier
12345
8 Digit Format Specifier
00012345
The Percent ("P") Format Specifier
24.68 %8 Digit Format Specifier
24.68013000 %
Custom Tests
**************************************************
AppendFormat: Dates
Day ## 07, Day Name Thursday, Month ## 06, Month Name June, YYYY 2018AppendFormat: ParamArray
Jack and Jill Went up the hill To fetch a pail of water Jack fell down And broke his crown, And Jill came tumbling afterAppendFormat: Array
Jack and Jill Went up the hill To fetch a pail of water Jack fell down And broke his crown, And Jill came tumbling after


I found this project interesting enough that I created a new account after deleting mine 1 week ago. I am looking for anything that I might have missed or any errors in my implementation, any ideas on how to maximize the capacity, and any strategies for handling errors.









share|improve this question










share|improve this question




share|improve this question









asked Jun 7 at 23:40









TinMan

55316




55316







  • 1




    Nice to have you back, and nice question!
    – Phrancis
    Jun 8 at 0:18










  • @Phrancis thanks. Good to be back.
    – TinMan
    Jun 11 at 11:23












  • 1




    Nice to have you back, and nice question!
    – Phrancis
    Jun 8 at 0:18










  • @Phrancis thanks. Good to be back.
    – TinMan
    Jun 11 at 11:23







1




1




Nice to have you back, and nice question!
– Phrancis
Jun 8 at 0:18




Nice to have you back, and nice question!
– Phrancis
Jun 8 at 0:18












@Phrancis thanks. Good to be back.
– TinMan
Jun 11 at 11:23




@Phrancis thanks. Good to be back.
– TinMan
Jun 11 at 11:23















active

oldest

votes











Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f196076%2fbringing-the-system-text-stringbuilder-up-to-lightning-speed-in-the-vba%23new-answer', 'question_page');

);

Post as a guest



































active

oldest

votes













active

oldest

votes









active

oldest

votes






active

oldest

votes










 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f196076%2fbringing-the-system-text-stringbuilder-up-to-lightning-speed-in-the-vba%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

Greedy Best First Search implementation in Rust

Function to Return a JSON Like Objects Using VBA Collections and Arrays

C++11 CLH Lock Implementation