Page 1 of 1

StringBuilder Interface

Posted: Thu Feb 16, 2023 10:46 pm
by HeX0R
This is a rework of my module version from ->here<-, using an interface now for easier handling.
Since I somehow "hijacked" the thread from mk-soft (my apologize), I've created a new thread now.

Code: Select all

;- Top
; -----------------------------------------------------------------------------
;  Name        : StringBuilder Interface
;  Description : Fast String concatenation
;  Author      : HeX0R
;  Date        : 2023-02-16
;  Original    : https://www.purebasic.fr/english/viewtopic.php?p=558277#p558277
;  OS          : Cross-platform
;  Forum       : https://www.purebasic.fr/english/viewtopic.php?p=595972#p595972
;
; AND...ashes on my head!!:
; Base idea from mk-soft
;              : https://www.purebasic.fr/english/viewtopic.php?t=75758
; -----------------------------------------------------------------------------

Structure struc_StringBuilder
	VTable.i
	*Address
	*Pointer
	MemSize.i
	PacketSize.i
	Length.i
EndStructure

Interface IF_StringBuilder
	Add(String.s)
	GetString.s()
	Reset(KeepMemory = #False)
	Release()
EndInterface

Procedure __SB_Reset(*THIS.struc_StringBuilder, KeepMemory = #False)
	;Reset back to initial values, for reusing the interface
	If KeepMemory = #False
		If *THIS\MemSize > *THIS\PacketSize
			*THIS\Address = ReAllocateMemory(*THIS\Address, *THIS\PacketSize, #PB_Memory_NoClear)
		EndIf
		*THIS\MemSize = *THIS\PacketSize
	EndIf
	*THIS\Pointer = *THIS\Address
	*THIS\Length  = 0
EndProcedure

Procedure __SB_Add(*THIS.struc_StringBuilder, String.s)
	Protected Len, *Add, Result = #True
	
	Len = StringByteLength(String)
	If Len + *THIS\Length + 2 > *THIS\MemSize
		*THIS\MemSize = *THIS\Length + Len + 2 + *THIS\PacketSize
		*Add          = ReAllocateMemory(*THIS\Address, *THIS\MemSize, #PB_Memory_NoClear)
		If *Add
			If *Add <> *THIS\Address
				*THIS\Pointer = *THIS\Pointer - *THIS\Address + *Add
			EndIf
			*THIS\Address = *Add
		Else
			;not enough memory?
			ProcedureReturn #False
		EndIf
	EndIf
	CopyMemoryString(@String, @*THIS\Pointer)
	*THIS\Length + Len
	
	ProcedureReturn Result
EndProcedure

Procedure.s __SB_GetString(*THIS.struc_StringBuilder)
	ProcedureReturn PeekS(*THIS\Address, *THIS\Length / SizeOf(Character))
EndProcedure

Procedure __SB_Release(*THIS.struc_StringBuilder)
	If *THIS\Address
		FreeMemory(*THIS\Address)
	EndIf
	FreeMemory(*THIS)
EndProcedure

Procedure New_StringBuilder(PacketSize.i = 65536)
	Protected *THIS.struc_StringBuilder, *Buffer
	
	*THIS        = AllocateMemory(SizeOf(struc_StringBuilder))
	*THIS\VTable = ?_VT_STRINGBUILDER_
	If *THIS = 0
		ProcedureReturn #False
	EndIf
	
	*Buffer = AllocateMemory(PacketSize, #PB_Memory_NoClear)
	If *Buffer
		*THIS\Address    = *Buffer
		*THIS\Pointer    = *Buffer
		*THIS\PacketSize = PacketSize
		*THIS\MemSize    = PacketSize
		ProcedureReturn *THIS
	Else
		FreeMemory(*THIS)
	EndIf
	ProcedureReturn #False
	
	DataSection
		_VT_STRINGBUILDER_:
		Data.i @__SB_Add()
		Data.i @__SB_GetString()
		Data.i @__SB_Reset()
		Data.i @__SB_Release()
	EndDataSection
EndProcedure

;----------------------------EOF-------------------------


CompilerIf #PB_Compiler_IsMainFile
	
	Define i, a$, b$, c$, s1, s2
	Define SB.IF_StringBuilder
	
	SB = New_StringBuilder()
	a$ = "abcdefghijklmnopqrstuvwxyz" + #CRLF$
	s1 = ElapsedMilliseconds()
	For i = 1 To 10000
		SB\Add(a$)
	Next i
	b$ = SB\GetString()
	s1 = ElapsedMilliseconds() - s1
	s2 = ElapsedMilliseconds()
	For i = 1 To 10000
		c$ + a$
	Next i
	s2 = ElapsedMilliseconds() - s2
	MessageRequester("Info", ~"Stringbuilder: \t" + Str(s1) + ~"ms\nPB-Way: \t" + Str(s2) + "ms")
	;Debug b$
	SB\Release()
	
CompilerEndIf

Re: StringBuilder Interface

Posted: Fri Feb 17, 2023 3:06 am
by ChrisR
Great, the interface really makes it easier to handle 8)
I reworked a bit on the base of your previous module, without the interface.
In order not to "hijack" your thread or mk-soft's one, I will post it in a new topic.
So one more, sorry, but there may be things to take.
Thank you for this rework, waiting for a native solution for large strings :wink:

Re: StringBuilder Interface

Posted: Fri Feb 17, 2023 9:54 am
by StarBootics
@HeXOR : when you allocate a structure with the AllocateStructure() it's better to use FreeStructure() for consistency not FreeMemory(). Beside that nice job !

Best regards
StarBootics

Re: StringBuilder Interface

Posted: Fri Feb 17, 2023 10:24 am
by HeX0R
That's true, but where exactly did I use AllocateStructure()?
There are no lists in those structures, therefore an AllocateMemory() is good enough

Re: StringBuilder Interface

Posted: Fri Feb 17, 2023 12:17 pm
by NicTheQuick
HeX0R wrote: Fri Feb 17, 2023 10:24 amThat's true, but where exactly did I use AllocateStructure()?
If I don't want to allocate only a certain amount of bytes, but there is an underlying structure, then I always use AllocateStructure(). This way I am also prepared for future changes to the structure.
Or does that create some overhead that I don't know about (yet)?

Re: StringBuilder Interface

Posted: Fri Feb 17, 2023 2:13 pm
by Mijikai
Thx for sharing HeX0R.

Re: StringBuilder Interface

Posted: Fri Feb 17, 2023 4:17 pm
by mk-soft
HeX0R wrote: Thu Feb 16, 2023 10:46 pm This is a rework of my module version from ->here<-, using an interface now for easier handling.
Since I somehow "hijacked" the thread from mk-soft (my apologize), I've created a new thread now.
If already, then already take over everything. :mrgreen:

Missing concat, left, right, insert, len :wink:

and missing "base of mk-soft" :cry:

Re: StringBuilder Interface

Posted: Fri Feb 17, 2023 5:33 pm
by HeX0R
Well... this is a stringbuilder... nothing else!
No need for any more fancy things like left, right, len, whatever.
It is to overcome the bottleneck of PBs string handling for concatenation very long strings, and who in the world would start to do a Rset() or Insert (and why??) in a several thousand bytes long string?
Regarding the AllocateMemory() against the AllocateStructure() discussion:
In the code above it doesn't really matter, although, all your remarks are valid!
But there never will be any list, array or map in that structure (again: it is nothing but a simple stringbuilder), therefore there is no real difference between both variants.
and missing "base of mk-soft" :cry:
You are abolutely right, my apologize, I'll add that in a minute!

Re: StringBuilder Interface

Posted: Fri Feb 17, 2023 7:41 pm
by ChrisR
hmm, I understand your point and I thought like you.
But I started to use it in my app and I have 3 functions that I need, copied from my Fork:
- Concat: to concatenate more quickly multiple stringbuilder
- Right: in few cases, I need to know if the string ends with a double #CRLF$ or not, and it's simple and fast here: *Pointer - Length
- Insert: to be able to insert at the beginning of the string, although I could do it differently but it's faster here with Move and CopyMemory
I don't need anything else right now

Re: StringBuilder Interface

Posted: Fri Feb 17, 2023 8:04 pm
by skywalk
Thanks for the post.
I know this is nitpicking, but a true comparison should return the string to a PB variable for later use.
b$ = SB\GetString()
And b$ = #Empty$ before PB way runs.

Re: StringBuilder Interface

Posted: Fri Feb 17, 2023 10:20 pm
by HeX0R
Not nitpicking, but valid!
I've added it for completeness.

@Chris:
All you've said is true, but the definition of a "stringbuilder" is nothing else, then what is included above.
Since yours is called ExString (and the one from mk-soft FastString), there is nothing wrong to add some more features to it.
I personally needed a stringbuilder only once, for a cgi script that builds-up pretty huge HTML output.
There was no need to do any more stuff on that huge string, but there might come a time, where I need to do more things, then I definately will look into your or mk-softs interpretations.
But in all "typical applications", I'm fine with PBs string performance.