Time cost of simple function/method calls

BlitzMax Forums/BlitzMax Programming/Time cost of simple function/method calls

plash(Posted 2009) [#1]
If I do this,
Type TMyType
	
	Field somefield:Int
		
		Method GetSomeField()
			
			Return somefield
			
		End Method
		
		Method DoStuff()
			
			Print(Pi * GetSomeField() + " " + GetSomeField())
			
		End Method
		
		
End Type


compared to this,
		Method DoStuff()
			
			Print(Pi * somefield + " " + somefield)
			
		End Method

(on a massive scale, and not just these types of operations) will there be a large slowdown?


GfK(Posted 2009) [#2]
Try it?

Just time 100,000 iterations of both.

[edit]
I get about 5ms difference over 1,000,000 iterations. Sometimes the first test is faster, sometimes the second. So, no difference worth worrying about.

Global t:tMyType = New tMyType
Global n:Int
Global s:Int

s = MilliSecs()
For n = 1 To 1000000
   t.DoStuff()
Next
s = MilliSecs() - s
Print "First test: " + s + "ms"

s = MilliSecs()
For n = 1 To 1000000
   t.DoStuff2()
Next
s = MilliSecs() - s
Print "Second test: " + s + "ms"
End



Type TMyType
	
	Field somefield:Int
		
		Method GetSomeField()
			
			Return somefield
			
		End Method
		
		Method DoStuff:String()
			
			Return (Pi * GetSomeField() + " " + GetSomeField())
			
		End Method
		
		
		Method DoStuff2:String()
			
			Return (Pi * somefield + " " + somefield)
			
		End Method
End Type




GW(Posted 2009) [#3]
Your calling the same method twice..
Also, because the field value is 0 its very fast
if the default field value is something else is much slower.


GW(Posted 2009) [#4]
I thought that using 'final' should speed things up just a bit.
but it actually slows it down a little. Strange....

change the default value of 'Somefield' and see the difference..

SuperStrict

Global t:tMyType = New tMyType
Global t2:tMytype2 = New tMytype2

Global n:Int
Global s:Int

s = MilliSecs()
For n = 1 To 1000000
   t.DoStuff()
Next
s = MilliSecs() - s
Print "First test: " + s + "ms"

s = MilliSecs()
For n = 1 To 1000000
   t.DoStuff2()
Next
s = MilliSecs() - s
Print "Second test: " + s + "ms"
'End

Print "Final"

s = MilliSecs()
For n = 1 To 1000000
   t2.DoStuff()
Next
s = MilliSecs() - s
Print "First test: " + s + "ms"

s = MilliSecs()
For n = 1 To 1000000
   t2.DoStuff2()
Next
s = MilliSecs() - s
Print "Second test: " + s + "ms"
End




Type TMyType
	
	Field somefield:Int '= 1
		
		Method GetSomeField%()
			
			Return somefield 
			
		End Method
		
		Method DoStuff:String()
			
			Return (Pi * GetSomeField() + " " + GetSomeField())
			
		End Method
		
		
		Method DoStuff2:String()
			
			Return (Pi * somefield + " " + somefield)
			
		End Method
End Type



Type TMyType2 Final
	
	Field somefield:Int '= 1
		
		Method GetSomeField%() Final
			
			Return somefield 
			
		End Method
		
		Method DoStuff:String() Final
			
			Return (Pi * GetSomeField() + " " + GetSomeField())
			
		End Method
		
		
		Method DoStuff2:String() Final
			
			Return (Pi * somefield + " " + somefield)
			
		End Method
End Type



Otus(Posted 2009) [#5]
I thought that using 'final' should speed things up just a bit.
but it actually slows it down a little. Strange....


All Blitz methods are what in C++ would be called 'virtual' - they use dynamic binding at runtime. The assembly code emitted for obj.DoStuff() is the same regardless of whether the method is abstract, final, extended in another type or a combination of those.

If you see a slowdown, it's probably because the GC starts collecting garbage from the first test during the second. Always use GCCollect between tests when profiling!

Edit: Oh, as for the actual question... if the function calling GetSomeField does anything remotely complicated, the overhead from a method call is probably insignificant. Eg. each string concatenation amounts to way more cycles than a method call. Still, why not call it once and store it to a local variable...


plash(Posted 2009) [#6]
Edit: Oh, as for the actual question... if the function calling GetSomeField does anything remotely complicated, the overhead from a method call is probably insignificant. Eg. each string concatenation amounts to way more cycles than a method call. Still, why not call it once and store it to a local variable...
Just what I was worrying over, if your going to have the method to get and set fields/properties, and it is faster to not use them so often, why bother localizing the field? Won't that just add to the memory? In that case it would make more sense to just access the fields directly.

The only reason I prefer using methods for get/set is because in the future it might be necessary to do some operations before setting/getting the given field, or if things need to be changed internally with the field, the code already in place usually doesn't need to be changed.

Your calling the same method twice..
Also, because the field value is 0 its very fast
if the default field value is something else is much slower.
Like I said, that isn't exactly what I'm doing.. Sometimes fields need operations/post operations when you get/set them (which you would obviously have to use), sometimes they just return or set the field.

Here are some of the methods in one of the duct modules (not committed yet)
Method GetCharCount:Int()
	
	Return GetText().Length
	
End Method
Method TextDelete()
	
	If GetCursorPosition() < GetCharCount()
		
		RemoveText(GetCursorPosition(), GetCursorPosition() + 1)
		
	End If
	
End Method
Method InsertTextAtIndex(_text:String, _index:Int, _setpos:Int = True)
	
	SetText(GetText()[0.._index] + _text + GetText()[_index..GetCharCount()], False)
	If _setpos = True Then SetCursorPosition(_index + _text.Length)
	
End Method

etc.

EDIT: And not only for text operations and the such, I have positional fields that mostly need to be set with operations, thus I use the given field a lot in the set/update/refresh method for the positional field.


GW(Posted 2009) [#7]
Thanks for clearing that up Otus


Otus(Posted 2009) [#8]
Just what I was worrying over, if your going to have the method to get and set fields/properties, and it is faster to not use them so often, why bother localizing the field? Won't that just add to the memory? In that case it would make more sense to just access the fields directly.


Local variables do not take memory. They are converted by the compiler to register accesses, which are fast. Only if you have like 10+ locals in a function, some may take actual memory, but even then there is probably no faster way to accomplish the same.

The only reason I prefer using methods for get/set is because in the future it might be necessary to do some operations before setting/getting the given field, or if things need to be changed internally with the field, the code already in place usually doesn't need to be changed.


That's a very good reason for giving a get/set interface to external code. Remember, premature optimization is the root of all evil. Do what seems cleanest and most convenient; worry about efficiency in the unlikely case that it ever becomes a problem.


Koriolis(Posted 2009) [#9]
All Blitz methods are what in C++ would be called 'virtual' - they use dynamic binding at runtime. The assembly code emitted for obj.DoStuff() is the same regardless of whether the method is abstract, final, extended in another type or a combination of those.
Actually GW is right : final methods can be compiled more efficiently in some situations. In his example, the instruction "t2.DoStuff" calls a final method, thus the compiler knows for a fact that if t2 actually point to an instance of a type derived from tMyType2 (rather than to an instance tMyType2), then it doesn't changes anything as DoStuff cannot be overriden (so it can only be tMyType2.DoStuff that gets called). This means there is no need for a dynamic dispatch here, a direct call (as when calling a plain function) is sufficient.
Wether BlitzMAx does it or not is another story.

Local variables do not take memory.
Of course they do. There are a limited amount of registers (very little on intel CPUs) so that even if the compiler stores *some* local variables directly in registers there's always a point where it has to be stored on the stack (your saying 10, ok maybe). But the real point is that there is not just one function on the stack at one point. If your first function (with local variables stored in registers) is calling another function, where will it store these local variables while the second function is executing? Right, on the stack, so in the end the more local variables has the first function, the more it takes memory at one point.
And actually usually functions do not work that way, they do store local variables on the stack (only they might load their values in registers *too* at the beginning for speed purpose).

That's a very good reason for giving a get/set interface to external code. Remember, premature optimization is the root of all evil. Do what seems cleanest and most convenient; worry about efficiency in the unlikely case that it ever becomes a problem.

That's a very good advice in general. But getters and setters generally make the code much less readable - not cleaner - IMHO, unless the compiler provides some syntactic sugar for calling getters/setters as if you were accessign simple fields (ala C# properties). That's why it would be really nice if BlitzMax provided such a syntactic sugar : you get


Otus(Posted 2009) [#10]
Actually GW is right [...] Wether BlitzMAx does it or not is another story.

I didn't say he was wrong, specifically, just that Blitz wouldn't optimize them. Now, after having having looked at the assembly, I stand corrected. Blitz does optimize Final calls. Calling a final method (or a method of a final type?) is handled like a static function call.

Of course they do. There are a limited amount of registers (very little on intel CPUs) so that even if the compiler stores *some* local variables directly in registers there's always a point where it has to be stored on the stack (your saying 10, ok maybe). But the real point is that there is not just one function on the stack at one point. If your first function (with local variables stored in registers) is calling another function, where will it store these local variables while the second function is executing? Right, on the stack, so in the end the more local variables has the first function, the more it takes memory at one point.

Locals are only stored for as long as they are needed, so there usually aren't too many simultaneously for them not to fit in registers. The point you make about function calls is true, but that's not specific to Local variables: also intermediate values from expressions need to be stored. What I'm saying is, there's (usually) no need to minimize the number of Local variables per se. Stuffing everything into a large expression isn't going to make it any faster, as long as the algorithm is the same. On the other hand it will make reading the code a pain.

And actually usually functions do not work that way, they do store local variables on the stack (only they might load their values in registers *too* at the beginning for speed purpose).

That depends on the compiler, I guess, but I think a Local that's only needed in the next two lines will not be stored on the stack.


Brucey(Posted 2009) [#11]
I suppose it depends how many clock cycles you REALLY need to save?