My Very First ASM Code! :D IsEven()

Share your advanced PureBasic knowledge/code with the community.
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

My Very First ASM Code! :D IsEven()

Post by Xombie »

Code updated for 5.20+

I needed a way to check if a value was even or odd. So, I looked in the forum and didn't notice anything. I looked around in the examples and didn't see anything. So, I tried some code. I messed around with memory stuff and strings. Got that to work okay. I then tried some stuff with Right(), Bin() and etc... that one worked faster. However, not so elegant. I thought... oooooohhhhh... nice time to learn a little ASM. Can't be *that* scary. ^_^ So, after reading the FASM tutorial on their website I managed to fumble my way through some stuff.

Here's the two procedures I came up with. One was the faster non-ASM code. The other is the much faster ASM code. Also, I got to learn about using "!" or inline ASM and I went with using "!" so you don't have to turn on inline asm.

Code: Select all

Procedure.b IsEven(inValue)
  If Right(Bin(inValue),1) = "1" : bRet = #False : Else : bRet = #True : EndIf
  ProcedureReturn bRet
EndProcedure

Procedure IsEvenASM(inValue)
  ; Test whether a value is Even (#TRUE) or Odd (#FALSE)
  !MOV eax, [esp]
  ; Copy our parameter value into the eax register.  inValue will be pushed onto the stack
  ; and is a LONG type.  It's the only parameter passed so it's offset is 0.  [esp + 0]
  !MOV ebx, 0
  ; From what I can tell CMOVC copies from one dword to another so store this as my FALSE value.
  !BT eax, 0
  ; Store the low order bit (the first bit starting from the right) to our CF (carry flag).
  !MOV eax, 1
  ; By default, pass that it is TRUE.  We will test if it's FALSE in the next CMOVC statement.
  !CMOVC eax, ebx
  ; Now test to see if our CF (carry flag) is set (the 'C' at the end of CMOV).  If so, copy
  ; ebx(0) to eax.  Odd numbers have a 1 in their low order bit.  In that case the CF is set.
  ; And when the CF flag thingie is set, we copy ebx to eax making our return value FALSE.
  ; If not, this won't copy anything since it's a conditional statement.
  ProcedureReturn
  ; Now return the value of eax.
EndProcedure

Define iLoop, iCount, Start, Stop

iCount = 0
Start = GetTickCount_()
For iLoop = 0 To 2000000
  If IsEvenASM(iLoop) = #True : iCount + 1 : EndIf
Next iLoop
Stop = GetTickCount_()
MessageRequester(Str(iCount), Str(Stop-Start)) ; 16 ms on my computer
;
iCount = 0
Start = GetTickCount_()
For iLoop = 0 To 2000000
  If IsEven(iLoop) = #True : iCount + 1 : EndIf
Next iLoop
Stop = GetTickCount_()
MessageRequester(Str(iCount), Str(Stop-Start)) ; 219 ms on my computer
So... go ahead and laugh and tell me how I messed it up. :D I'm rather proud of myself that I got it to work and I kinda a bit of what that tutorial was talking about. Of course, now I *must* try to convert all my slow-ish procedures to ASM! :D


[Edit: Oh hey! Look at that! It must be fate. I learn a little ASM and now I'm an "Apprentice"! :D ]
[Edit 2: Duh, I didn't need two of the lines in the ASM version so I removed them. Didn't really notice a speed increase, though.]
filperj
User
User
Posts: 77
Joined: Tue Sep 16, 2003 8:53 pm
Location: Nevers(France)

Post by filperj »

Boole is our friend^^

Code: Select all

Procedure.b IsEven(n);inValue.l)
   ;If Right(Bin(inValue),1) = "1" : bRet = #False : Else : bRet = #True : EndIf
   ProcedureReturn (~n)&1;bRet <- no more strings
EndProcedure
Procedure IsEvenASM(inValue.l)
   ; Test whether a value is Even (#TRUE) or Odd (#FALSE)
   !MOV eax, [esp]
   ; Copy our parameter value into the eax register.  inValue will be pushed onto the stack
   ; and is a LONG type.  It's the only parameter passed so it's offset is 0.  [esp + 0]
   !MOV ebx, 0
   ; From what I can tell CMOVC copies from one dword to another so store this as my FALSE value.
   !BT eax, 0
   ; Store the low order bit (the first bit starting from the right) to our CF (carry flag).
   !MOV eax, 1
   ; By default, pass that it is TRUE.  We will test if it's FALSE in the next CMOVC statement.
   !CMOVC eax, ebx
   ; Now test to see if our CF (carry flag) is set (the 'C' at the end of CMOV).  If so, copy
   ; ebx(0) to eax.  Odd numbers have a 1 in their low order bit.  In that case the CF is set.
   ; And when the CF flag thingie is set, we copy ebx to eax making our return value FALSE.
   ; If not, this won't copy anything since it's a conditional statement.
   ProcedureReturn
   ; Now return the value of eax.
EndProcedure

iLoop.l
iCount.l
Start.l
Stop.l

iCount = 0
Start = GetTickCount_()
For iLoop = 0 To 20000000
   If IsEvenASM(iLoop) = #True : iCount + 1 : EndIf
Next iLoop
Stop = GetTickCount_()
MessageRequester(Str(iCount), Str(Stop-Start)) ; 16 ms on my computer
;
iCount = 0
Start = GetTickCount_()
For iLoop = 0 To 20000000
   If IsEven(iLoop) = #True : iCount + 1 : EndIf
Next iLoop
Stop = GetTickCount_()
MessageRequester(Str(iCount), Str(Stop-Start)) ; 219 ms on my computer
;
iCount = 0
Start = GetTickCount_()
For iLoop = 0 To 20000000
   If (~iLoop)&1 : iCount + 1 : EndIf  ;<- no more procedure call
Next iLoop
Stop = GetTickCount_()
MessageRequester(Str(iCount), Str(Stop-Start)) ; 
Your main mistake was to use strings, it's always much slooooooower than integers...
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

Nice! I couldn't quite figure out how to do it with boolean stuff and I kinda wanted to try asm so I didn't even go that route. Thanks for showing me that :D I *knew* there had to be a nice simple way to do it with boolean operators but I just couldn't find it at first.

Now this is where macros would be nice. Instead of a procedure for every IsEven() check it could drop in that little bit of code where needed ^_^
filperj
User
User
Posts: 77
Joined: Tue Sep 16, 2003 8:53 pm
Location: Nevers(France)

Post by filperj »

Yes, it's a good example of macro's usefulness...
"(~Number)&1" is not long to type, but "IsEven(Number)" is a bit more readable :)
Tommeh
Enthusiast
Enthusiast
Posts: 149
Joined: Sun Aug 29, 2004 2:25 pm
Location: United Kingdom

Post by Tommeh »

Nice one, that assembly will be a good starter to a lot of people.

Here, you can use the modulo % operator for very quick results

Code: Select all

Procedure IsOdd(num)
ProcedureReturn num % 2
EndProcedure

Debug IsOdd(901)
Its easier if you use IsOdd because then You don't have to alter anything, :)
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Post by Rescator »

You can speed that up a bit more btw!

!MOV eax, [esp]

Just remove that!
By default the first variable passed to a procedure is stored in Eax.
So you don't have to fech it from [Esp]

So by removing that you can shave off 1 more instruction from the routine :P

Hmm! Tommeh that's a great idea.
Altough speedwise that is about the same speed as the ASM one I bet.
The very fastest solution it seems is to simply use

if myval % 2

Rather than if IsOdd(myval)
as a procedure call in itself slows things down a bit.
So if val % 2 works great, then that should be the fastest
since you avoid using a procedure call.
Tommeh
Enthusiast
Enthusiast
Posts: 149
Joined: Sun Aug 29, 2004 2:25 pm
Location: United Kingdom

Post by Tommeh »

I know, it was just an example :D
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Post by Rescator »

Yeah Tommeh but your solution is the fastest and possibly the simplest too :P
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6166
Joined: Sat May 17, 2003 11:31 am
Contact:

Post by blueznl »

that's a lot of code :-)

Code: Select all

Procedure.l x_odd(x)
  ProcedureReturn x & 1
EndProcedure

Procedure x_even(x)
  ProcedureReturn 1 - (x & 1)
EndProcedure

Debug x_odd(1)
Debug x_odd(2)
Debug x_even(1)
Debug x_even(2)
yeah i know, it's not about the function but about the (assembly) code but i just couldn't resist :-)
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB)
( The path to enlightenment and the PureBasic Survival Guide right here... )
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

This is excellent. Not only did I get to learn some ASM but I also just learned why I couldn't figure out the boolean expression to use. See - I thought the RHS (right hand side) figure needed to be the same number of bits when using &, |, ~, etc... Which is stupid because a LONG binary is always 32 bits :lol: I guess Bin() only shows what's significant without the padding? If I had thought that one little step further, I would've been able to do it with boolean expressions.

Ah well, I'm not sorry about that since I got to dip my fingers into the ASM pool :D

Also - Rescator - I removed the !MOV eax, [esp] line but then the function returned False all the time. Did I do something wrong?

Also, did I read the docs right when it said we're only allowed to use the 32 bit general registers in our ASM code? Is that only for inline asm and not if we pass ASM directly to the FASM?
Post Reply