GMS 2 About with() statement performance

that's a interesting question which I've asked myself several times but never got around to test.

the way you can test this is to:
1) get the current time.
2) run the code maybe 10,000 times.
3) get the current time again and check the difference in time.

do the same with the other code and you have the result!
just remember to run each test a few times to make sure the result is fairly consistent.
 

FrostyCat

Member
You should only use the second form, for reasons covered in the GMS 1.x version of the Manual (which in this case also applies to GMS 2).

The first form takes x from the first instance of oOBJ the runner could find, adds 1 to that and assigns the sum to the x in all instances of oOBJ. This is clearly incorrect behaviour, with the only exception being when there is one instance of oOBJ. And even then it is still awful form.

The second form runs through all instances of oOBJ and makes each of them increment their own x. This works all the time, regardless of how many instances of oOBJ there are.

I am not going to bother benchmarking this because the difference in speed likely negligible, and I don't want to encourage the micro-optimization movement that is in vogue around here. The first form's undesirable behaviour alone is enough for me to bring the gavel down on it right away.

One of the biggest mistakes you can make as a developer is valuing performance to the point that you neglect correctness.
 
oh yeah right. I asumed it was a regular variable with a reference to a instance but on further investegstion it seems to be a object reference.
 

TheouAegis

Member
The first one is faster because it was coded improperly. When actually done properly, they'd be nearly the same speed.

If oOBJ was a variable holding the id of an instance known to exist, then oOBJ.x+=1 (or even oOBJ.x++) would be faster.
 

CloseRange

Member
What you want to focus on is what works makes more sense in contex.

A with statement just iterates through every instance of oOBJ and performs that code on it.
I would half disagree with @FrostyCat because, though correct about how your code works, you also didn't code it properly (like TheouAegis said)
Code:
oOBJ.x = oOBJ.x + 1;
will cause every oOBJ's x position to be the EXACT same position (but increase by 1)
so Frosty was right, this code is wrong, however
Code:
oOBJ.x++;
this will make every oOBJ move to the right from where they are.
this is how you should have coded it, and it works.

case 1 is also how you would have to program in most other languages.
If you wanted to do a "with" in another language you'd have to make an array of the object you want, then use a for loop through that array and perform case 1.

with statement's are viable and used a lot because of just how convenient they are, but I stand by case 1 and use it all the time.
 

FrostyCat

Member
What you want to focus on is what works makes more sense in contex.

A with statement just iterates through every instance of oOBJ and performs that code on it.
I would half disagree with @FrostyCat because, though correct about how your code works, you also didn't code it properly (like TheouAegis said)
Code:
oOBJ.x = oOBJ.x + 1;
will cause every oOBJ's x position to be the EXACT same position (but increase by 1)
so Frosty was right, this code is wrong, however
Code:
oOBJ.x++;
this will make every oOBJ move to the right from where they are.
this is how you should have coded it, and it works.

case 1 is also how you would have to program in most other languages.
If you wanted to do a "with" in another language you'd have to make an array of the object you want, then use a for loop through that array and perform case 1.

with statement's are viable and used a lot because of just how convenient they are, but I stand by case 1 and use it all the time.
You clearly didn't test this, and you clearly don't know the difference between an instance and an object.

Open a new project in GMS 2, then create object0 with a sprite assigned to it and scatter several instances of it across the default first room. Then place any of the following in the object0's Step event:
Code:
object0.x++;
Code:
object0.x += 1;
Code:
object0.x = object0.x + 1;
Any of the above would produce the identical unwanted behaviour: The instances align in a vertical column and then move in unison across the screen, instead of moving according to their own x coordinates. This is conclusive proof that your advice is just plain wrong.

Also, case 1 is definitely NOT how you would usually program in other languages, unless you are dealing with a static variable in OOP or a global variable (for languages that do provide it or as part of a singleton pattern). If you're talking about an equivalent to something like this.x++; in GML, the actual equivalent is self.x++;, or more commonly just x++; in absence of a local scope vs. instance scope naming conflict. In GML, objects don't have variables, instances have variables.
 

Bentley

Member
If I coded in, obj_control:
Code:
obj_something.x += 1;
Wouldn't that just find one instance of obj_something. So there's no certainty unless there's one instance of obj_something? The same would be true if the above code was in obj_something's step event. It would find one instance of obj_something, not necessarily itself. I could be wrong. Just chiming in.
 

FrostyCat

Member
If I coded in, obj_control:
Code:
obj_something.x += 1;
Wouldn't that just find one instance of obj_something. So there's no certainty unless there's one instance of obj_something? The same would be true if the above code was in obj_something's step event. It would find one instance of obj_something, not necessarily itself. I could be wrong. Just chiming in.
Yes, you are absolutely wrong.

Open a new project in GMS 2. Create object0 with an assigned sprite and object1 without. Then in the default starting room, put in one instance of object1 and several scattered instances of object0. In object1's Step event, put in any of the following:
Code:
object0.x++;
Code:
object0.x += 1;
Code:
object0.x = object0.x + 1;
Each of the above produces the same kind of unwanted alignment behaviour shown in the variation from my previous post, but at a per-step rate of 1 instead of the number of object0 instances.

There is a reason why I advised readers in this article NEVER to reference or use instances by object ID when multiple instances of the object exist. The disproportionate prevalence of these kinds of misbeliefs is why I wrote it out in bold.
 

CloseRange

Member
Yes, you are absolutely wrong.

Open a new project in GMS 2. Create object0 with an assigned sprite and object1 without. Then in the default starting room, put in one instance of object1 and several scattered instances of object0. In object1's Step event, put in any of the following:
Code:
object0.x++;
Code:
object0.x += 1;
Code:
object0.x = object0.x + 1;
Each of the above produces the same kind of unwanted alignment behaviour shown in the variation from my previous post, but at a per-step rate of 1 instead of the number of object0 instances.

There is a reason why I advised readers in this article NEVER to reference or use instances by object ID when multiple instances of the object exist. The disproportionate prevalence of these kinds of misbeliefs is why I wrote it out in bold.
I suppose you're correct huh 10 years of gml still a lot to learn!
I do know the differance between instances and objects in gml, it's the same relasionship between classes and objects (except objects = classes and instances = objects)
I just always assumed that with all the other fancy things gml did it was smart enough to know I want it to iterate through the instances and not the whole object as if it was a static method.
 

Bentley

Member
Yes, you are absolutely wrong.

Open a new project in GMS 2. Create object0 with an assigned sprite and object1 without. Then in the default starting room, put in one instance of object1 and several scattered instances of object0. In object1's Step event, put in any of the following:
Code:
object0.x++;
Code:
object0.x += 1;
Code:
object0.x = object0.x + 1;
Each of the above produces the same kind of unwanted alignment behaviour shown in the variation from my previous post, but at a per-step rate of 1 instead of the number of object0 instances.

There is a reason why I advised readers in this article NEVER to reference or use instances by object ID when multiple instances of the object exist. The disproportionate prevalence of these kinds of misbeliefs is why I wrote it out in bold.
Huh, wow. I placed an obj_control in the room and in its step coded "obj_enemy.x++". I then placed a bunch of obj_enemy instances in the room, and lined them up vertically. They all moved in unison. I would have thought that only one instance would have moved.
 

TheouAegis

Member
Reading a variable reads from only one. Writing a variable writes to all. a.n++, a.n+=1, and a.n=a.n+1 are all read-write operations. They each read from one instance and write to all instances of that object.

Consequently, obj.x=obj.x sets the x of all instances of obj to the same coordinate.

Edit: actually that's not true. It does read from every single instance of that object. The problem is it must complete the read operation before it goes on to the write operation. So it is going to read from every single instance of that object, but each instance is going to overwrite the value of the previous instance.
 
Last edited:

icuurd12b42

TMC Founder
GMC Elder
Our tests which are dated a few years back as far as using with() using an INSTANCE ID to set a variable, for example, compared with using <dot>variable is that it is more efficient to use the <dot>variable method up to the point you need to set more than 3 variables. it's easy to test again I guess...

<later>

Well some drastic improvements has been made, seems to me the with() is much faster even when setting one value Hmmm.

Tested on 1.4 Runner only...

Code:
//object0 create code, have object0 in room and a empty object1 in room

num = 1000000;
theid = object1.id;
t = get_timer();
repeat(num)
{
theid.x = 10;

}
show_debug_message("theid.x=10 once " + string(get_timer()-t));

t = get_timer()
repeat(num)
{
with(theid)
{
x = 10;

}
}
show_debug_message("with(theid), x=10 once " + string(get_timer()-t));

t = get_timer()
repeat(num)
{
theid.x = 10;
theid.x = 10;
theid.x = 10;
theid.x = 10;
theid.x = 10;
theid.x = 10;
theid.x = 10;
theid.x = 10;
theid.x = 10;
theid.x = 10;
}
show_debug_message("theid.x=10 ten times " + string(get_timer()-t));


t = get_timer()
repeat(num)
{
with(theid)
{
x = 10;
x = 10;
x = 10;
x = 10;
x = 10;
x = 10;
x = 10;
x = 10;
x = 10;
x = 10;
}
}
show_debug_message("with(theid), x=10 ten times "+string(get_timer()-t));
Results:
Code:
theid.x=10 once             113549
with(theid), x=10 once       83761
theid.x=10 ten times        531116
with(theid), x=10 ten times 250860
 
Last edited:

jo-thijs

Member
Edit: actually that's not true. It does read from every single instance of that object. The problem is it must complete the read operation before it goes on to the write operation. So it is going to read from every single instance of that object, but each instance is going to overwrite the value of the previous instance.
What makes you say that?

I just tested it:
Code:
repeat K {
    instance_create_depth(random(room_width), random(room_height), random(1), object1);
}

var t = current_time, v = 1;

repeat 100000000 {
    v = object1.hi;
}

show_message(current_time - t);
with object1 an object with only a create event containing:
Code:
hi = 5;
and I got the following results:
Code:
For K = 1:
9918
9208
9774
9392
For K = 100000:
9265
9324
9432
9705
Notice how the times are independent from K.

For comparison, I also tested this:
Code:
repeat K {
    instance_create_depth(random(room_width), random(room_height), random(1), object1);
}

var t = current_time, v = 1;

repeat 100000000 {
    with object1 {
        v = hi;
    }
}

show_message(current_time - t);
which gave me the following results:
Code:
For K=1:
12003
11758
11906
12705
For K=10:
77617
77012
76266
78127
and I tested this:
Code:
repeat K {
    instance_create_depth(random(room_width), random(room_height), random(1), object1);
}

var t = current_time, v = 1;

repeat 100000000 {
    object1.hi = v;
}

show_message(current_time - t);
which gave me the following results:
Code:
For K=1:
9399
9641
9412
9447
For K=10:
23970
25374
24230
24693
Notice how in both cases the time hugely depends on K.

This behavior would not be there if GameMaker retrieved the values from each instance, throwing every unused one away.
Why would it even be programmed to do that?
 

xot

GMLscripter
GMC Elder
Your tests are showing something different, jo-thijs. In some cases you are iterating over every instance, in others you are just selecting the first instance found. Some of those comparisons are not informative.

However, the speed of the last, implied iterator object1.hi = v is interesting. I don't think it is intuitive that it sets instance variable hi for all instances of object1 but it does. What's needed is a comparable test using a with construct but it seems like it might be faster.
 
Last edited:

jo-thijs

Member
Your tests are showing something different, jo-thijs. In some cases you are iterating over every instance, in others you are just selecting the first instance found.
That was exactly what I was showing.
TheouAegis claimed this was not the case, that in all situations GameMaker iterates over all instances of the specified object.

However, the speed of the last, implied iterator object1.hi = v is interesting. I don't think it is intuitive that it sets instance variable hi for all instances of object1 but it does.
It has been this way for a while now (since some early version of GameMaker:Studio 1, I don't remember which).
Although, behavior differs between build targets (it has been this way for the windows target, but only a single variable gets updated for the HTML5 target).
It is documented in the manual:
https://docs.yoyogames.com/source/dadiospice/002_reference/001_gml language overview/401_05_addressing.html

What's needed is a comparable test using a with construct but it seems like it might be faster.
icuurd12b42 said he already did that test and that the with statement came out faster.
However, my results would suggest otherwise, so I performed the test with code:
Code:
repeat K {
    instance_create_depth(random(room_width), random(room_height), random(1), object1);
}

var t = current_time, v = 1;

repeat 100000000 {
    with object1 {
        hi = v;
    }
}

show_message(current_time - t);
and results:
Code:
For K=1:
11737
11570
11527
11554
For K=10:
75306
75017
74768
76222
This would suggest with-structures are indeed slower.
However, there is 1 big difference with icuurd12b42's code: I iterate over an object index as opposed to an instance index.
So, testing on that with code:
Code:
var i = instance_create_depth(random(room_width), random(room_height), random(1), object1);

var t = current_time, v = 1;

repeat 100000000 {
    i.hi = v;
}

show_message(current_time - t);
gives as result:
Code:
11412
11731
11454
11449
and with code:
Code:
var i = instance_create_depth(random(room_width), random(room_height), random(1), object1);

var t = current_time, v = 1;

repeat 100000000 {
    with i {
        hi = v;
    }
}

show_message(current_time - t);
gives as result:
Code:
12257
12173
12137
12176
So, that still doesn't confirms icuurd12b42's claim.
So I further substituted the occurence of variable v in my test code:
Code:
var i = instance_create_depth(random(room_width), random(room_height), random(1), object1);

var t = current_time;

repeat 100000000 {
    i.hi = 1;
}

show_message(current_time - t);
which gives:
Code:
10654
10680
10776
10632
and:
Code:
var i = instance_create_depth(random(room_width), random(room_height), random(1), object1);

var t = current_time;

repeat 100000000 {
    with i {
        hi = 1;
    }
}

show_message(current_time - t);
which gives:
Code:
11201
11035
11012
11103
So, still nothing to confirm icuurd12b42's claim, so I went ahead and tested his proposed code and got as results:
Code:
theid.x=10 once 117583
with(theid), x=10 once 121125
theid.x=10 ten times 762007
with(theid), x=10 ten times 353556
So it appears my results just simply are inconsistent with icuurd12b42's results.
I tested all of these on GM:S 2.1.4.218 on the windows' target.
 

xot

GMLscripter
GMC Elder
Sorry, I thought your response was to icuurd12b42. I did not read carefully enough.

As for inconsistencies, these kinds of "benchmarks" always need to be taken with a grain of salt. There are too many factors outside of the control of the tester, including YoYo's engine, the way the project is compiled, OS memory management, and the CPU's low-level caching.

I get comparable timings to icuurd12b42's when running under the VM. With the YYC, the differences minimized in the ten variable assignment test and in the case of using with() for a single variable assignment, it appears around 4x slower with the YYC than using dot notation. There seems to be some genuine overhead to initializing the construct but who really knows what the hell is going on.

Only the most massive differences are usually worth note. And in this case, FrostyCat gets to what's really salient about this question: the two cases are not the same thing for reasons that are a little obscure, and seeking efficiency through syntax will not yield much value compared to organizational or algorithmic changes.
 
Last edited:

TheouAegis

Member
Well i stand corrected. lol I didn't actually test it.

I think there may be some misunderstanding is going on and these last few posts. icuurd's test wasn't about if with was faster than dot notation, it was about if with is faster than dot notation when 10 variable operations are performed. His test shows that with is slower than dot notation when only one variable is operated on. When 10 variables are operated on, with is clearly faster; both of your tests verified that.

Dot notation presumes that the target instances already exist, with does not. This means that with performs a secondary instance_exists() call. That is why the with() is always slower for single variable operations: for each instance, the dot notation will load the instance pointer into a register and then write the variable immediately, but with() will perform an instace_exists() check first. With multiple variables, dot notation is going to load the instance pointer for each variable, whereas with() will only load it one time. This is why when dealing with one instance and multiple variables, with is actually faster. Once you throw in an instance_exists() call with dot notation, the speeds balance out.

So if you don't know for certain an instance will exist when the code is called, you should use with() instead of dotnotation. Even if you know the instance will exist, if you are writing to multiple variables, you should use with(). If you want to perform a read-write, you should use with() if there can be more than 1 instance.

BUT.....

You must use dot notation with a single instance id to write to a deactivated instance; with() won't let you operate on deactivated instances last I checked.
 

jo-thijs

Member
icuurd's test wasn't about if with was faster than dot notation, it was about if with is faster than dot notation when 10 variable operations are performed. His test shows that with is slower than dot notation when only one variable is operated on. When 10 variables are operated on, with is clearly faster; both of your tests verified that.
That's what he says before the <later>, but that was based on tests performed for earlier versions of GM
and afterwards he mentions that his tests claimed that with is faster in both situations.

Dot notation presumes that the target instances already exist, with does not. This means that with performs a secondary instance_exists() call. That is why the with() is always slower for single variable operations: for each instance, the dot notation will load the instance pointer into a register and then write the variable immediately, but with() will perform an instace_exists() check first. With multiple variables, dot notation is going to load the instance pointer for each variable, whereas with() will only load it one time. This is why when dealing with one instance and multiple variables, with is actually faster.
But wouldn't the dot notation also need to perform that test so it knows when to show an error message?

I was more thinking in the line of with-statements needing to update stuff like what self and other refer to, pushing the previous values for other on a stack
and iterations of a with-statement being executed separately from each other, allowing for less potential optimizations.

Anyway, we both agree that the with-statement being more performant for icuurd12b42 is kind of strange.

Once you throw in an instance_exists() call with dot notation, the speeds balance out.
Looking at my previous results, I don't think it will.

instance_exists should provide a constant ovehead and the overhead generated by the with-statement seems to grow stronger than that of the dot-notation.
 

icuurd12b42

TMC Founder
GMC Elder
Well i stand corrected. lol I didn't actually test it.
His test shows that with is slower than dot notation when only one variable is operated on. When 10 variables are operated on, with is clearly faster; both of your tests verified that.
Actually, I state that it was the case 2 years ago and the test I did last night deny this completely as
theID.x = 10;
is way slower than
with(theID) x = 10;

which left me perplexed...
 

jo-thijs

Member
Actually, I state that it was the case 2 years ago and the test I did last night deny this completely as
theID.x = 10;
is way slower than
with(theID) x = 10;

which left me perplexed...
I've ran your test multiple times and I never got that behavior though.
It might have something to do with your hardware / operating system / whatever.

Do you consistently get these results?
 

icuurd12b42

TMC Founder
GMC Elder
I've ran your test multiple times and I never got that behavior though.
It might have something to do with your hardware / operating system / whatever.

Do you consistently get these results?
Yep, xot did that too and get similar result to my tests 2 years back

Goes to say you cannot trust benchmarks on one single machine
 

EvanSki

Raccoon Jam Host
I hate with statements, I usually always run into an error and wonder why my variables arent transferring over to the new object or why my code just skips the with command and I end up bashing my head onto the keyboard to realize I spelt something wrong
 

TheouAegis

Member
But wouldn't the dot notation also need to perform that test so it knows when to show an error message?
Not quite. In the case of n.x, if there are no instances of n, you'll get an <unknown>.<unknown> error or a "no instances of object exist" error. At least on reads... It seems it just does its normal routines for reading and writing variables in the same vein as you'd get an error if you destroy or deactivate an instance then try to perform an operation on it. But with() explicitly performs an instance_exists() run on top of that though, as you will (probably) never get an error on a write op, only a read if the variable itself doesn't exist. And since instance_exists() ignores deactivated instances, with() doesn't allow read-write ops on deactivated instances. However, passing an object_index through n.x seems to ignore deactivated instances, so I don't know what is going on under the hood there. You would think if n.x ignores deactivation in one case, it'd ignore it in all.


(Disclaimer: deactivated instance testing was done on GMS1.4 a few months ago)
 

jo-thijs

Member
Not quite. In the case of n.x, if there are no instances of n, you'll get an <unknown>.<unknown> error or a "no instances of object exist" error. At least on reads... It seems it just does its normal routines for reading and writing variables in the same vein as you'd get an error if you destroy or deactivate an instance then try to perform an operation on it. But with() explicitly performs an instance_exists() run on top of that though, as you will (probably) never get an error on a write op, only a read if the variable itself doesn't exist. And since instance_exists() ignores deactivated instances, with() doesn't allow read-write ops on deactivated instances.
I don't see how that'd imply that no instance existence check is performed for the dot notation.
Despite what information the error is showing, a check needs to be performed to know if the error message should be displayed.

And since instance_exists() ignores deactivated instances, with() doesn't allow read-write ops on deactivated instances. However, passing an object_index through n.x seems to ignore deactivated instances, so I don't know what is going on under the hood there. You would think if n.x ignores deactivation in one case, it'd ignore it in all.
Oh ok, that would imply some additional instance existence check for the with-statement when provided with an instance id.

My guess would be that GameMaker keeps per object a structure containing all active instances in the room belonging to that object
and keeps an other structure that maps instance ids to their respective instances.

That would make it difficult for some_object_index.some_variable = some_value to also loop over all deactivated instances of the object,
whereas there is no issue for some_instance_id.some_variable = some_value to also include deactivated instances.
 

Neptune

Member
I find i rarely ever use 'with' on objects... Because it runs through all of the instances of the object.

However it is very powerful and useful, especially since you can run scripts as if the instance called them itself... Or execute events etc
 

kjagrpk908

Member
Thanks everyone.

I had mistake the question of first post. Of couse, I supposed that oOBJ is a single instance. (Situation : instance_number (oOBJ) = 1)
But I noticed that I didn't know right handling about object and instance. I have learnning a lot of things here.

There is a reason why I advised readers in this article NEVER to reference or use instances by object ID when multiple instances of the object exist. The disproportionate prevalence of these kinds of misbeliefs is why I wrote it out in bold.
As another case,
I almost used like "obj.x =" code. but now I noticed that I didn't use about "self.x =".
I still have bad way about using specific single instance(id) in multiple instances on a object.

About with() performance, I will test on my projects and I will use a lot of "with()" for right coding more than before.:)

https://forum.yoyogames.com/index.php?threads/whats-the-difference-objects-and-instances.29005/
 
Top