Page 2 of 2

Posted: Sat Mar 11, 2006 10:20 pm
by freak
The reason why the structure solution does not work is that a String inside
a structure is referenced through a pointer to that string only. (because of its variable length)

The "String" structure looks like this:

Code: Select all

Structure String
  s.s
EndStructure
So when doing *Struct.String, you actually have a pointer (the structure pointer)
to a pointer (the s.s) to a string (the actual data)
Doing "@String$" however returns the pointer to the actual data.
This is a fundamental difference to the other basic types, where the variable data
is actually stored inside the structure, and that is why it will not work with strings.

This one illustrates it:

Code: Select all

String$ = "Hello World"
*Pointer = @String$        ; get pointer to string data
*Struct.String = @*Pointer ; get pointer to pointer to string data
Debug *Struct\s            ; now it displays
The best solution to this problem really depends on what you need to do with the string.

If you need to compare the string data to something else, the best thing
is to use commands like CompareMemoryString()/MemoryStringLength(),
which can operate on pointers, so you have no overhead at all.

If what you need is to copy the string from the pointer into a normal
PB variable, PeekS() is a good thing to use, as it is almost as fast as
a normak string copy like "a$ = b$".

Posted: Sat Mar 11, 2006 10:21 pm
by Trond
I was a bit stupid actually, I found out that in reality I didn't need to cast anything at all, I could simply move the value into eax and then do a procedurereturn..... ....as soon as the debugger starts debugging strings correctly, of course:

Code: Select all

Procedure.s HardTrim(Instr.s) 
  Instr = Trim(Instr) 
  !mov eax, [esp] 
  !l_loopstart: 
  !cmp dword [eax], 0 
  !jz  l_hardtrim_procedurereturn 
  !inc eax 
  !jmp l_loopstart 
  !l_hardtrim_procedurereturn: 
  ProcedureReturn 
EndProcedure 

; Debug HardTrim("Debug statement messes it all up!") 
sine.s = HardTrim("Debug statement messes it up!") 
!mov dword [v_Temp], eax 
Debug PeekB(Temp) ; We see that this is a null string 
Debug sine        ; Which is why it debugs emptyness 
                  ; However, the infamous ... 
Debug HardTrim("Debug statement messes it all up!")

Posted: Sat Mar 11, 2006 10:28 pm
by Dare2
So the structureunion idea (2 posts earlier) is not a good one?

Posted: Sat Mar 11, 2006 10:28 pm
by Trond
freak wrote:If what you need is to copy the string from the pointer into a normal PB variable, PeekS() is a good thing to use, as it is almost as fast as a normak string copy like "a$ = b$".
Not quite as fast:

Code: Select all

*AString.s = "Apple pie"

#Tries = 10000000 

; Fast way of copying a string from a pointer into a normal string
time = GetTickCount_() 
For I = 0 To #Tries 
  !mov  edx, [p_AString] 
  !lea  ecx, [v_InSane]
  !call SYS_FastAllocateStringFree
  InSane.s 
Next 
MessageRequester("", Str(GetTickCount_()-time)) 

; PeekS() way of copying a pointer into a normal string
time = GetTickCount_() 
For I = 0 To #Tries 
  InSane.s = PeekS(@AString) 
Next 
MessageRequester("", Str(GetTickCount_()-time))

Posted: Sat Mar 11, 2006 10:30 pm
by Trond
Dare2 wrote:So the structureunion idea (2 posts earlier) is not a good one?
Not good for my purpose, since what I wanted to do was to return a pointer to a string as a string from a procedure. But instead of converting it to a string, then returning it, I forgot that I could simply leave the pointer in eax and case solved.Image

Posted: Sat Mar 11, 2006 10:36 pm
by Dare2
:D

Anyhow (and depending on use as, in some cases, you effectively have the string without a need to copy), even PeeKS is faster than this:

Code: Select all

Structure tooMany
  StructureUnion
    Zstring.string
    ptr.l
  EndStructureUnion
EndStructure

test.tooMany
test\ptr = AllocateMemory(20)
PokeS(test\ptr,"Peaches and Pears")

time = GetTickCount_()
For I = 0 To #Tries
  notSoMad.s = test\Zstring\s
Next
MessageRequester("", Str(GetTickCount_()-time))

FreeMemory(test\ptr)
Add some pointer arith and PeekS leaves it dead. And yours leaves PeeKS behind.

Case closed, as you said. :)

Edit: And thanks for the useful code.

Posted: Sat Mar 11, 2006 10:45 pm
by freak
> Not quite as fast:

I said as fast as a normal "a$ = b$" statement, not as fast as some inline asm code.

try this:

Code: Select all

#LENGTH = 50000
#LOOPS  = 10000

String$ = RSet("A", #LENGTH, "A")
*Ptr = @String$
*Struct.String = @*Ptr

time0 = ElapsedMilliseconds()

For i = 1 To #LOOPS
  x$ = String$
Next i

time1 = ElapsedMilliseconds()

For i = 1 To #LOOPS
  x$ = *Struct\s
Next i

time2 = ElapsedMilliseconds()

For i = 1 To #LOOPS
  x$ = PeekS(*Ptr)
Next i

time3 = ElapsedMilliseconds()


Text$ = "x$ = String$:  "+Str(time1-time0)+" ms"+Chr(13)
Text$ + "x$ = *Struct\s:  "+Str(time2-time1)+" ms"+Chr(13)
Text$ + "x$ = PeekS(*Ptr):  "+Str(time3-time2)+" ms"+Chr(13)

MessageRequester("", Text$)
Try with different length and loop counts.. it makes no big difference.


Now your asm procedure above is compleetly wrong. Its a fluke that part of your code actually works.

Try to put the result from your procedure through another function, and you will see it fails as well:

Code: Select all

sine.s = Trim(HardTrim("Debug statement messes it up!"))
Have a look at the asm code generated by PB for a procedure that returns strings to
see how that is actually handled.

In fact, for returning a string from a procedure (given its pointer), PeekS() is probably the best way to go.

Code: Select all

Procedure.s A(Input$)
  ProcedureReturn Input$
EndProcedure

Procedure.s B(Input$)
  ProcedureReturn PeekS(@Input$)
EndProcedure

#LENGTH = 50000
#LOOPS  = 10000

String$ = RSet("A", #LENGTH, "A")

time0 = ElapsedMilliseconds()

For i = 1 To #LOOPS
  x$ = A(String$)
Next i

time1 = ElapsedMilliseconds()

For i = 1 To #LOOPS
  x$ = B(String$)
Next i

time2 = ElapsedMilliseconds()


Text$ = "A:  "+Str(time1-time0)+" ms"+Chr(13)
Text$ + "B:  "+Str(time2-time1)+" ms"

MessageRequester("", Text$)

Posted: Sat Mar 11, 2006 10:49 pm
by Dare2
This is getting to be an interesting and informative/useful thread!


Edit:

If you don't actually need to copy the string (Say, for example, parsing an html file preloaded into memory), the structureunion approach with the .string structure embedded just flies. Need some nifty code to outperform it.

Posted: Sun Mar 12, 2006 1:20 am
by Flype
another trick based on freak tips.

parsing a string in memory :

Code: Select all

Macro LPSZSTR(userVar,userExpr)
  userVar#str.s = (userExpr)
  userVar#ptr.Byte = @userVar#str
  userVar.String = @userVar#ptr
EndMacro

LPSZSTR(*pointer,"TEMP = "+GetEnvironmentVariable("TEMP"))

While *pointerPtr\b
  Debug *pointer\s
  *pointerPtr + 1
Wend

Posted: Sun Mar 12, 2006 9:35 am
by Trond
freak wrote:> Not quite as fast:

I said as fast as a normal "a$ = b$" statement, not as fast as some inline asm code.
"Some inline assembly code" is exactly the same as PB generated for a$ = b$.

Code: Select all

; a$ = b$
  MOV    edx,dword [v_b$]
  LEA    ecx,[v_a$]
  CALL   SYS_FastAllocateStringFree

Posted: Wed Jun 14, 2006 8:56 pm
by Dr. Dri
i only read the first post of this topic and i would like to notice this:

@Variable.s gives the address of the string and not the address of the "Variable" symbol
There is no way you can get it with PB native (no asm) code

Maybe a new keyword could fix this (something like AddressOf(String.s) or whatever)

i wish i could do this with regular strings:

Code: Select all

Dim String.s(0)
String(0) = "PureBasic"

*Pointer.String = String()
Debug *Pointer
Debug @String(0)
Debug PeekL(*Pointer)
Debug *Pointer\s
Dri