Testing for empty arrays and clearing arrays

Monkey Forums/Monkey Programming/Testing for empty arrays and clearing arrays

Grey Alien(Posted 2014) [#1]
Hi all, I'd like to check that my understanding of the following points are correct, thanks!

Consider the following code:

Local test:TParticlePhase[]
If not test Then Print "Test is null"
Local phase:TParticlePhase = New TParticlePhase
test = test.Resize(test.Length + 1)
test[test.Length - 1] = phase
Print test.Length
If not test Then Print "Test is null"
test =[]
If not test Then Print "Test is null"
test = test.Resize(test.Length + 1)
test[test.Length - 1] = phase
Print test.Length


output is as expected:
Test is null
1
Test is null
1


I'm aware that an array is not an object, therefore you cannot test to see if it is null e.g.
if test = null then


However I discovered that you can use:
if test then

and you can also use:
if not test then

which has the same effect as:
if test.Length()=0 then


So how is that working then?

As for clearing an array I was using this to create a zero length slice
test = test [0..0]

but then I discovered other people were using
test = []

which seems to work fine in the code above.

Am I doing it right? Any feedback welcome. Thanks!


Gerry Quinn(Posted 2014) [#2]
I assume there's an implicit conversion of array to bool with a non-empty array returning true and an empty one returning false.

As for the second question:

Local a:Int[] = []			' fine
Local b:Int[] = a			'  ""
Local c:String[] = []			'  ""
Local d:String[] = c			'  ""
Local e:String[] = a			' nope


It will accept [] to initialise an empty array, but it is not actually creatijng [] and setting your array to that.

Both your methods are fine. Depending on optimisations, setting it to [] might be faster. Note that you cannot actually clear an array - whichever method you choose, you are creating a new zero-length array, and setting test to that.


Jesse(Posted 2014) [#3]
the problem is with the If statement
this should be valid but is not:
Local a:int[] = []
if not a then ' is ok
if a = []     ' not ok  <------ fine, I'll buy that.
If a = Null Then  ' not ok  <---------- but I don't think this should be an error. 



muddy_shoes(Posted 2014) [#4]
>So how is that working then?

Pretty much how you imagined. ""If arr" implicitly casts to Bool and if you look in the trans code the Bool cast for an array is:

If ArrayType( src ) Return Bra( t+".Length()!=0" )


The same thing occurs for strings.

>Am I doing it right?

There's nothing wrong with what you're doing on a line by line basis. Algorithmically, if you're blowing away large arrays of object references then you may well be risking GC performance issues though. Stylistically I'm not a big fan of the implicit Bool casting. I think it's unclear, and that lack of clarity produced this very thread, but it's valid Monkey and I do find myself lazily using it.


Grey Alien(Posted 2014) [#5]
Thanks all, very interesting.

@muddy_shoes: So you'd prefer if test.Length()=0 instead of the implicit Bool casting? I was wondering if using Length() might be slower (I'm using it a large number of times per frame as part of my particle system). Although I admit it probably won't make much difference (yes I could test it). As for killing off large arrays of object references, the only way round that would be to use object pooling (which I do for my particles and emitters) right?


muddy_shoes(Posted 2014) [#6]
So you'd prefer if test.Length()=0 instead of the implicit Bool casting? I was wondering if using Length() might be slower (I'm using it a large number of times per frame as part of my particle system).


Stylistically I'd prefer it because that's what it really means. In terms of performance the difference is somewhere from 0 to not a lot depending on platform. To be honest I'd have thought it would be zero for everything, seeing as it's functionally equivalent, but for some reason actual calls to the Array Length method go through different translation paths than the Bool cast on some targets*. A call to Length on Java and C# incurs a null test in the native code even though the array instance should never be null and uses library Array utility functions instead of the instance method. Not sure what that's about but it seems that using "If arr.Length() = 0" might actually be marginally slower with the way Monkey works currently (Would need testing though. It could all come out equal in the final compiled code.).

That said if you're performing this test often enough in a small enough loop for the difference between those two calls to make a difference then avoiding the calls altogether by tracking whether you've initialised the array in a separate boolean would seem to be the desirable option.

As for killing off large arrays of object references, the only way round that would be to use object pooling (which I do for my particles and emitters) right?


Right. You will also still have the GC impact of the array itself. I'd guess you're unlikely to be planning to instantiate and throw away enough large arrays to be a big deal but it's worth remembering.


Gerry Quinn(Posted 2014) [#7]
If you are concerned about the GC impact of the array, use a Stack instead.


muddy_shoes(Posted 2014) [#8]
Okay, I got curious enough to try a little test. I found that in XNA "If arr" is about 40% faster than "If arr.Length() <> 0" and on Android "If arr" is a fairly crazy 20+ times faster than "If arr.Length() <> 0". It looks like a completely avoidable problem in trans to me, but I may be missing something.

Anyway, the length check is taking ~1.5 microseconds on my crappy Android phone, which would add up to something tangible if you were doing thousands per frame. So, bonkers though it is, using "If arr" actually could make a performance difference.


consty(Posted 2014) [#9]
Hi there I did an experiment to find out what is going on.

I have this code:
Local test:TestClass[]
If Not test Then Print("test is Null")


Then I look at the cpp file that the transcompiler generated and the condition statement had been:
if(!((t_test).Length()!=0)){
	DBG_BLOCK();
	bbPrint(String(L"test is Null",12));
}


So then this means that if the condition would be to simplified would be
If test.Length = 0 Then Print("test is Null")


So thus transcompiler would do
if(t_test.Length()==0){
	DBG_BLOCK();
	bbPrint(String(L"test is Null",12));
}


Very interesting, isn't it?


Grey Alien(Posted 2014) [#10]
Yes very interesting. What does the faster (as discovered by muddy_shoes) "if test" compile into?

Does this all mean if would be faster to do this to test for an empty array? Seems weird but if it's faster I'll take it.

if test
 'do nothing
else
 'code that you wanted to do if the array is empty



muddy_shoes(Posted 2014) [#11]
The performance difference is that (in Java and C#) "if test" goes through a bool cast and gets converted to a direct reference to the underlying language array.length property. Calls to Monkey's Array.Length() result in a call to a function that has significant overhead, including an unnecessary null check.

Funnily enough, after I wrote the post about I remembered that I found this issue before and provided Mark with a fix in a pull request -- https://github.com/blitz-research/monkey/pull/5. As is his habit, he declined to take the code and said he'd do it manually. Then, as is often the result of choosing to step outside systems for tracking change, the change apparently got lost.


Grey Alien(Posted 2014) [#12]
Awwwwwwwww. Well thanks anyway.