Performance Tips - 1

With this article I want to start a series of articles with tips that can help improve the performance of your apps when they are moved from Visual Objects and/or Vulcan to X#.

The first tip is to use the IS comparison in stead of IsInstanceOf()

Try the following code in a VO Console application:

FUNCTION start AS VOID 
LOCAL oErr AS OBJECT
LOCAL nI AS LONG
LOCAL f AS FLOAT
oErr := Error{}
f := Seconds()
nI := 0
FOR VAR nX := 1 TO 10_000_000
IF IsInstanceOf(oErr, #Error)
nI++
ENDIF
NEXT
? Seconds() - f, nI
f := Seconds()
FOR VAR nX := 1 TO 10_000_000
IF oErr IS Error
nI++
ENDIF
NEXT
? Seconds() - f, nI
WAIT

On my machine the first loop takes an average of 1,3 seconds, the second loop 0,02 seconds. Of course this is a ridiculous amount of type checks, but you will get the point. If you use IS instead of IsInstanceOf() everywhere in your code then you will see a (small) performance improvement. (Btw did you notice the number format 10_000_000 ?  We have added this format because this makes large numbers much easier to read).

Btw there is a second advantage

If you accidentally make a typo in your code (for example type #Eror in stead of #Error) then the first loop will compile and run happily, the second loop however will not compile because the compiler will check if your type exists at compile time. It will generate the error:

Error XS0246 The type or namespace name 'Eror' could not be found (are you missing a using directive or an assembly reference?) 

If you just make a typo in the first loop then you will see that it will compile and run but it will be much slower (> 4 seconds on my machine).
That is because the code in the runtime will now walk the whole class hierarchy for every object. Error inherits from Exception which inherits from Object. You can inspect the code at https://github.com/X-Sharp/XSharpPublic/blob/feature/Runtime/Runtime/XSharp.RT/Functions/OOP.prg#L842 to see what we are doing. You will also notice that we are not caching anything (we did in the past but that was causing a problem). If you look at that code you will also see that the function is typed as

FUNCTION IsInstanceOf(oObject AS OBJECT,symClassName AS STRING) AS LOGIC

You may wonder why this compiler at all, because we are calling the function with a symbol, but the parameter is typed as STRING. The reason for this is that we have added an automatic (implicit) conversion from Symbol to String in the runtime. So each time you are calling IsInstanceOf() the symbol will be converted to a string. So if you would change the code to:

IF IsInstanceOf(oErr, "Error")

it would be slightly faster (but not as fast as the IS check).

There is one situation where it is probably better to use IsInstanceOf():
If your code dynamically loads assemblies then the type may not be available at compile time.
Many (advanced) developers have done that, because statically linking a large number of DLLs could have a negative impact on the startup time of your app.
In .Net things work differently: it loads assemblies and code 'on demand'. That means that it is no longer a performance killer to have a large number of references in your application.

Finally

Did you know that we also support the following syntax

IF oErr IS Error VAR oTypedError
? oTypeError:Description
ENDIF

The Local Variable oTypedError will be guaranteed of the type Error, not NULL_OBJECT  and will only be available in the scope of the IF block.

I hope this helps

 


2 comments