Sure, there are plenty of tips discussing how to use sequences in JavaFX, but the one I wanted to cover quickly today is regarding the fact that sequences are immutable. That is, once you create them, their content can’t change.
‘You lie!‘ I hear you all shout, running off to your IDE’s to show me the following fully legal and compiler-friendly code:
[jfx]
var seq = [ 1, 2, 3 ];
insert 4 into seq;
insert 5 into seq;
delete 1 from seq;
println(seq);
[/jfx]
If you run this code, the output will be [ 2, 3, 4, 5 ]
, as expected. It’s understandable if you think the sequence is being updated in-place – it certainly looks that way from where we’re all standing – except that under the hood it isn’t, and there are performance implications we need to be clear on. To get straight to the point, the insert and delete instructions create a brand new sequence instance, but fortunately they also ensure that the seq variable always refers to the new instance, quietly leaving the old instance to get garbage collected.
Of course, as I just noted, from a developers perspective we can treat seq as a reference to a sequence instance and be oblivious to these ‘behind-the-scenes’ sequence shenanigans. There is nothing wrong with this, but it’s important to know that creating sequences isn’t free, and certainly minimising the amount of sequences we create is a smart idea.
To be slightly more technical, I should note that there are some exceptions to this ‘sequences are immutable’ rule when dealing with non-shared sequences of specific types, but I don’t want to muddy the point of this blog, which is….
Use the expression language features of JavaFX to reduce the number of sequence instances you need to create.
For example, you might have code that looks like the following:
[jfx]
var seq:Integer[];
for (i in [1..MAX]) {
insert i*i into seq;
}
[/jfx]
I hope, with this blog post firmly at the forefront of your mind, you can now see why perhaps this isn’t such a great idea: we’re creating MAX
sequence instances, one for each iteration of the for
loop – ouch. Importantly, this example isn’t far-fetched and made to prove a point: I just found a very similar example in my code the other day (which I promptly fixed up), and even found a few blog posts on this site and other sites that use this approach. What you actually should do is something like the following:
[jfx]
var seq:Integer[];
seq = for (i in [1..MAX]) {
i*i
}
[/jfx]
In this case rather than create MAX
instances of the sequence, we’re creating only one – the result of the for
expression. This is considerably more efficient, and can lead to huge performance gains, depending on what you’re doing. In my case it made the difference between my code being usable and being jittery. Obviously in my case this was a very critical for
loop, and it may be similar in your use cases also.
I hope that this helps you eek out a little more performance from your apps. Now, go forth and optimise your sequences 🙂
Yes, I often advice to use the second form of the loop, for performance reasons, and because it is a more javafxish idiom (the first form is more javaish…).
If the loop is to insert new elements continuously in an existing sequence, it is better to do it in two times: create a sequence of new items, insert the sequence in the target one. And so on.
Another thing to watch out for.
Sequence is NOT an array.
So you should not use code for array manipulation with sequences.
This is especially true in a loop where you are deleting items from a sequence, the index might not point to the itme you have in mind after an item in the middle gets deleted.
Yes, this is correct. In this case a sequence is more like an ArrayList in Java, rather than an array.
So what happens, when binding the sequence comprehension from above and then changing the MAX value?
I think a new sequence instance is created too, because trying this several times leads to massive memory consumption.
Is this avoidable somehow?
Lichti,
Yes, if the sequence comprehension is bound, and then MAX is changed, the sequence comprehension is recomputed, which means that the loop is run again.
In the second code snippet, this will lead to a bunch of sequences being created and then GC’d.
In the third code snippet, this will not lead to massive memory consumption as only one new sequence is created.
Therefore, yes, it’s avoidable if you take the advice given in this post, and use the last code snippet, rather than the second one.