Max() and Min(), procedure and macro variants.

Share your advanced PureBasic knowledge/code with the community.
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Max() and Min(), procedure and macro variants.

Post by Rescator »

If you are unsure which one of the below implementations to use then use either the Max5() and Min5() ones for maximum flexibility. (integers and floats)
Or the inline "if" method examples, for when you really need hardcore speed routines.

I needed this code myself but found it interesting enough to post here.
Something odd here though, the Macro despite being larger in number of instructions, it is faster than the procedure.

I assume the main difference in speed is the call overhead of the procedures.

Another interesting thing is compiling this code with the debugger on or off.
(leave the DisableDebugger in the source, if not it'll take ages)
It seems that if you choose "Compile with debugger" in the IDE menu the result is smaller (i.e. faster) that can't be right can it? *laughs*

I'm also curious if anyone can improve the macro's or the procedures to gain even more speed.

Code: Select all

;Public Domain
DisableDebugger
EnableExplicit

;Max5() and Min5() should handle any value, with floats be aware of rounding errors. PB 5.10 update by Demivec
Macro Min5(a,b)
	(Bool((a)<=(b)) * (a) + Bool((b)<(a)) * (b))
EndMacro

Macro Max5(a,b)
	(Bool((a)>=(b)) * (a) + Bool((b)>(a)) * (b))
EndMacro

;Max4() and Min4() are single float only, change .f to .d for doubles.
;Note! It is possible to use integers as output and cast the result back to integer,
;just beware of potential rounding errors and float number ranges. doubles are more flexible.
Procedure.f Min4(a.f,b.f)
	If a<b
		ProcedureReturn a
	EndIf
	ProcedureReturn b
EndProcedure

Procedure.f Max4(a.f,b.f)
	If a>b
		ProcedureReturn a
	EndIf
	ProcedureReturn b
EndProcedure

;Max3() and Min3() should handle any integer.
;Max3() and Min3() created by technicorn (see post further down)
Macro Min3(a,b)
	((Bool((a)<=(b))-1)&(b))|((Bool((b)<(a))-1)&(a))
EndMacro

Macro Max3(a,b)
	((Bool((a)>=(b))-1)&(b))|((Bool((b)>=(a))-1)&(a))
EndMacro

;Max2() and Min2() are longs only, change .l to .b or .w or .q for other integers.
Procedure.l Min2(a.l,b.l)
	If a<b
		ProcedureReturn a
	EndIf
	ProcedureReturn b
EndProcedure

Procedure.l Max2(a.l,b.l)
	If a>b
		ProcedureReturn a
	EndIf
	ProcedureReturn b
EndProcedure


;Max() and Min() should handle any integer.
Macro Min1(a,b)
	(Bool((a)>(b))*(b))|(Bool((b)>(a))*(a))
EndMacro

Macro Max1(a,b)
	(Bool((a)<(b))*(b))|(Bool((b)<(a))*(a))
EndMacro

timeBeginPeriod_(1)

Define start.l,stop.l,a.l,b.l,n.l,x.l,y.l,text$,c.f,d.f,cc.d,dd.d,z1.f,z2.f,zz1.d,zz2.d

a.l=20
b.l=15
c.f=20.5
d.f=14.8
cc.d=19.9
dd.d=9.3

n.l=0
start=timeGetTime_()
For n=1 To 100000000
	x=Max1(a,b)
	y=Min1(a,b)
Next
stop=timeGetTime_()
text$+"Max1()/Min1(): "+Str(stop-start)+"ms"+#LF$

n.l=0
start=timeGetTime_()
For n=1 To 100000000
	x=Max2(a,b)
	y=Min2(a,b)
Next
stop=timeGetTime_()
text$+"Max2()/Min2(): "+Str(stop-start)+"ms"+#LF$

n.l=0
start=timeGetTime_()
For n=1 To 100000000
	x=Max3(a,b)
	y=Min3(a,b)
Next
stop=timeGetTime_()
text$+"Max3()/Min3(): "+Str(stop-start)+"ms"+#LF$

n.l=0
start=timeGetTime_()
For n=1 To 100000000
	x=Max4(a,b)
	y=Min4(a,b)
Next
stop=timeGetTime_()
text$+"Max4()/Min4(): "+Str(stop-start)+"ms"+#LF$

;This is a example of "proper" coding, the Max and Min is done using If's in the loop/code.
n.l=0
start=timeGetTime_()
For n=1 To 100000000
	If a>b : x=a : Else : x=b : EndIf ;Does the same as i.e x=Max2(a,b)
	If a<b : y=a : Else : y=b : EndIf ;Does the same as i.e y=Min2(a,b)
Next
stop=timeGetTime_()
text$+"If long: "+Str(stop-start)+"ms"+#LF$

n.l=0
start=timeGetTime_()
For n=1 To 100000000
	If c>d : z1=c : Else : z1=d : EndIf ;Does the same as i.e x=Max4(a,b)
	If c<d : z2=c : Else : z2=d : EndIf ;Does the same as i.e y=Min4(a,b)
Next
stop=timeGetTime_()
text$+"If single: "+Str(stop-start)+"ms"+#LF$

n.l=0
start=timeGetTime_()
For n=1 To 100000000
	If cc>dd : zz1=cc : Else : zz1=dd : EndIf
	If cc<dd : zz2=cc : Else : zz2=dd : EndIf
Next
stop=timeGetTime_()
text$+"If double: "+Str(stop-start)+"ms"+#LF$

n.l=0
start=timeGetTime_()
For n=1 To 100000000
	x=Max5(a,b)
	x=Min5(a,b)
Next
stop=timeGetTime_()
text$+"Max5()/Min5() long: "+Str(stop-start)+"ms"+#LF$

n.l=0
start=timeGetTime_()
For n=1 To 100000000
	z1=Max5(c,d)
	z2=Min5(c,d)
Next
stop=timeGetTime_()
text$+"Max5()/Min5() single: "+Str(stop-start)+"ms"+#LF$

n.l=0
start=timeGetTime_()
For n=1 To 100000000
	zz1=Max5(cc,dd)
	zz2=Min5(cc,dd)
Next
stop=timeGetTime_()
text$+"Max5()/Min5() double: "+Str(stop-start)+"ms"+#LF$

MessageRequester("Results",text$)

timeEndPeriod_(1)
EDIT: Updated to work with Purebasic 5.10
Last edited by Rescator on Fri Feb 22, 2013 9:18 pm, edited 7 times in total.
Derek
Addict
Addict
Posts: 2354
Joined: Wed Apr 07, 2004 12:51 am
Location: England

Post by Derek »

I'm sure I'll get proved wrong here but here goes anyway, I don't think there is much you can do to improve the speed of the procedures apart from using some assembler in them perhaps as they are down to a bare minimum of code as they are.
pjay
Enthusiast
Enthusiast
Posts: 251
Joined: Thu Mar 30, 2006 11:14 am

Post by pjay »

The differences in compile times are really odd.

With the debugger enabled, it took 532ms - without the debugger it took 9250ms!

Aside from that though, I had a stab at an ASM macro version; It's faster here, but it's not well tested.

Code: Select all

DisableDebugger 
EnableExplicit 

Macro MaxFast(a,b,Result) 
  !MOV Eax,dword[v_#a] 
  !MOV Ecx,dword[v_#b] 
  !CMP Ecx,Eax 
  !cmovg Eax,Ecx 
  !MOV dword[v_#Result],Eax
EndMacro

Macro MinFast(a,b,Result)
  !MOV Eax,dword[v_#a] 
  !MOV Ecx,dword[v_#b] 
  !CMP Eax,Ecx 
  !cmovg Eax,Ecx 
  !MOV dword[v_#Result],Eax
EndMacro

Macro Max(a,b) 
  ((Not a>b)*b)|((Not b>a)*a) 
EndMacro 

Macro Min(a,b) 
  ((Not a<b)*b)|((Not b<a)*a) 
EndMacro 

timeBeginPeriod_(1) 

Define start.l,stop.l,a.l,b.l,n.l,x.l,y.l,text$ 
a.l=20 
b.l=15

n.l=0 
start=timeGetTime_() 
For n=1 To 100000000 
  x=Max(a,b) 
  y=Min(a,b) 
Next 
stop=timeGetTime_() 
text$+"Standard Macro: "+Str(stop-start)+#LF$ 

n.l=0 
start=timeGetTime_() 
For n=1 To 100000000 
  MaxFast(a,b,x) 
  MinFast(a,b,y) 
Next 
stop=timeGetTime_() 
text$+"ASM Macro: "+Str(stop-start)+#LF$ 

MessageRequester("Results",text$) 
timeEndPeriod_(1)
User avatar
pdwyer
Addict
Addict
Posts: 2813
Joined: Tue May 08, 2007 1:27 pm
Location: Chiba, Japan

Post by pdwyer »

If you are doing 100 million loops then the macro will be quicker because there is no procedure call overhead (push, push, call, pop, pop, calc etc) it;s "inline" so to speak

With only one line of code though, I don't think a proc would be appropriate. A macro cleans it up for code readability (unless you are one of those anti-macro people) but leaves it inline
Paul Dwyer

“In nature, it’s not the strongest nor the most intelligent who survives. It’s the most adaptable to change” - Charles Darwin
“If you can't explain it to a six-year old you really don't understand it yourself.” - Albert Einstein
technicorn
Enthusiast
Enthusiast
Posts: 105
Joined: Wed Jan 18, 2006 7:40 pm
Location: Hamburg

Post by technicorn »

Hello Rescator,

here's a faster version, I've also add testing.
Max3, Min3 are the new versions,
they are about 18% faster on my comp.

Edit: But Max3, Min3 only works for integers, whiles Rescators should also work for float/double

Code: Select all

DisableDebugger
EnableExplicit

Procedure Max2(a.l,b.l)
 If a>b
  ProcedureReturn a
 EndIf
 ProcedureReturn b
EndProcedure

Procedure Min2(a.l,b.l)
 If a<b
  ProcedureReturn a
 EndIf
 ProcedureReturn b
EndProcedure

Macro Max(a,b)
 ((Not a>b)*b)|((Not b>a)*a)
EndMacro

Macro Max3(a,b)
 (((Not a<=b)-1)&b)|(((Not b<a)-1)&a)
EndMacro

Macro Min(a,b)
 ((Not a<b)*b)|((Not b<a)*a)
EndMacro

Macro Min3(a,b)
 (((Not a>=b)-1)&b)|(((Not b>=a)-1)&a)
EndMacro

timeBeginPeriod_(1)

Define start.l,stop.l,a.l,b.l,n.l,x.l,y.l,xc.l,yc.l,err.l,text$

start=timeGetTime_()
For a = -1 To 1
  For b = -1 To 1
    err = 0
    x=Max(a,b)
    y=Min(a,b)
    If a>b: xc=a: Else: xc=b: EndIf
    If x <> xc
      text$ + "Error Max("+Str(a)+","+Str(b)+"): " + Str(x) + " <> " + Str(xc)+#LF$
      err = 1
    EndIf
    If a<b: yc=a: Else: yc=b: EndIf
    If y <> yc
      text$ + "Error Min("+Str(a)+","+Str(b)+"): " + Str(y) + " <> " + Str(yc)+#LF$
      err = 1
    EndIf
    If err: Continue: EndIf
    For n=1 To 20000000
      x=Max(a,b)
      y=Min(a,b)
    Next
  Next b
Next a
stop=timeGetTime_()
text$+"MinMax: " + Str(stop-start)+#LF$

start=timeGetTime_()
For a = -1 To 1
  For b = -1 To 1
    err = 0
    x=Max3(a,b)
    y=Min3(a,b)
    If a>b: xc=a: Else: xc=b: EndIf
    If x <> xc
      text$ + "Error Max3("+Str(a)+","+Str(b)+"): " + Str(x) + " <> " + Str(xc)+#LF$
      err = 1
    EndIf
    If a<b: yc=a: Else: yc=b: EndIf
    If y <> yc
      text$ + "Error Min3("+Str(a)+","+Str(b)+"): " + Str(y) + " <> " + Str(yc)+#LF$
      err = 1
    EndIf
    If err: Continue: EndIf
    For n=1 To 20000000
      x=Max3(a,b)
      y=Min3(a,b)
    Next
  Next b
Next a
stop=timeGetTime_()
text$+"Min3Max3: " + Str(stop-start)+#LF$


start=timeGetTime_()
For a = -1 To 1
  For b = -1 To 1
    err = 0
    x=Max2(a,b)
    y=Min2(a,b)
    If a>b: xc=a: Else: xc=b: EndIf
    If x <> xc
      text$ + "Error Max2("+Str(a)+","+Str(b)+"): " + Str(x) + " <> " + Str(xc)+#LF$
      err = 1
    EndIf
    If a<b: yc=a: Else: yc=b: EndIf
    If y <> yc
      text$ + "Error Min2("+Str(a)+","+Str(b)+"): " + Str(y) + " <> " + Str(yc)+#LF$
      err = 1
    EndIf
    If err: Continue: EndIf
    For n=1 To 20000000
      x=Max3(a,b)
      y=Min3(a,b)
    Next
  Next b
Next a
stop=timeGetTime_()
text$+"Min2Max2: " + Str(stop-start)+#LF$


MessageRequester("Results",text$)

timeEndPeriod_(1)
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Post by Trond »

Macro MinFast(a,b,Result)
!MOV Eax,dword[v_#a]
!MOV Ecx,dword[v_#b]
!CMP Eax,Ecx
!cmovg Eax,Ecx
!MOV dword[v_#Result],Eax
EndMacro

MinFast(4, N, Result)
Nice, but it doesn't work.
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Post by Rescator »

And sadly asm that way is nowhere as flexible as a procedure
or the faster Macro's in my original post.

The point was to be able to use Max() and Min() inside expressions.

Like:

x=Max(y,z)+Min(a,b)

Which is not possible with a asm implementation.

I updated the first post and added technicorn's macros as well.

Right now I would advise the Max3() and Min3() macros for integers,
as they are a hair faster than my Max() and Min() macros.

And Max4() and Min4() for floats. (change to .d for doubles)
Floats are up to 3 times slower though sadly.

And the procedure based Max2() and Min2() are oddly not consistent in speed we you compile and run with debugger on or off (from menu) any clue why PB Team?

Ideally an asm implementation of Max3() and Min3() and float equivalent would be made and thus giving us native integer and float Max and Min functions.

PS! The fastest Max and Min is still to simply do a
If a>b
Endif

as part of your code/loop directly.
and much faster than the macros as well, see the last example in first post ;)
It is also type independent, and probably close to what native PB Max and Min performance would be.
User avatar
Kaeru Gaman
Addict
Addict
Posts: 4826
Joined: Sun Mar 19, 2006 1:57 pm
Location: Germany

Post by Kaeru Gaman »

Rescator wrote:PS! The fastest Max and Min is still to simply do a
If a>b
Endif

as part of your code/loop directly.
and much faster than the macros as well, see the last example in first post ;)
It is also type independent, and probably close to what native PB Max and Min performance would be.
yap, sure thing.

for those (like me) who learned programming without hundreds of higher functions like Max(),
it's still the sure and fast side to thing in the old greater-or-smaller combined conditions... ;)
oh... and have a nice day.
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Post by Rescator »

Updated the first post again.
Kaeru Gaman wrote:
Rescator wrote:PS! The fastest Max and Min is still to simply do a
If a>b
Endif

as part of your code/loop directly.
for those (like me) who learned programming without hundreds of higher functions like Max(),
it's still the sure and fast side to thing in the old greater-or-smaller combined conditions... ;)
Well, you might like Max5() and Min5() for general use and flexibility though :)
  • The new Max5() and Min5() works with any number,
  • are almost as fast as the Max3() and Min3() macros,
  • are as fast as the Max() and Min() macros,
  • are faster than the Max4() and Min4() procedures for floats.
  • Can be used anywhere a procedure or function or commands can be used.
Basically Max5() and Min5() would be my advice as to which to use,
unless someone find a way to optimize those two obviously.

Please also note that the test loops test BOTH Max and Min variants,
so divide the result by two to see how fast each of them are.

(IDE Menu>Compile with debugger)
Max5 and Min5 integer long = 1142 secs to do 100 million loops.
Max5 and Min5 float single = 2333 secs to do 100 million loops.
Max5 and Min5 float double = 2246 secs to do 100 million loops.

(IDE Menu>Compile without debugger)
Max5 and Min5 integer long = 1143 secs to do 100 million loops.
Max5 and Min5 float single = 2301 secs to do 100 million loops.
Max5 and Min5 float double = 2320 secs to do 100 million loops.

Yes odd difference I know, probably a timing/debugger overhead thing.
The numbers are pretty consistent on multiple runs though.
And again, these are tight two line loops going 100 million times.

I'm impressed by PureBasic speed, it annoyed me my earlier macros did not handle integers and floats.
And the procedure overhead was annoying. (overhead of around 1000ms per 100 million loops?)

I'm just really happy a multipurpose Max and Min is finally available with decent speed.

Obviously using if's inside speed critical loops is still advised, and that view will never change :)
michaeled314
Enthusiast
Enthusiast
Posts: 340
Joined: Tue Apr 24, 2007 11:14 pm

Post by michaeled314 »

Trond wrote:
Macro MinFast(a,b,Result)
!MOV Eax,dword[v_#a]
!MOV Ecx,dword[v_#b]
!CMP Eax,Ecx
!cmovg Eax,Ecx
!MOV dword[v_#Result],Eax
EndMacro

MinFast(4, N, Result)
Nice, but it doesn't work.
Try erasing the # signs
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Post by Rescator »

First of all these asm macros are not as flexible as the macros and procedures in the first post.
Also, did anyone test these before posting?
I'm not sure it's possible to handle both direct numbers and var and pass them to/from asm in PB macros, it also creates a dependency in that you must be sure to either pass numbers only or vars only, talk about clunky :P
Electric Sheep
New User
New User
Posts: 4
Joined: Mon Oct 10, 2011 2:26 pm

Re: Max() and Min(), procedure and macro variants.

Post by Electric Sheep »

I've been using the macros min5and max5 and just recently found that when they compare equal numbers they add them together.

You can fix this by using <= and >= in the second half of the macros...

Code: Select all

Macro Max5(a,b)
(((Not a>b)*b)+((Not a<=b)*a))
EndMacro

Macro Min5(a,b)
(((Not a<b)*b)+((Not a>=b)*a))
EndMacro
User avatar
Michael Vogel
Addict
Addict
Posts: 2806
Joined: Thu Feb 09, 2006 11:27 pm
Contact:

Re: Max() and Min(), procedure and macro variants.

Post by Michael Vogel »

I would be happy, if fast and flexible (integer) functions would be available, but this will never happen without the help of fred :?

I have just tried to tune one of my programs showing 3D tracks using Min3/Max3 and the results were horrible:
1) the graphics was completely distorted, because lines like m=Min3(x2,ScreenX-1) causing wrong results
2) changing the macros to something like (((Not (a)>=(b))-1)&(b))|(((Not (b)>=(a))-1)&(a)) repaired the graphic output, but the resulting macros are much slower than procedures (!) for min and max

Code: Select all

Procedure.l Min(a.l, b.l)
	!mov eax,dword[p.v_a]
	!mov ecx,dword[p.v_b]
	!cmp eax,ecx
	!jl @f
	!mov eax,ecx
	!@@:
	ProcedureReturn
EndProcedure
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3942
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Max() and Min(), procedure and macro variants.

Post by wilbert »

Procedures aren't always slow.
It's very logical that a simple procedure is faster than a complex macro.
If you need to process more values, for example both the min of x and y coordinates, you could speed things up by doing it with one procedure.
WilliamL
Addict
Addict
Posts: 1252
Joined: Mon Aug 04, 2008 10:56 pm
Location: Seattle, USA

Re: Max() and Min(), procedure and macro variants.

Post by WilliamL »

I get this error with 5.10 (now that we have Bool):
Line 78: Boolean comparisons can only be used with
test operators like If, While, Repeat. Use Bool()
instead.
MacBook Pro-M1 (2021), Sequoia 15.4, PB 6.20
Post Reply