Java-Gaming.org Hi !
Featured games (91)
games approved by the League of Dukes
Games in Showcase (804)
Games in Android Showcase (239)
games submitted by our members
Games in WIP (868)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1] 2
  ignore  |  Print  
  Best Practices for Creating New Objects  (Read 17778 times)
0 Members and 1 Guest are viewing this topic.
Offline unlight
« Posted 2015-07-21 07:24:19 »

Hi Guys,

I use a typical "Vector2f" type class for storing my entities positions, velocities etc and had a question about best practices when it comes to reassigning the objects. In a game loop which runs at 60 fps, I am updating my cameras position using my players position..

1  
camera.setPosition(player.getPosition());


The player.getPosition is returning a Vector2f object and my camera class is then reassigning its position as a new object..

1  
2  
3  
public void setPosition(Vector2f pos) {
    this.position = new Vector2f(pos);
}


If this is happening 60 times per second, am I generating 60 obsolete objects which will require garbage collection at some stage? If this is the case, should I be getting into the habit of assigning the new position as shown below?

1  
2  
3  
4  
public void setPosition(Vector2f pos) {
    this.position.x = pos.x;
    this.position.y = pos.y;
}


I doubt this will effect performance in any noticeable way but I would like to make sure that I am developing good habits as I go with programming.

Cheers guys.
Offline princec

« JGO Spiffy Duke »


Medals: 1146
Projects: 3
Exp: 20 years


Eh? Who? What? ... Me?


« Reply #1 - Posted 2015-07-21 07:50:40 »

Generally speaking... it will improve performance (measurably) if you consistently apply it just about everywhere.

The important thing though is not to go refactoring all your code to do this until it actually proves to be some sort of performance issue. 60 objects a second is absolutely nothing in the grand scheme of things. Even 600 is nothing. Or 6,000. Maybe at 60,000 you might start to see some issues.

Cas Smiley

Offline PlainBug
« Reply #2 - Posted 2015-07-21 07:53:45 »

Hi,

This definitely is the better solution:


1  
2  
3  
4  
public void setPosition(Vector2f pos) {
    this.position.x = pos.x;
    this.position.y = pos.y;
}


I doubt this will effect performance in any noticeable way but I would like to make sure that I am developing good habits as I go with programming.

Yes, in this case you won't notice any performance-effect, but in greater games where this happens about 200 times per frame you will probably notice performance-problems  Smiley

Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline klaus
« Reply #3 - Posted 2015-07-21 07:54:35 »

You can use object pools for this kind of reusable objects.  Pointing
Offline princec

« JGO Spiffy Duke »


Medals: 1146
Projects: 3
Exp: 20 years


Eh? Who? What? ... Me?


« Reply #4 - Posted 2015-07-21 08:05:23 »

Another generalisation here but I'd avoid object pools unless your objects are expensive to construct.

Cas Smiley

Offline orangepascal
« Reply #5 - Posted 2015-07-21 08:54:10 »

I always use object-pools.. left-over from my J2ME days Wink

the guy behind Orangepixel | twitter@orangepascal
http://www.orangepixel.net
Offline SHC
« Reply #6 - Posted 2015-07-21 09:19:20 »

For simple things like vectors and matrices, I keep a reusable stack (similar to object pool). Then whenever I need a temporary object I pop the objects from it (if the stack is empty, I create the instance). Once the work with that object is done, I push it back into that stack.

Offline unlight
« Reply #7 - Posted 2015-07-21 11:16:50 »

Thank you for the response there guys, that puts it into perspective for me!
Offline KevinWorkman

« JGO Plugged Duke »


Medals: 288
Projects: 12
Exp: 12 years


HappyCoding.io - Coding Tutorials!


« Reply #8 - Posted 2015-07-21 13:08:51 »

The short answer: Don't worry about premature optimization. If you aren't noticing any performance hits, don't go looking to fix stuff that ain't broke. (What princec said.)

The longer answer:

If this is happening 60 times per second, am I generating 60 obsolete objects which will require garbage collection at some stage?

Yup.

And although you shouldn't worry about premature optimization, this is indeed one obvious area for improvement. Garbage collection doesn't really matter on a modern PC- you'd be shocked to see how many Objects are being created behind the scenes by whatever library you're using and Java itself. But it **can** matter in places like Android development.


If this is the case, should I be getting into the habit of assigning the new position as shown below?

1  
2  
3  
4  
public void setPosition(Vector2f pos) {
    this.position.x = pos.x;
    this.position.y = pos.y;
}


That's certainly one approach. Another approach would be to make your Vector2f class immutable so you don't have to worry about copying them (but that might end up creating even more Objects, if you have a bunch of mutator functions).

I doubt this will effect performance in any noticeable way but I would like to make sure that I am developing good habits as I go with programming.

Do some profiling. Find out how many Objects are being created behind the scenes, and what percentage of those are yours. Find out how much time your code is spending on creating and garbage collecting those objects. That's the only way to actually answer this question.

HappyCoding.io - Coding Tutorials!
Happy Coding forum - Come say hello!
Offline princec

« JGO Spiffy Duke »


Medals: 1146
Projects: 3
Exp: 20 years


Eh? Who? What? ... Me?


« Reply #9 - Posted 2015-07-21 14:19:28 »

As a minor case study in "behind the scenes":

working on Battledroid last year I was having a few frame rate issues (30fps - yuk).

Now I'd written all my code to that point in the nicest possible style, and the particular case that interested me was this construct:
1  
for (Entity<?> e : entities) { .... }

where entities is a List of some sort (and in fact, almost always an ArrayList).

I was doing this kind of thing so often, and in so many places, every single frame, that I was creating tens of thousands of iterators a second, and this was firstly actually slightly slower than your traditional for loop, and secondly it created tends of thousands of objects in garbage, which caused a noticeable judder every few seconds when they all got collected.

So I replaced them all with the usual:
1  
2  
n = entities.size();
for (int i = 0; i < n; i ++) { Entity<?> e = entities.get(i); ... }


... and this alone was enough to stop the juddering and mostly get my framerates back to 60fps (with vsync on it only takes a tiny amount of time over 17ms to suddenly halve your framerates, see). So a small change with a surprisingly large impact.

Now, why this is relevant is because for a number of years I'd been labouring under the impression that Hotspot was being smart enough to allocate those iterators on the stack with escape analysis and so they'd never create any garbage; and furthermore that they'd be pretty efficient too. Well, it turns out after a bit of profiling, nope.

Cas Smiley

Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline klaus
« Reply #10 - Posted 2015-07-21 14:55:12 »

Now, why this is relevant is because for a number of years I'd been labouring under the impression that Hotspot was being smart enough to allocate those iterators on the stack with escape analysis and so they'd never create any garbage; and furthermore that they'd be pretty efficient too. Well, it turns out after a bit of profiling, nope.
Yep! I thought exactly the same and also saw tons of iterator objects when profiling with visual vm... It's a pity though! You really shouldn't have to worry about this kind of optimizations.  Cranky
Offline Riven
Administrator

« JGO Overlord »


Medals: 1371
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #11 - Posted 2015-07-21 15:15:21 »

No no no! (no...!) Pointing Kiss

The profiler enforces the creation of the Iterator instance, by injecting bytecode that prevents the stack allocation. When you disable the profiler you're (almost) guaranteed to not have the Iterator created. Maybe you made other optimizations at the same time that yielded your performance bump?

A better way to 'profile' memory usage when stack allocation is relevant, is by making heap dumps. But this is normally a Royal PITA.


http://psy-lob-saw.blogspot.com/2014/12/the-escape-of-arraylistiterator.html

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings!
Offline klaus
« Reply #12 - Posted 2015-07-21 15:20:49 »

Hehe, yeah, a PITA indeed  Grin

And yeah, I think you already mentioned that somewhere on the forum, right? I vaguely remember that now! Wink ok ok.. will keep that in mind! Thanks for your knowledge!  Cool
Offline princec

« JGO Spiffy Duke »


Medals: 1146
Projects: 3
Exp: 20 years


Eh? Who? What? ... Me?


« Reply #13 - Posted 2015-07-21 16:21:00 »

Well, the one thing I did measure correctly was the jump from 30fps back to 60fps and the removal of the periodic jitter: but I'll never be entirely sure now...

Cas Smiley

Offline Riven
Administrator

« JGO Overlord »


Medals: 1371
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #14 - Posted 2015-07-21 16:36:05 »

Maybe the jitter was due to the profiler always being enabled? Anyhoo, maybe you're right and Iterable instances are not reliably optimized away yet. I might do some quirky benchmarks to verify whether it works in non-trivial cases.

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings!
Offline philfrei
« Reply #15 - Posted 2015-07-21 20:02:42 »

Probably no difference between this:
1  
    for (int i = 0, n = entities.size(); i < n; i++)
and this:
1  
2  
    int n = entities.size();
    for (int i = 0; i < n; i++)

??

I can't remember what prompted the change any more, but I started using the former version several years ago. It seems to me there would be no particular advantage now, if there ever was any.

True/false?

music and music apps: http://adonax.com
Offline princec

« JGO Spiffy Duke »


Medals: 1146
Projects: 3
Exp: 20 years


Eh? Who? What? ... Me?


« Reply #16 - Posted 2015-07-21 20:05:23 »

There's no difference in it no, except that n is nicely tucked away in the scope of the for loop in the one-line version, if you've no other use for it later.

Cas Smiley

Offline basil_

« JGO Bitwise Duke »


Medals: 418
Exp: 13 years



« Reply #17 - Posted 2015-07-21 21:05:17 »

if you go bonkers it makes a difference. in that loop
entities.size()
is called every iteration - which costs the method call (!).

it's also better to iterate backwards :
for( int i = entities.size(); i-- != 0; )
is 0.000000001% faster cos testing
!= zero
is cheaper then testing
< some value
- but wont be "unrolled" if that's available anyway.

what i learned about
new
is what princec said. using an object pool is only good if the ctor is more expensive than fetching an object from an array which is pretty expensive acutally.

in the end, if all the vm-magic kicks in and things get replaced on-stack it doesn't matter - on the other hand, this is not really "stable" and can still create "spikes", not to mention the "warm start". if that's an issue the best way to treat objects (at least for me) is not to have any. just use memory.
Offline princec

« JGO Spiffy Duke »


Medals: 1146
Projects: 3
Exp: 20 years


Eh? Who? What? ... Me?


« Reply #18 - Posted 2015-07-21 21:40:03 »

 I think you misread his loop there - n is only initialised at loop start.

Cas Smiley

Offline Riven
Administrator

« JGO Overlord »


Medals: 1371
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #19 - Posted 2015-07-21 21:40:48 »

if you go bonkers it makes a difference. in that loop
entities.size()
is called every iteration - which costs the method call (!).
Re-read what philfrei posted and you'll see the only difference is the scope of the 'n' variable Pointing In C this matters, in Java it does not.

it's also better to iterate backwards :
for( int i = entities.size(); i-- != 0; )
is 0.000000001% faster cos testing
!= zero
is cheaper then testing
< some value
- but wont be "unrolled" if that's available anyway.
If you inspect the native code HotSpot generates, then you'll see backwards-looping is actually not optimized as aggresively - let alone unrolling. Only iterate backwards if that make sense in the logic.

what i learned about
new
is what princec said. using an object pool is only good if the ctor is more expensive than fetching an object from an array which is pretty expensive acutally.
Object pooling by definition thwarts escape analysis. Stack allocation allows 'objects' to be treated as a series of local variables, which potentially means no memory I/O at all - let alone the levels of indirection (likely cache misses) for an array access + field access on retrieved object.

in the end, if all the vm-magic kicks in and things get replaced on-stack it doesn't matter - on the other hand, this is not really "stable" and can still create "spikes", not to mention the "warm start". if that's an issue the best way to treat objects (at least for me) is not to have any. just use memory.
That prevents the extreme performance wins of stack-allocation ('flattening objects into local variables'). I benchmarked this thoroughly when working on LibStruct, and doing 'raw memory access' (either through unsafe-pointers or primitive-array access) was not even in the ballpark of stack-allocation: raw memory access was 4-10x slower than letting HotSpot do its magic, even when ALL data was in L1.

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings!
Offline Sindisil

Junior Devvie


Medals: 4
Exp: 15 years


TANSTAAFL


« Reply #20 - Posted 2015-07-21 21:45:36 »

Quote
In C this matters, in Java it does not.

I'm curious -- what do you mean by that?

I'm thinking it matters in both languages - either way it constrains the scope of n to the loop.

Offline Riven
Administrator

« JGO Overlord »


Medals: 1371
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #21 - Posted 2015-07-21 21:49:41 »

In C, every declaration of a variable causes memory to be allocated on the stack.

So in the following case, there will be 2 stack-allocations (pointer bumps) for the two 'i' variables:
1  
2  
3  
4  
for(int i=0; i<n; i++) {
}
for(int i=0; i<n; i++) {
}

whereas in Java the localvar-slot is reused, as we can prove that for every possible case, the 'i' will not 'escape' its scope, as Java does not have pass-by-reference.



In C you typically see this pattern:
1  
2  
3  
4  
5  
6  
int i;

for(i=0; i<n; i++) {
}
for(i=0; i<n; i++) {
}

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings!
Offline Sindisil

Junior Devvie


Medals: 4
Exp: 15 years


TANSTAAFL


« Reply #22 - Posted 2015-07-22 03:59:24 »

I'm not sure what would make you think that.

For the code snippit in question, a C compiler is free to generate code as you describe, but it is also free to only bump the stack pointer to make room for one i, since only one is live at a time.

In fact, a C compiler is free to make no room at all on the stack (keeping "both" is in a register the whole time) since at no point is i's address taken.


Offline KaiHH

JGO Kernel


Medals: 796



« Reply #23 - Posted 2015-07-22 09:45:09 »

True. If we look at what GCC 4.9.2 outputs even in -O0, then we see it does not use the stack at all, but two different registers.
In -O1 it performs soooo much optimizations that the assembly code it produces is so insanely clever, that it's not funny anymore, really. Smiley
For an overly contrived example like this...
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
static int test(int n) {
  int ret = 0;
  for(int i = 0; i < n; i++) {
    ret++;
  }
  for(int i = 0; i < n; i++) {
    ret++;
  }
  return ret;
}
int main(int argc, char** argv) {
  return test(argc);
}

...I would not have dreamt about what clever tricks the compiler actually generates under -O1, which actually really resembles this C code:
1  
2  
3  
int main(int argc, char** argv) {
  return argc >= 0 ? argc + argc : 0;
}

and that even without using the ALU, but using the processor's LEA instruction to double argc!
This is amazing!
Offline BurntPizza

« JGO Bitwise Duke »


Medals: 486
Exp: 7 years



« Reply #24 - Posted 2015-07-22 19:06:26 »

I still get the two loops in -O1 on https://gcc.godbolt.org/, but -O2 cleans it all up.

Since I like thinking about these things, here's a possible transformation path (how 'clever' is it?):

Loop fusion:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
static int test(int n) {
  int ret = 0;
-  for(int i = 0; i < n; i++) {
-    ret++;
-  }
  for(int i = 0; i < n; i++) {
    ret++;
+   ret++;
  }
  return ret;
}


This will get picked up by SCCP if not other passes:

1  
2  
3  
4  
5  
6  
7  
8  
9  
static int test(int n) {
  int ret = 0;
  for(int i = 0; i < n; i++) {
-   ret++;
-   ret++;
+   ret += 2;
  }
  return ret;
}


Induction variable substitution:

1  
2  
3  
4  
5  
6  
7  
8  
static int test(int n) {
  int ret = 0;
  for(int i = 0; i < n; i++) {
-   ret += 2;
+   ret = 2 * (i + 1);
  }
  return ret;
}


Dead store elimination:

1  
2  
3  
4  
5  
6  
7  
8  
static int test(int n) {
-  int ret = 0;
-  for(int i = 0; i < n; i++) {
-   ret = 2 * (i + 1);
-  }
-  return ret;
+  return n >= 0 ? 2 * n : 0;
}


Strength reduction:

1  
2  
3  
4  
static int test(int n) {
-  return n >= 0 ? 2 * n : 0;
+  return n >= 0 ? n + n : 0;
}


Inlined into main: (this would actually probably be first thing that happens, but w/e)

1  
2  
3  
4  
int main(int argc, char** argv) {
-  return test(argc);
+  return argc >= 0 ? argc + argc : 0;
}


And then instruction selection comes up with this:

1  
2  
3  
4  
5  
6  
main:
    lea     eax, [rdi+rdi]
    test    edi, edi
    mov     edx, 0
    cmovle  eax, edx
    ret


LEA is chosen possibly because, unlike ADD, the destination register can be anything, not just the left operand.



Oh, yeah and don't do object pools for small objects, unless testing shows real improvement, etc etc...  persecutioncomplex
Offline KaiHH

JGO Kernel


Medals: 796



« Reply #25 - Posted 2015-07-22 19:13:15 »

I still get the two loops in -O1 on https://gcc.godbolt.org/, but -O2 cleans it all up.
Yes, you are right. It was -O2 with me, too.

(ah, you edited your post)
Hehe. Sneaky, yes. Smiley

And yes, GCC has to put in the guard that n actually was positive when the original loop was entered, otherwise we would get a negative optimized result.

Offline BurntPizza

« JGO Bitwise Duke »


Medals: 486
Exp: 7 years



« Reply #26 - Posted 2015-07-22 19:16:46 »

Derp, you're very right. Edited.
Offline Oskuro

JGO Ninja


Medals: 77
Exp: 10 years


Coding in Style


« Reply #27 - Posted 2015-08-06 13:23:19 »

The short answer: Don't worry about premature optimization. If you aren't noticing any performance hits, don't go looking to fix stuff that ain't broke. (What princec said.)

Although this is very true, I think there's some value in training yourself to use more optimal patterns from the get go.

Offline KevinWorkman

« JGO Plugged Duke »


Medals: 288
Projects: 12
Exp: 12 years


HappyCoding.io - Coding Tutorials!


« Reply #28 - Posted 2015-08-06 13:30:50 »

Although this is very true, I think there's some value in training yourself to use more optimal patterns from the get go.

I don't disagree with you, but as this thread shows, it's not always obvious what "more optimal" means. Trying to outsmart the JIT compiler is often a bad idea.

My main point is that it's a waste of time for novices to worry too much about "efficiency" before they really understand how the basics work. Writing better code comes with practice.

HappyCoding.io - Coding Tutorials!
Happy Coding forum - Come say hello!
Offline princec

« JGO Spiffy Duke »


Medals: 1146
Projects: 3
Exp: 20 years


Eh? Who? What? ... Me?


« Reply #29 - Posted 2015-08-06 14:11:21 »

"Optimal" requires that you know what you're optimising for of course. If you're optimising for rapid coding time and brevity... you'd not be in the least concerned by pooling for example.

Cas Smiley

Pages: [1] 2
  ignore  |  Print  
 
 

 
Riven (579 views)
2019-09-04 15:33:17

hadezbladez (5501 views)
2018-11-16 13:46:03

hadezbladez (2398 views)
2018-11-16 13:41:33

hadezbladez (5761 views)
2018-11-16 13:35:35

hadezbladez (1220 views)
2018-11-16 13:32:03

EgonOlsen (4660 views)
2018-06-10 19:43:48

EgonOlsen (5681 views)
2018-06-10 19:43:44

EgonOlsen (3196 views)
2018-06-10 19:43:20

DesertCoockie (4094 views)
2018-05-13 18:23:11

nelsongames (5113 views)
2018-04-24 18:15:36
A NON-ideal modular configuration for Eclipse with JavaFX
by philfrei
2019-12-19 19:35:12

Java Gaming Resources
by philfrei
2019-05-14 16:15:13

Deployment and Packaging
by philfrei
2019-05-08 15:15:36

Deployment and Packaging
by philfrei
2019-05-08 15:13:34

Deployment and Packaging
by philfrei
2019-02-17 20:25:53

Deployment and Packaging
by mudlee
2018-08-22 18:09:50

Java Gaming Resources
by gouessej
2018-08-22 08:19:41

Deployment and Packaging
by gouessej
2018-08-22 08:04:08
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!