v.2.6.
It's well-tested class for concatenating strings like:
Code:
s = s & "one line"
s = s & "two line"
...
s = s & "N line"
but much much more faster than VB runtime do it.
Cases when it is needed sometimes:
Well, e.g., I'm using it to prepare some really huge logs of program in a single 'String' variable (e.g. up to 1 MB.) rather then writing them line by line to file (for performance acceleration purposes, if debug mode of my app. is disabled).
Don't know what else good cases. Better, don't store large data in this way, use suitable tools: arrays, databases ...
Using examples:
Code:
Option Explicit
Private Sub Form_Load()
'init
Dim sb As clsStringBuilder
Set sb = New clsStringBuilder
'concatenation
sb.Append "Very "
sb.Append "many "
sb.Append "pieces "
sb.Append "of data"
'result
Debug.Print "All data: "; sb.ToString
Debug.Print "Len of data: "; sb.length
'removing 5 characters from position # 1
sb.Remove 1, 5
Debug.Print "New data: "; sb.ToString
'inserting string in position # 1
sb.Insert 1, "Not "
Debug.Print "New data: "; sb.ToString
'overwrite part of the text from position # 1 (same like MID(str,x) = "...")
sb.Overwrite 1, "How"
Debug.Print "New data: "; sb.ToString
'getting 2 first characters
Debug.Print "2 left chars: "; sb.ToStringLeft(2)
'getting 2 characters from the end
Debug.Print "2 end chars: "; sb.ToStringRight(2)
'getting 2 characters from the middle, beginning from position 6
Debug.Print "2 middle chars: "; sb.ToStringMid(6, 2)
'getting a pointer to a NUL terminated string to use somehow (e.g. write on disk by ptr, WriteFile, e.t.c.)
'this method is much faster than .ToString()
'warning: you should use this pointer before calling next any method of StringBuilder, that may cause changing its data
Debug.Print "ptr to string: "; sb.ToStringPtr
'replacing the data (same as Clear + Append)
sb.StringData = "Anew"
Debug.Print "New data: "; sb.ToString
'go to the next line (append CrLf)
sb.AppendLine ""
'append second line with CrLf at the end
sb.AppendLine "Second line"
'append third line without CrLf
sb.Append "Third Line"
Debug.Print sb.ToString
'clear all data
sb.Clear
Debug.Print "Len of data (after clear): "; sb.length
'Search samples (by default search is case sensitive)
'Set new data
sb.StringData = "|textile|Some|text|to|search"
Debug.Print "New data: "; sb.ToString
'Simple search ('text' will be found inside 'textile' word)
Debug.Print "Position of 'text': " & sb.Find(1, "text")
'Simple search (start search from position 3)
Debug.Print "Position of 'text': " & sb.Find(3, "text")
Debug.Print "'some' (case sensitive): " & sb.Find(1, "some")
Debug.Print "'some' (case insensitive): " & sb.Find(1, "some", , vbTextCompare)
'Search by delimiter
Debug.Print "searching for |text|: " & sb.Find(1, "text", "|")
'Search for empty string, saved with delimiter |
Debug.Print "empty string (delim = '|'): " & sb.Find(1, "", "|")
'Undo operations
sb.StringData = "Some data "
Debug.Print "Orig. string: " & sb.ToString
sb.Append "remove"
Debug.Print "After Append: " & sb.ToString
'or you can use .UndoAppend
sb.Undo
Debug.Print "After Undo: " & sb.ToString
sb.Insert 6, "bad "
Debug.Print "After Insert: " & sb.ToString
'or you can use .UndoInsert
sb.Undo
Debug.Print "After Undo: " & sb.ToString
sb.Overwrite 1, "Here"
Debug.Print "After Overwrite: " & sb.ToString
'or you can use .UndoOverwrite
sb.Undo
Debug.Print "After Undo: " & sb.ToString
sb.Remove 2, 5
Debug.Print "After Remove: " & sb.ToString
'or you can use .UndoRemove
sb.Undo
Debug.Print "After Undo: " & sb.ToString
'when you finished work with the class
Set sb = Nothing
Unload Me
End Sub
Result:
All data: Very many pieces of data
Len of data: 24
New data: many pieces of data
New data: Not many pieces of data
New data: How many pieces of data
2 left chars: Ho
2 end chars: ta
2 middle chars: an
ptr to string: 228655136
New data: Anew
Anew
Second line
Third Line
Len of data (after clear): 0
New data: |textile|Some|text|to|search
Position of 'text': 2
Position of 'text': 15
'some' (case sensitive): 0
'some' (case insensitive): 10
searching for |text|: 15
empty string (delim = '|'): 1
Orig. string: Some data
After Append: Some data remove
After Undo: Some data
After Insert: Some bad data
After Undo: Some data
After Overwrite: Here data
After Undo: Some data
After Remove: Sata
After Undo: Some data
Copyrights: VolteFace (Fork by Dragokas)
Last edited by Dragokas; Oct 2nd, 2023 at 11:56 AM.
Reason: new update
Re: [VB6] StringBuilder - Fast string concatenation
Very useful source code, thanks for sharing. If the Find function could be added to this class, that would be great.
Code:
Public Function Find(ByRef str As String, Optional ByVal delimiter As String) As Long
End Function
Edit:
I missed three parameters in the Find function, now correct as follows
Code:
Public Function Find(ByRef str As String, ByVal destination As String, Optional ByVal start As long = 1, Optional ByVal compare As VbCompareMethod = vbBinaryCompare, Optional ByVal delimiter As String = vbNullString) As Long
End Function
Last edited by dreammanor; Nov 12th, 2017 at 11:30 PM.
Re: [VB6] StringBuilder - Fast string concatenation
Hi, dreammanor!
Thanks for the suggestion. I'll see what I can do.
However, what is mean under "delimiter" for the search text ?
I must warn that for unknown reason this code sometimes crash the program on class destruction in this line:
Code:
HeapFree GetProcessHeap, 0&, m_pMemoryPtr
Further I added some safely checkings:
Code:
Private Sub Class_Terminate()
Dim hProcHeap As Long
' If we have memory allocated, free it
If m_pMemoryPtr <> 0 Then
hProcHeap = GetProcessHeap()
If hProcHeap <> 0 Then
If HeapValidate(hProcHeap, 0&, m_pMemoryPtr) Then
'//TODO
'disabled until I figure out why it sometimes crash the program in /silentautolog mode
'HeapFree hProcHeap, 0&, m_pMemoryPtr
End If
End If
End If
End Sub
But, it still crash on the same line.
I unable to resolve this.
I still not sure, maybe it is caused by error in another part of my project that can overwrite by mistake a memory region used by this class.
Because, I don't want give up this class, finally, I commented freeing the resource. It's of course lead to memory leak, but do not crash anymore.
If anyone has ideas, I glad to see.
Re: [VB6] StringBuilder - Fast string concatenation
Originally Posted by Dragokas
However, what is mean under "delimiter" for the search text ?
Sorry, I missed three parameters in the Find function. Now correct as follows:
Code:
Public Function Find(ByRef str As String, ByVal destination As String, Optional ByVal start As long = 1, Optional ByVal compare As VbCompareMethod = vbBinaryCompare, Optional ByVal delimiter As String = vbNullString) As Long
End Function
The meaning of delimiter is similar to the join and split functions:
Join (Sourcearray, Delimiter)
Split (Expression, Delimiter)
Many people often need to use the delimiter when working with strings, for example:
sItemList = sItem1 & vbTab & sItem2 & vbTab & sItem3 & vbTab ...
or:
sItemList = sItem1 & "|" & sItem2 & "|" & sItem3 & "|" ...
or:
sItemList = sItem1 & ";" & sItem2 & ";" & sItem3 & ";" ...
The logic of the Find function is as follows: (Edit: the logic of Find function is incorrect)
Code:
Public Function Find(ByRef str As String, ByVal destination As String, _
Optional ByVal start As Long = 1, _
Optional ByVal compare As VbCompareMethod = vbBinaryCompare, _
Optional ByVal delimiter As String = vbNullString) As Long
If start <= 0 Then start = 1
If destination = vbNullString Then
Exit Function
ElseIf delimiter = vbNullString Then
Find = InStr(start, str, destination, compare)
Exit Function
End If
'--------------------------------------------------------------------------
'--- Processing the delimiter:
'--------------------------------------------------------------------------
Dim length As Long, position As Long
length = Len(destination)
If Left(str, length + 1) = destination & delimiter Then
If start = 1 Then
position = 1
End If
ElseIf Right(str, length + 1) = delimiter & destination Then
position = Len(str) - length + 1
If position < start Then
position = 0
End If
Else
position = InStr(start, str, delimiter & destination & delimiter, compare)
End If
Find = position
End Function
Last edited by dreammanor; Nov 13th, 2017 at 10:13 AM.
Re: [VB6] StringBuilder - Fast string concatenation
Very sorry, the above logic of Find function is incorrect and now correct as follows:
Code:
Public Function Find(ByRef str As String, ByVal destination As String, _
Optional ByVal start As Long = 1, _
Optional ByVal compare As VbCompareMethod = vbBinaryCompare, _
Optional ByVal delimiter As String = vbNullString) As Long
If start <= 0 Then start = 1
If destination = vbNullString Then
Exit Function
ElseIf delimiter = vbNullString Then
Find = InStr(start, str, destination, compare)
Exit Function
End If
'--------------------------------------------------------------------------
'--- Processing the delimiter:
'--------------------------------------------------------------------------
Dim length As Long, position As Long
If start = 1 And Left(str, length + 1) = destination & delimiter Then
position = 1
Else
position = InStr(start, str, delimiter & destination & delimiter, compare)
If position = 0 Then
If Right(str, length + 1) = delimiter & destination Then
position = Len(str) - length + 1
If position < start Then
position = 0
End If
End If
End If
End If
Find = position
End Function
Or
Code:
Public Function Find(ByRef str As String, ByVal destination As String, _
Optional ByVal start As Long = 1, _
Optional ByVal compare As VbCompareMethod = vbBinaryCompare, _
Optional ByVal delimiter As String = vbNullString) As Long
If destination <> vbNullString Then
If start <= 0 Then start = 1
If delimiter = vbNullString Then
Find = InStr(start, str, destination, compare)
Else
Find = InStr(start, delimiter & str & delimiter, delimiter & destination & delimiter, compare)
End If
End If
End Function
Last edited by dreammanor; Nov 13th, 2017 at 10:14 AM.
Re: [VB6] StringBuilder - Fast string concatenation
Understood.
I am using something similar in my programs:
Code:
Public Function inArraySerialized( _
Stri As String, _
SerializedArray As String, _
Delimiter As String, _
Optional lB As Long = -2147483647, _
Optional uB As Long = 2147483647, _
Optional CompareMethod As VbCompareMethod) As Boolean
On Error GoTo ErrorHandler:
Dim MyArray() As String
If 0 = Len(SerializedArray) Then
If 0 = Len(Stri) Then inArraySerialized = True
Exit Function
End If
MyArray = Split(SerializedArray, Delimiter)
If lB = -2147483647 Or lB < LBound(MyArray) Then lB = LBound(MyArray) 'some trick
If uB = 2147483647 Or uB > UBound(MyArray) Then uB = UBound(MyArray) 'Thanks to Казанский :)
Dim i As Long
For i = lB To uB
If StrComp(Stri, MyArray(i), CompareMethod) = 0 Then inArraySerialized = True: Exit For
Next
Exit Function
ErrorHandler:
ErrorMsg Err, "inArraySerialized", "SerializedString: ", SerializedArray, "delim: ", Delimiter
If inIDE Then Stop: Resume Next
End Function
But it is not suitable for in-memory search, like C++ strstr(), and not intended for searching within a large amount of data like StringBuilder holds.
So, I need to build own replacement for that, or to get a ready solution if someone already have a code for the fast text search by pointers.
Re: [VB6] StringBuilder - Fast string concatenation
Hi Dragokas. Your crash in HeapFree is caused by a bad API declaration. Change the declaration to "ByVal lpMem as Long", or explicitly pass m_pMemoryPtr as ByVal. (Honestly, the class has quite a few issues like this - to avoid other problems, you could change many of its API declarations to ByVal Long instead of ByRef Any, then remove all the hard-coded ByVal statements in function calls.)
Re: [VB6] StringBuilder - Fast string concatenation
For the search purposes: because StrStr() returns a pointer, is there a way to guarantee that HeapReAlloc() function will return continous block of memory?
Use HEAP_REALLOC_IN_PLACE_ONLY flag? And if it fails -> make call to HeapAlloc, sacrificing the speed. Am I right? Or there is a better way?
Re: [VB6] StringBuilder - Fast string concatenation
No problem Dragokas, I'm glad it was quick to fix!
HeapAlloc always allocates contiguous blocks of memory, so that shouldn't be a concern. HEAP_REALLOC_IN_PLACE_ONLY just means that re-allocations are not allowed to move the memory somewhere new (which is usually required, since free memory may not exist immediately following the current allocation). You definitely don't want that flag for a string builder.
To be honest, there's not really a benefit to using HeapAlloc directly for memory allocation in VB6. Unless an API specifically requires heap handles, you can just use standard VB arrays for the same result. This would greatly simplify the class if you don't mind the work. (Of course, at that point, you have basically rewritten the entire class as your own!)
Original `clsStringBuilder` has another nice feature though -- it prevents COM string's heap exhaustion by keeping the chunk of memory in a separate heap.
cheers,
</wqw>
Last edited by wqweto; Nov 14th, 2017 at 03:48 PM.
Reason: buffer -> builder
Re: [VB6] StringBuilder - Fast string concatenation
@wqewto I always thought that this was how the .NET stringbuilder works (1 big chain), but my notes say otherwise.
i could have sworn .NET just collects / chains each string, and then concat's them all at once. *shrugs*
Re: [VB6] StringBuilder - Fast string concatenation
Originally Posted by DEXWERX
@wqewto I always thought that this was how the .NET stringbuilder works (1 big chain), but my notes say otherwise.
i could have sworn .NET just collects / chains each string, and then concat's them all at once. *shrugs*
Good memory, Dex. That's still how the version on GitHub works, anyway:
// A StringBuilder is internally represented as a linked list of blocks each of which holds
// a chunk of the string. It turns out string as a whole can also be represented as just a chunk,
// so that is what we do.
Sample of their ToString() implementation:
Code:
do
{
if (chunk.m_ChunkLength > 0)
{
// Copy these into local variables so that they are stable even in the presence of race conditions
char[] sourceArray = chunk.m_ChunkChars;
int chunkOffset = chunk.m_ChunkOffset;
int chunkLength = chunk.m_ChunkLength;
// Check that we will not overrun our boundaries.
if ((uint)(chunkLength + chunkOffset) <= (uint)result.Length && (uint)chunkLength <= (uint)sourceArray.Length)
{
fixed (char* sourcePtr = &sourceArray[0])
string.wstrcpy(destinationPtr + chunkOffset, sourcePtr, chunkLength);
}
else
{
throw new ArgumentOutOfRangeException(nameof(chunkLength), SR.ArgumentOutOfRange_Index);
}
}
chunk = chunk.m_ChunkPrevious;
}
while (chunk != null);
Re: [VB6] StringBuilder - Fast string concatenation
If the C# StringBuilder is much faster than VB string processing, it may be a good choice to rewrite a VB StringBuilder based on the C# StringBuilder. I know someone has changed the partial dotNet library to the VB framework, unfortunately that framework doesn't include the StringBuilder class.
Re: [VB6] StringBuilder - Fast string concatenation
Updated to v2.0
WARNING: meaning of prototypes is changed to meet VB6 friendly convention: "Index" is replaced by "pos" (position), where "pos" is start from 1 (for "Index" it was 0).
Changelog:
' Full revision
' .Find method has been added (by dreammanor's request).
' .ToStringPtr method has been added.
' .ToStringLeft method has been added.
' .ToStringMid method has been added.
' .ToStringRight method has been added.
' .ToString method replaced by 2x faster version (PutMem4 + SysAllocString) (thanks to Bonnie West).
' .Clear method is improved in speed (removed RtlZeroMemory).
' Finally, fixed the crash with HeapFree and respectively a memory leak (wrong declaration) (thanks to Tanner_H).
' A bit faster memory allocation (removed HEAP_ZERO_MEMORY).
' A bit faster working with heap (creating new heap instead of using default process heap + HEAP_NO_SERIALIZE).
' Fixed some formulas on reallocation calculations.
' Added some safe checkings.
Examples of using are updated.
Descriptions of new methods:
' #############################
'
' .Find
'
' Returns a position to the first occurrence of search string, or 0 if does not occur.
'
' #############################
Find(StartPos As Long, StrSearch As String, Optional Delimiter As String, Optional CompareMethod As VbCompareMethod) As Long
If delimiter is specified, search will be performed inside the delimiters, like regexp:
'stady 1: search ^string$
'stady 2: search ^string|$
'stady 3: search .*|string|.*
'stady 4: search .*|string$
' #############################
'
' .ToStringPtr
'
' Get the pointer to string (much faster than using .ToString).
'
' Note 1: Use .Length method to know the size of this string.
' Note 2: This pointer should be used before next calling to .Append / .Insert / .Overwrite / .StringData methods.
' Note 3: Application should not manually free this pointer.
' Note 4: Class guarantee the returned pointer contains the string with two NUL terminators.
'
' #############################
.ToStringLeft / .ToStringMid / .ToStringRight
Get the copy of internally stored string (only the some part)
Re: [VB6] StringBuilder - Fast string concatenation
Nice work! I've tested it a bit and it is about 2x faster than my existing StringBuilder.
One thing I have need for is the ability to undo the most recent Append call. It's a convenience for simpler code when building strings where I don't necessarily know how many appends I'll need, or I don't want the extra code to check if I'm on the last item.
For example, when building an SQL string I might need a list of field names all separated by commas except for the last field. In this case, the following:
Code:
For ii = 1 to Fields.Count
sb.Append Fields(ii).Name
sb.Append ", "
Next ii
sb.UndoLastAppend
Looks nicer to me then the following:
Code:
For ii = 1 to Fields.Count
sb.Append Fields(ii).Name
if ii < Fields.Count Then sb.Append ", "
Next ii
I admit that's probably a matter of taste, but if you have any interest in including such a feature in your string builder, I've added it in the following code:
Code:
Option Explicit
' ****************************************************
'
' cStringBuilder
' By VolteFace
'
' Date Created: 3/21/2004
'
' This class was created to provide more or less the
' same functionality as the System.Text.StringBuider
' class available in the .NET framework. It makes use
' of direct memory allocation and manipulation, so is
' much faster than traditional VB string concatenation.
'
' **************************************************
'
' Fork by Alex Dragokas
'
' v2.0 (15.11.2017)
'
' Full revision
' .Find method has been added (by dreammanor's request).
' .ToStringPtr method has been added.
' .ToStringLeft method has been added.
' .ToStringMid method has been added.
' .ToStringRight method has been added.
' .ToString method replaced by 2x faster version (PutMem4 + SysAllocString) (thanks to Bonnie West).
' .Clear method is improved in speed (removed RtlZeroMemory).
' Finally, fixed the crash with HeapFree and respectively a memory leak (wrong declaration) (thanks to Tanner_H).
' A bit faster memory allocation (removed HEAP_ZERO_MEMORY).
' A bit faster working with heap (creating new heap instead of using default process heap + HEAP_NO_SERIALIZE).
' Fixed some formulas on reallocation calculations.
' Added some safe checkings.
'
' v1.3 (13.05.2017)
'
' Added heap validation before freeing to prevent application crash, just in case it is corrupted somehow.
'
' v1.2 (12.07.2015)
'
' Fixed bug: .ToString method returns stripped string, if it contains NUL characters
'
' v1.1 (10.07.2015)
'
' Some methods renamed
' Changed pointer type for all methods - "byval" to "byref"
' Fixed bug: wrong buffer size defined during reallocation in .Append method which cause application crash
'
' ****************************************************
' ############################# TYPE DECLARES
Private Type SYSTEM_INFO
dwOemID As Long
dwPageSize As Long
lpMinimumApplicationAddress As Long
lpMaximumApplicationAddress As Long
dwActiveProcessorMask As Long
dwNumberOrfProcessors As Long
dwProcessorType As Long
dwAllocationGranularity As Long
wProcessorLevel As Integer
wProcessorRevision As Integer
End Type
' ############################# API DECLARES
Private Declare Function GetVersionEx Lib "kernel32.dll" Alias "GetVersionExW" (lpVersionInformation As Any) As Long
Private Declare Sub GetSystemInfo Lib "kernel32.dll" (lpSystemInfo As SYSTEM_INFO)
Private Declare Function HeapCreate Lib "kernel32.dll" (ByVal flOptions As Long, ByVal dwInitialSize As Long, ByVal dwMaximumSize As Long) As Long
Private Declare Function HeapAlloc Lib "kernel32.dll" (ByVal hHeap As Long, ByVal dwFlags As Long, ByVal dwBytes As Long) As Long
Private Declare Function HeapReAlloc Lib "kernel32.dll" (ByVal hHeap As Long, ByVal dwFlags As Long, ByVal lpMem As Long, ByVal dwBytes As Long) As Long
Private Declare Function HeapFree Lib "kernel32.dll" (ByVal hHeap As Long, ByVal dwFlags As Long, ByVal lpMem As Long) As Long
Private Declare Function HeapDestroy Lib "kernel32.dll" (ByVal hHeap As Long) As Long
Private Declare Function HeapValidate Lib "kernel32.dll" (ByVal hHeap As Long, ByVal dwFlags As Long, ByVal lpMem As Long) As Long
'Private Declare Function GetProcessHeap Lib "kernel32.dll" () As Long
'Private Declare Sub RtlZeroMemory Lib "kernel32.dll" (Destination As Any, ByVal length As Long)
Private Declare Function memcpy Lib "kernel32.dll" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal length As Long) As Long
Private Declare Function GetMem2 Lib "msvbvm60.dll" (Src As Any, Dst As Any) As Long
Private Declare Function PutMem4 Lib "msvbvm60.dll" (ByVal Addr As Long, ByVal NewVal As Long) As Long
Private Declare Function SysAllocString Lib "oleaut32.dll" (ByVal pOlechar As Long) As Long
Private Declare Function StrStrW Lib "Shlwapi.dll" (ByVal pszFirst As Long, ByVal pszSrch As Long) As Long
Private Declare Function StrStrIW Lib "Shlwapi.dll" (ByVal pszFirst As Long, ByVal pszSrch As Long) As Long
Private Declare Function CompareStringOrdinal Lib "kernel32.dll" (ByVal lpString1 As Long, ByVal cchCount1 As Long, ByVal lpString2 As Long, ByVal cchCount2 As Long, ByVal bIgnoreCase As Long) As Long
Private Declare Function lstrlen Lib "kernel32.dll" Alias "lstrlenW" (ByVal lpString As Long) As Long
Private Declare Function lstrcmpW Lib "kernel32.dll" (ByVal lpString1 As Long, ByVal lpString2 As Long) As Long
Private Declare Function lstrcmpiW Lib "kernel32.dll" (ByVal lpString1 As Long, ByVal lpString2 As Long) As Long
' ############################# CONSTANTS
Private Const CHUNK_SIZE As Long = 1048576 'each (re)allocation == 1 MB by default.
Private Const HEAP_NO_SERIALIZE As Long = 1&
Private Const HEAP_ZERO_MEMORY As Long = &H8&
Private Const CSTR_EQUAL As Long = 2&
' ############################# MEMBER VARIABLES
Private m_pMemoryPtr As Long
Private m_lAllocSize As Long
Private m_lChunkLength As Long
Private m_lLength As Long
Private m_lLengthUndoAppend As Long
Private m_hHeap As Long
Private m_Chunk_Size_Aligned As Long
Private m_bIsVistaAndNewer As Boolean
' #############################
'
' Class_Initialize
'
' Initializes the class, creates new heap
' and allocates the initial string buffer.
'
' #############################
Private Sub Class_Initialize()
Dim inf(68) As Long
Dim MajorMinor As Single
Dim si As SYSTEM_INFO
GetSystemInfo si
If si.dwPageSize = 0 Then
Debug.Print "Error in retrieving page size. GetSystemInfo failed with 0x" & Hex(Err.LastDllError)
Err.Raise 5, , "Cannot obtain page size"
Exit Sub
End If
inf(0) = 276
GetVersionEx inf(0)
MajorMinor = inf(1) + inf(2) / 10
m_bIsVistaAndNewer = (MajorMinor >= 6)
'align chunk to the upper bound of the page size
m_Chunk_Size_Aligned = AlignUp(CHUNK_SIZE, si.dwPageSize)
m_hHeap = HeapCreate(HEAP_NO_SERIALIZE, m_Chunk_Size_Aligned, 0&)
If m_hHeap = 0 Then
Debug.Print "HeapCreate failed with 0x" & Hex(Err.LastDllError)
Err.Raise 5, , "Cannot create new heap"
Exit Sub
End If
' Allocate default chunk size
Allocate m_Chunk_Size_Aligned
End Sub
'Align number to the upper bound
Private Function AlignUp(Num As Long, Align As Long) As Long
AlignUp = (Num \ Align) * Align
If AlignUp < Num Then
AlignUp = AlignUp + Align
End If
End Function
' #############################
'
' Allocate
'
' Allocates a specified amount of memory
' for the string buffer.
'
' #############################
Private Sub Allocate(Size As Long)
Dim tmp As Long
Dim newSize As Long
' If no memory is allocated yet, allocate some from the heap - otherwise
' reallocate (resize) the block that has already been allocated
If m_pMemoryPtr = 0 Then
m_pMemoryPtr = HeapAlloc(m_hHeap, HEAP_NO_SERIALIZE, Size)
Else
m_pMemoryPtr = HeapReAlloc(m_hHeap, HEAP_NO_SERIALIZE, m_pMemoryPtr, Size)
End If
m_lAllocSize = Size
End Sub
' #############################
'
' .ToString
'
' Get the copy of internally stored string
'
' #############################
Public Property Get ToString() As String
If m_lLength = 0 Then Exit Property
PutMem4 VarPtr(ToString), SysAllocString(m_pMemoryPtr)
End Property
' #############################
'
' .StringData
'
' Set the new string by clearing all stored data
'
' #############################
Public Property Let StringData(str As String)
Clear
Append str
End Property
' #############################
'
' .ToStringPtr
'
' Get the pointer to string (much faster than using .ToString).
'
' Note 1: Use .Length method to know the size of this string.
' Note 2: This pointer should be used before next calling to .Append / .Insert / .Overwrite / .StringData methods.
' Note 3: Application should not manually free this pointer.
' Note 4: Class guarantee the returned pointer contains the string with two NUL terminators.
'
' #############################
Public Property Get ToStringPtr() As Long
If m_lLength = 0 Then Exit Property
ToStringPtr = m_pMemoryPtr
End Property
' #############################
'
' .ToStringLeft
'
' Get the copy of internally stored string (only the part from beginning of the string)
'
' #############################
Public Property Get ToStringLeft(ByVal length As Long) As String
If m_lLength = 0 Then Exit Property
If length > (m_lLength \ 2) Then
length = m_lLength \ 2
End If
ToStringLeft = String$(length, 0&)
memcpy ByVal StrPtr(ToStringLeft), ByVal m_pMemoryPtr, length * 2
End Property
' #############################
'
' .ToStringMid
'
' Get the copy of internally stored string (only the part from middle of the string)
'
' #############################
Public Property Get ToStringMid(StartPos As Long, ByVal length As Long) As String
Dim Index As Long
Index = StartPos - 1
If m_lLength = 0 Then Exit Property
If StartPos > (m_lLength \ 2) Then Exit Property
If Index + length > (m_lLength \ 2) Then
length = (m_lLength \ 2) - Index
End If
ToStringMid = String$(length, 0&)
memcpy ByVal StrPtr(ToStringMid), ByVal m_pMemoryPtr + Index * 2, length * 2
End Property
' #############################
'
' .ToStringRight
'
' Get the copy of internally stored string (only the part from end of the string)
'
' #############################
Public Property Get ToStringRight(ByVal length As Long) As String
If m_lLength = 0 Then Exit Property
If length > (m_lLength \ 2) Then
length = m_lLength \ 2
End If
PutMem4 VarPtr(ToStringRight), SysAllocString(m_pMemoryPtr + m_lLength - length * 2)
End Property
' #############################
'
' .Clear
'
' Removes all string data from the
' initial string buffer, and resizes
' the buffer down to the initial 1MB.
'
' #############################
Public Sub Clear()
' Clean out the string buffer
m_lLengthUndoAppendAppend = 0
If m_lLength <> 0 Then
m_lLength = 0
Allocate m_Chunk_Size_Aligned
End If
End Sub
' #############################
'
' .Append
'
' Adds a specified string on to the
' end of the string stored in the
' buffer.
'
' #############################
Public Sub Append(str As String)
Dim pTo As Long
' If we are going to need more memory (if the final size of the append is going to be
' greater than the currently allocated size), we need to find out how much more we
' need (in increments of CHUNK_SIZE, default 1MB) and allocate it
' +2 to hold NUL terminator
m_lLengthUndoAppend = m_lLength
If m_lLength + LenB(str) + 2 > m_lAllocSize Then
Allocate AlignUp(m_lLength + LenB(str) + 2, m_Chunk_Size_Aligned)
End If
' Put the specified string at the end of the string buffer
pTo = m_pMemoryPtr + m_lLength
memcpy ByVal pTo, ByVal StrPtr(str), LenB(str) + 2 '+2 for NUL terminator
m_lLength = m_lLength + LenB(str)
End Sub
' #############################
'
' .Insert
'
' Inserts a specified string into the
' stored string at a specific index.
'
' #############################
Public Sub Insert(pos As Long, str As String)
Dim pFrom As Long
Dim pTo As Long
Dim Index As Long
Index = pos - 1
If Len(str) = 0 Then Exit Sub
If (Index < 0) Then Exit Sub
If (Index > (m_lLength \ 2)) Then Exit Sub
' If we are going to need more memory (if the final size of the insert is going to be
' greater than the currently allocated size), we need to find out how much more we
' need (in increments of CHUNK_SIZE, default 1MB) and allocate it
' +2 to consider 2 NUL characters as terminator
If m_lLength + LenB(str) + 2 > m_lAllocSize Then
Allocate AlignUp(m_lLength + LenB(str) + 2, m_Chunk_Size_Aligned)
End If
' Copy the entire stored string, from 'index' to the end and move it over to the
' right to accomodate for the new string to be inserted, and then put the specified
' string in the correct position
' str = 'NEW ' (Len = 4)
' v
' INITIAL STRING FOR TEST
' | | | |
' | | | m_lLength
' | | |
' | | pTo
' | |
' | pFrom (Index)
' |
' m_pMemoryPtr
pFrom = m_pMemoryPtr + (Index * 2&)
pTo = pFrom + LenB(str)
memcpy ByVal pTo, ByVal pFrom, m_lLength - (Index * 2&) + 2 '+2 - include NUL terminator
memcpy ByVal pFrom, ByVal StrPtr(str), LenB(str)
m_lLength = m_lLength + LenB(str)
m_lLengthUndoAppend = m_lLength ' Obliterate Undo Append position tracker
End Sub
' #############################
'
' .Overwrite
'
' Inserts a string into the middle
' of the stored string, wiping out
' the characters at that position.
'
' #############################
Public Sub Overwrite(pos As Long, str As String)
Dim pFrom As Long
Dim pTo As Long
Dim Index As Long
Dim bExpanded As Boolean
Index = pos - 1
If Len(str) = 0 Then Exit Sub
If (Index < 0) Then Exit Sub
If (Index > (m_lLength \ 2)) Then Exit Sub
' If we are going to need more memory (if the inserted string goes over
' the length of the current string, and ends up being longer than the allocated
' memory block, we need to calculate how much we need (in increments of CHUNK_SIZE,
' default 1MB) and allocate it
' +2 to consider 2 NUL characters as terminator
If Index * 2 + LenB(str) + 2 > m_lAllocSize Then
Allocate AlignUp(m_lLength + LenB(str) + 2, m_Chunk_Size_Aligned)
End If
' str = 'OVER'
' v
' STRING HERE FOR TEST
' | | |
' | | m_lLength
' | |
' | pFrom (Index)
' |
' m_pMemoryPtr
' Copy the specified string into the stored string
pFrom = m_pMemoryPtr + (Index * 2&)
memcpy ByVal pFrom, ByVal StrPtr(str), LenB(str)
' If the string got longer (the inserted string hung over the end of the
' old string) we need to calculate how much bigger it got
' and append NUL terminator
If (Index * 2&) + LenB(str) > m_lLength Then
m_lLength = Index * 2& + LenB(str)
GetMem2 ByVal StrPtr(vbNullChar), ByVal (m_pMemoryPtr + m_lLength)
End If
m_lLengthUndoAppend = m_lLength ' Obliterate Undo Append position tracker
End Sub
' #############################
'
' .Remove
'
' Removes text from the middle of
' the stored string.
'
' #############################
Public Sub Remove(pos As Long, ByVal length As Long)
Dim pFrom As Long
Dim pTo As Long
Dim Index As Long
Index = pos - 1
If (length <= 0) Then Exit Sub
If (Index < 0) Then Exit Sub
If (Index > (m_lLength \ 2)) Then Exit Sub
' Copy the entire stored string, from 'index' to the end and move it over to the
' left to overright the desired chracters, and then excess characters at the end
' of the string
If (length + Index > (m_lLength \ 2)) Then
length = (m_lLength \ 2) - Index
End If
' GOOD GARBAGE STRING
' | | | |
' | | | |
' | | | m_lLength
' | | |
' | | pFrom
' | |
' | pTo (Index)
' |
' m_pMemoryPtr
pTo = m_pMemoryPtr + (Index * 2&)
pFrom = m_pMemoryPtr + ((Index + length) * 2&)
memcpy ByVal pTo, ByVal pFrom, m_lLength - ((Index + length) * 2&)
m_lLength = m_lLength - (length * 2&)
'Append NUL terminator
GetMem2 ByVal StrPtr(vbNullChar), ByVal (m_pMemoryPtr + m_lLength)
m_lLengthUndoAppend = m_lLength ' Obliterate Undo Append position tracker
End Sub
' #############################
'
' .Length
'
' Returns the length of the string
'
' #############################
Public Property Get length() As Long
' Since the string is stored as unicode, every character is 2 bytes
length = m_lLength \ 2
End Property
' #############################
'
' .UndoAppend
'
' Undo the last append.
' Must be called before any intervening Insert, Overwrite, Remove calls
'
' #############################
Public Sub UndoAppend()
If m_lLength <> m_lLengthUndoAppend Then
m_lLength = m_lLengthUndoAppend
GetMem2 ByVal StrPtr(vbNullChar), ByVal (m_pMemoryPtr + m_lLength)
Else
' Nothing to undo!
Debug.Assert False
End If
End Sub
' #############################
'
' .Find
'
' Returns a position to the first occurrence of search string, or 0 if does not occur.
'
' #############################
Public Property Get Find(StartPos As Long, StrSearch As String, Optional Delimiter As String, Optional CompareMethod As VbCompareMethod) As Long
' StartPos - what position the searching must be started from
' StrSearch - what string to search for
' Delimiter - if strings in StringBuilder are delimited by some character(s),
' e.g. if .Find 'rose' in '|melrose|rose|' should return pos. == 10, not 5
' CompareMethod - case sensitive mode switch
Dim Index As Long
Index = StartPos - 1
If Index > m_lLength \ 2 Then Exit Property
'if Search string is empty
If Len(StrSearch) = 0 Then
If Len(Delimiter) = 0 Then '1. Search == "" + no Delim -> return pos == 1
Find = 1
Else '2. Search == "" + some Delim -> search for empty value, surrounded by Delim
'stady 1: if empty data -> false
'stady 2: if ^|.*
'stady 3: if .*||.*
'stady 4: if .*|$
'for 2,3,4: returns a position next to the delimiter, even if it is exceed the size of StringBuilder's data
'1
If m_lLength = 0 Then 'no records yet -> no matches
Find = 0
Else
'2
If StartPos = 1 Then
If m_lLength \ 2 >= Len(Delimiter) Then
If StrComp(ToStringLeft(Len(Delimiter)), Delimiter, CompareMethod) = 0 Then
Find = 1
Exit Property
End If
End If
End If
'3
Find = InstrPtr(StartPos, StrPtr(Delimiter & Delimiter), m_pMemoryPtr, CompareMethod)
If Find <> 0 Then
Find = Find + Len(Delimiter)
Exit Property
End If
'4
If m_lLength \ 2 >= Index + Len(Delimiter) Then
If StrComp(ToStringRight(Len(Delimiter)), Delimiter, CompareMethod) = 0 Then
Find = m_lLength \ 2 + 1 'returns a position bigger than the size of stringbuilder's data
End If
End If
End If
End If
Exit Property
End If
If m_lLength = 0 Then Exit Property
If Len(Delimiter) = 0 Then
Find = InstrPtr(StartPos, StrPtr(StrSearch), m_pMemoryPtr, CompareMethod)
Else
'Delimiter is ON
'stady 1: search ^string$
'stady 2: search ^string|$
'stady 3: search .*|string|.*
'stady 4: search .*|string$
'1
If (StartPos = 1) And (m_lLength \ 2 = Len(StrSearch)) Then
If StrCompPtrEx(m_pMemoryPtr, StrPtr(StrSearch), Len(StrSearch), CompareMethod) = 0 Then
Find = 1
Exit Property
End If
End If
'2
If (StartPos = 1) And ((m_lLength \ 2) >= (Len(StrSearch) + Len(Delimiter))) Then
If StrCompPtrEx(m_pMemoryPtr, StrPtr(StrSearch & Delimiter), Len(StrSearch) + Len(Delimiter), CompareMethod) = 0 Then
Find = 1
Exit Property
End If
End If
'3
Find = InstrPtr(StartPos, StrPtr(Delimiter & StrSearch & Delimiter), m_pMemoryPtr, CompareMethod)
If Find <> 0 Then
'consider len of delimiter
Find = Find + Len(Delimiter)
Exit Property
End If
'4
If m_lLength \ 2 >= Index + Len(Delimiter) + Len(StrSearch) Then
If StrCompPtrEx(m_pMemoryPtr + m_lLength - LenB(StrSearch) - LenB(Delimiter), _
StrPtr(Delimiter & StrSearch), Len(Delimiter) + Len(StrSearch), CompareMethod) = 0 Then
Find = m_lLength \ 2 - Len(StrSearch) + 1
Exit Property
End If
End If
End If
End Property
' #############################
'
' InstrPtr
' (analogue of Instr(), but takes pointers instead of strings.
'
' Returns a position to the first occurrence of search string, or 0 if does not occur.
'
' #############################
Private Function InstrPtr(StartPos As Long, StrSearchFor As Long, StrSearchIn As Long, Optional CompareMethod As VbCompareMethod) As Long
'Attention: no safe checkings here. Use with caution.
If CompareMethod = vbTextCompare Then
InstrPtr = StrStrIW(StrSearchIn + (StartPos - 1) * 2, StrSearchFor)
Else
InstrPtr = StrStrW(StrSearchIn + (StartPos - 1) * 2, StrSearchFor)
End If
If InstrPtr <> 0 Then
InstrPtr = (InstrPtr - StrSearchIn) \ 2 + 1
End If
End Function
' #############################
'
' StrCompPtrEx
' (something like StrComp(), but takes pointers instead of strings + the number of characters. So, strings can be not NUL terminated)
'
' Returns FALSE, if strings are match, or TRUE if not.
'
' #############################
Private Function StrCompPtrEx( _
StrString1 As Long, StrString2 As Long, _
cchCount As Long, Optional CompareMethod As VbCompareMethod) As Boolean
If m_bIsVistaAndNewer Then
StrCompPtrEx = (CSTR_EQUAL <> CompareStringOrdinal(StrString1, cchCount, StrString2, cchCount, CompareMethod))
Else
Dim pStr1 As Long
Dim pStr2 As Long
Dim StrBuf1 As String
Dim StrBuf2 As String
'preparing NUL terminated strings
If lstrlen(StrString1) = cchCount Then
pStr1 = StrString1
Else
StrBuf1 = String$(cchCount, 0&)
memcpy ByVal StrPtr(StrBuf1), ByVal StrString1, cchCount * 2
pStr1 = StrPtr(StrBuf1)
End If
If lstrlen(StrString2) = cchCount Then
pStr2 = StrString2
Else
StrBuf2 = String$(cchCount, 0&)
memcpy ByVal StrPtr(StrBuf2), ByVal StrString2, cchCount * 2
pStr2 = StrPtr(StrBuf2)
End If
If CompareMethod = vbTextCompare Then
StrCompPtrEx = lstrcmpiW(pStr1, pStr2)
Else
StrCompPtrEx = lstrcmpW(pStr1, pStr2)
End If
End If
End Function
' #############################
'
' Class_Terminate
'
' Deallocates all allocated memory.
'
' #############################
Private Sub Class_Terminate()
If m_hHeap <> 0 Then
HeapDestroy m_hHeap
End If
End Sub
Re: [VB6] StringBuilder - Fast string concatenation
Updated to v2.1
' .Undo method has been added (allows to revert to initial state from the last write operation, excepting .StringData; 1 step only)
' .UndoAppend method has been added (do .Undo if only last operation was .Append) (by jpbro's request)
' .UndoInsert method has been added (do .Undo if only last operation was .Insert)
' .UndoOverwrite method has been added (do .Undo if only last operation was .Overwrite)
' .UndoRemove method has been added (do .Undo if only last operation was .Remove)
jpbro, no problem. I also sometimes hate to see how the code looks when removing manually such last delimiter ", ".
I just expanded Undo a bit to cover Insert/Overwrite/Remove. However, don't know is somebody will be fortunate to find where to apply undo on such methods in real task
Last edited by Dragokas; Nov 17th, 2017 at 05:36 PM.
Re: [VB6] StringBuilder - Fast string concatenation
Originally Posted by jpbro
For example, when building an SQL string I might need a list of field names all separated by commas except for the last field. In this case, the following:
Code:
For ii = 1 to Fields.Count
sb.Append Fields(ii).Name
sb.Append ", "
Next ii
sb.UndoLastAppend
I often solve the above with a separate String-Array (which performs much better - e.g. in larger CSV-exports) -
and it is not much more to code (same amount of lines).
Code:
Redim Tmp(1 to Fields.Count) As String 'the Fields-Count is usually known before entering the loop
For ii = 1 to Fields.Count
Tmp(ii) = Fields(ii).Name
Next ii
sb.Append Join(Tmp, ", ")
BTW - I'm not sure, whether the larger efforts (dealing with separate Heap-Allocs) is worth it in a String-Builder-Class -
since I've found that a LeftHand-side Mid$-instruction works just as well (I use that simple scheme in the RC5-SB) -
below is a small performance-test (based on a simulated CSV-export-scenario with 32 Cols and 100000 rows).
Code:
Option Explicit
Private SArr(0 To 31, 0 To 100000) As String
Private Sub Form_Load() 'prefill a static string-array with the inputs
Dim x&, y&, S$
For y = 0 To UBound(SArr, 2)
S = "Row" & y & "_"
For x = 0 To UBound(SArr, 1)
SArr(x, y) = S & "Col" & x
Next
Next
End Sub
Private Sub Form_Click()
AutoRedraw = True: Cls
Dim x&, y&, Arr$()
Dim SB1 As clsStringBuilder, SB2 As vbRichClient5.cStringBuilder
New_c.Timing True
Set SB1 = New clsStringBuilder
ReDim Arr(0 To UBound(SArr, 1)) 'redim beforehand (the Column-Size is known)
For y = 0 To UBound(SArr, 2)
For x = 0 To UBound(SArr, 1)
Arr(x) = SArr(x, y)
Next
SB1.Append Join(Arr, ", ")
SB1.Append vbCrLf
Next
Print "SB1 Dragokas", New_c.Timing
Set SB1 = Nothing
New_c.Timing True
Set SB2 = New_c.StringBuilder
ReDim Arr(0 To UBound(SArr, 1)) 'redim beforehand (the Column-Size is known)
For y = 0 To UBound(SArr, 2)
For x = 0 To UBound(SArr, 1)
Arr(x) = SArr(x, y)
Next
SB2.AppendNL Join(Arr, ", ")
Next
Print "SB2 RichClient", New_c.Timing
End Sub
The RC5-SB (using Mid$) being about factor 4 faster on my machine (for such larger outputs).
Re: [VB6] StringBuilder - Fast string concatenation
I agree with Olaf. I don't use the Mid$ method in VBCorLib, but do allocate a single string buffer and use CopyMemory, doubling the buffer size as it needs to grow. Using Olaf's benchmark, VBCorLib is an additional 30-40% faster than RC5. I'm not sure why. I want to think it's the difference with Mid$ and CopyMemory, but I can't believe it would cause that much of a discrepancy, so maybe the way the buffer is managed.
This clsStringBuilder is nice, but it does seem a bit complex with buffer management. It does have a lot of features though. I'm always interested in seeing other people's implementations of similar classes.
Last edited by killian353535; Nov 18th, 2017 at 06:48 AM.
Re: [VB6] StringBuilder - Fast string concatenation
I'd be interested in seeing the ConcatCollection method. It must still have to iterate the collection and append into a final buffer. The difference with VBCorLib is that it is appending into a buffer with each call to Append so the final ToString method just returns a substring of the internal buffer. However, my way of doing things does prevent me from being able to Undo, such as the clsStringBuilder class was updated to do. But, yes the Collection class can be extremely fast with adding and iterating values. So that might be the best way to go if you're going to chunk as you did. VBCorLib doubles the buffer when increasing its capacity. Though you can set the capacity to a higher initial value if you know you're handling a lot of data. Cuts down on the doubling a bit. All-in-all I've seen many implementations of a string builder over the years. It's always interesting. Now if we can only get Olaf to share his
Last edited by killian353535; Nov 18th, 2017 at 09:55 AM.
VB6 is my therapy.
Sometimes when you're trying to solve a problem you need to talk to the
Re: [VB6] StringBuilder - Fast string concatenation
Thanks, wqweto and Olaf. Nice tests.
The RC5-SB (using Mid$) being about factor 4 faster on my machine (for such larger outputs).
It's because of too frequent reallocations. For such large data, initial Chunk increasing size in VolteFace's SB should be adjusted to ~ like 10 MB (Const CHUNK_SIZE = 10485760).
In such case above test returns on my machine in compiled mode:
Re: [VB6] StringBuilder - Fast string concatenation
Originally Posted by Dragokas
Thanks, wqweto and Olaf. Nice tests.
It's because of too frequent reallocations. For such large data, initial Chunk increasing size in VolteFace's SB should be adjusted to ~ like 10 MB (Const CHUNK_SIZE = 10485760).
In such case above test returns on my machine in compiled mode:
Yep, much depends on the re-allocation-scheme.
Though I'd consider an *initial* Chunk-allocation of 10MB a bit too much - in case the SB is fired-up
as only an intermediate instance, to gather a few smaller string-snippets (in the 1-50KByte range).
A dynamic chunk-allocation covers both scenarios - although a factor 2 (as used by killian) is a bit much for my taste, I usually choose 1.6 as a compromise.
Here's an extract of the simple little SB-Class of the RC5 (I've removed the UTF8-stuff due to cross-refs into other RC5-modules)...
The way it is currently implemented represents, what one can achieve without API-usage (being quite universal in small- and large-size- concat-scenarios without being a slouch).
Code:
Option Explicit
Private mString As String, mCurPos As Long, mBufLen As Long
Public Property Get length() As Long
length = mCurPos
End Property
Public Sub Append(ByRef NextPart As String)
Dim lLen As Long
lLen = Len(NextPart)
If lLen + mCurPos >= mBufLen Then
mString = mString & Space$(mCurPos * 1.6 + lLen + 8192)
mBufLen = Len(mString)
End If
Mid$(mString, mCurPos + 1) = NextPart: mCurPos = mCurPos + lLen
End Sub
Public Sub AppendNL(ByRef NextPart As String, Optional NLChars As String = vbNewLine)
Append NextPart
Append NLChars
End Sub
Public Sub Clear()
mString = vbNullString: mCurPos = 0: mBufLen = 0
End Sub
Public Property Get ToString() As String
If mCurPos > 0 Then ToString = Left$(mString, mCurPos)
End Property
Re: [VB6] StringBuilder - Fast string concatenation
I can see a factor of 2 might be too much at times. Never even though about changing it, maybe even letting it be adjustable. You've given me something to think about.
So I noticed something about your extracted RC5 code and made a change to see how your test was affected. So I created two SB classes and in keeping with your no API-usage I removed one of the string allocations when you're expanding the buffer.
Re: [VB6] StringBuilder - Fast string concatenation
Bumping the chunk size in Dragokas' code certainly improves performance at the expense of memory of course. It's usually slightly faster than RC5 in this case.
I didn't try ConcatCollection because I don't see where that code is??
I've just tried using an array of Strings with Join$ for the Value and it is giving me the best performance now (CStringBuilderJ):
Code:
Option Explicit
Public Enum e_AppendModifier
[_appendmod_Ignore] = -1 ' Internal use
appendmod_None
appendmod_CrLf ' Append CRLF after primary append operation
appendmod_Lf ' Append LF after primary append operation
End Enum
Private Const mc_BufferSize As Long = 128
Private ma_Strings() As String
Private m_NextIndex As Long
Private m_UndoBump As Long
Private m_Undoable As Boolean
Public Property Get Value() As String
If UBound(ma_Strings) <> m_NextIndex - 1 Then
ReDim Preserve ma_Strings(m_NextIndex - 1)
End If
Value = Join$(ma_Strings, "")
End Property
Public Sub Undo()
Dim ii As Long
If Not m_Undoable Then Err.Raise 5, , "Undo buffer is exhausted."
If m_NextIndex = 0 Then Err.Raise 5, , "Nothing to undo."
m_Undoable = False
For ii = 0 To m_UndoBump
m_NextIndex = m_NextIndex - 1
ma_Strings(m_NextIndex) = ""
Next ii
End Sub
Public Sub Append(ByVal p_String As String, Optional ByVal p_Modifier As e_AppendModifier)
Dim l_ReRun As Boolean
m_Undoable = True
Do
If m_NextIndex = 0 Then
ReDim ma_Strings(mc_BufferSize - 1)
Else
If m_NextIndex > UBound(ma_Strings) Then
ReDim Preserve ma_Strings(UBound(ma_Strings) + mc_BufferSize - 1)
End If
End If
ma_Strings(m_NextIndex) = p_String
m_NextIndex = m_NextIndex + 1
If p_Modifier > appendmod_None Then
m_UndoBump = 1
l_ReRun = True
Select Case p_Modifier
Case appendmod_CrLf
p_Modifier = [_appendmod_Ignore]
p_String = vbCrLf
Case appendmod_Lf
p_Modifier = [_appendmod_Ignore]
p_String = vbLf
End Select
Else
l_ReRun = False
If p_Modifier = appendmod_None Then
m_UndoBump = 0
End If
End If
Loop While l_ReRun
End Sub
Re: [VB6] StringBuilder - Fast string concatenation
Originally Posted by Dragokas
jpbro, see post #11.
BTW, here is a project with all 4 tests.
A quite similar result to the "VB-Collection-Concat approach" can be achieved with the RC5-cArrayList (which has a Join-method).
Adding the following into the Test-Setup:
Code:
New_c.Timing True
Dim SB5 As cArrayList
Set SB5 = New_c.ArrayList(vbString)
ReDim Arr(0 To UBound(SArr, 1)) 'redim beforehand (the Column-Size is known)
For y = 0 To UBound(SArr, 2)
For x = 0 To UBound(SArr, 1)
Arr(x) = SArr(x, y)
Next
SB5.Add Join(Arr, ", ")
Next
SB5.Add ""
s = s & vbCrLf & "RC5-ArrList" & vbTab & New_c.Timing & vbTab & Len(SB5.Join(vbCrLf))
txtText1.Text = s
Set SB5 = Nothing
Edit: Have just seen, that the retrieval of the resulting String-Concat was outside the Timing.
To remedy that I've included (after each of the Test-Loops):
Code:
Dim L&
...
L = Len(ConcatCollection(SB3)) 'or ToString or .Value or whatever is needed to get the result
x = x & New_c.Timing & ... & L
With that corrected, the timings now look this way:
Re: [VB6] StringBuilder - Fast string concatenation
Dragokas, the collection isn't including vbCrLf. But that aside, I don't think the timing is accurate since RC5 does 99% of the work on each append instead of a form of chunking as the rest do. By moving the New_c.Timing to the end of the description string it will include the entire building process.
With that I get the Array version as the fastest. I included VBCorLib for my comparison and added the vbCrLf for the collection version.
Re: [VB6] StringBuilder - Fast string concatenation
This thread has been hijacked with these comparisons, but it has been enlightening.
Not problem, you all helped me a lot in understanding of alternatives and also how to improve my own fork in speed.
I already done 1.6 factor increasing of Chunk and it show even more performance.
killian353535, give me code example for VBCorLib based SB and I'll include it to the tests.
Is that VBCorLib.dll (modified 08.06.2008) at Github is a latest version?
Re: [VB6] StringBuilder - Fast string concatenation
After moving all the Timing calls after any final calls to the stringbuilder implementations, I'm basically getting a wash between wqweto's Collection, my String() array, and Olaf's RC5 ArrayList approaches. Running multiple tests, sometimes any one of them come out ahead marginally over the others - all around the 500-600 msec range for me.
Dragokas' heap approach with large buffer and Olaf's current RC5 StringBuilder implementation are slightly slower, both in the 700-800 msec range. Again, the winner flip-flops a bit between either in multiple timing tests.
Re: [VB6] StringBuilder - Fast string concatenation
Originally Posted by killian353535
Dragokas, the collection isn't including vbCrLf.
Yep, but that can be corrected with an:
SB3.Add ""
after the outer loop (similar to what I did with the cArrayList-approach - I think you also have that in your CorLib - worth a try I think).
Originally Posted by killian353535
By moving the New_c.Timing to the end of the description string it will include the entire building process.
Yep. In my prior posting I've suggested to use a Long-Variable to gather the resulting Len - but that will work as well.
Originally Posted by killian353535
With that I get the Array version as the fastest. I included VBCorLib for my comparison and added the vbCrLf for the collection version.