Java-Gaming.org    
Featured games (81)
games approved by the League of Dukes
Games in Showcase (482)
Games in Android Showcase (110)
games submitted by our members
Games in WIP (550)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  Game gets slower  (Read 1880 times)
0 Members and 1 Guest are viewing this topic.
Offline skappler

Senior Newbie





« Posted 2013-01-25 10:47:03 »

Hey guys
I just finished the basic engine for my game, but the longer I play, the slower it gets. I checked if I create a huge amount of Objects somewhere that blocks the memory but I couldn't find any. Only some local Variables that should be removed by the GC after the method is finished. Could it be that the GC is too slow?
I have absolut no idea where the problem could be.

Are there some typical noob problems that cause such a behaviour?

Here is the github link if anybody wants to take a deeper look
https://github.com/skappler/BlockWorld

Any help is appreciated!
Offline sproingie

JGO Kernel


Medals: 202



« Reply #1 - Posted 2013-01-25 16:42:49 »

If you're getting intermittent performance spikes, it could be gc.  If it's steady performance degradation, then it's probably something else (though it could be a memory leak making gc eat 99% CPU). 

Run jvisualvm (it comes with the JDK) and attach it to your process.  Open the profiler tab and let it run for a while, and it'll tell you what's taking the most time.

Also look at the heap usage graphs.  If you see a sawtooth pattern in the heap graph, that's gc working as normal.  If it goes up and up and never goes down and the orange area gets thin, that's a memory leak that's over-stressing gc and causing your performance problems.
Offline DrewLols

Senior Member


Medals: 2
Projects: 1


Noob going through metamorphosis...


« Reply #2 - Posted 2013-01-25 17:52:18 »

One thing I've done in the past to locate a memory leak is to implement the finalize method of particularly large objects and have them print out their last words before they get gc'd.  This worked for me because every time I transitioned from room to room in my game, I expected the last room to be gc'd.  When one wasn't, I checked to see what could be hanging on to the room that was also connected to the root of the program.  Just kept deleting elements from the room until that finalize method was called.  I also forced the gc to run to get more immediate results.  I dunno if this will help you, but I just thought I might give you my two cents.

Did you know that 90% of statistics are wrong?
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline sproingie

JGO Kernel


Medals: 202



« Reply #3 - Posted 2013-01-25 18:00:20 »

By implementing a finalize method, you interfered with the normal gc process and delayed it by a generation.  I guess if you're requesting gc, then you compensated for it somewhat.  Still, the finalizer is not something you want to keep around in production code.  If you want to fire off an event when something is gc'd in its normal cycle, you should use a PhantomReference and register it on a ReferenceQueue.  With that approach, you can keep the same code in production, just turn off generating the phantomrefs.

If you do that, you'll probably want to subclass PhantomReference and stick an informative name on the instance when you create it, since there's no way of getting at the referent (otherwise it would be a Weak/SoftReference, and those delay gc the same as finalizers).

All of this is way too twisty for a beginner: I just recommend using the heap profiler if you see memory not being gc'd, then using the Mk.I Eyeball on the codebase for whatever it tells you is leaking.
 
Offline skappler

Senior Newbie





« Reply #4 - Posted 2013-01-25 18:03:04 »

Thank you very much. That was exactly what I was looking for.
The heap usage is going up, then the GC comes and it drops a little bit. Then it rises again. So the GC is working but after every run it's less then after the last.

The profiler says that there is a char[] that takes about 25% of the total memory used. That's weird, because I never use a char[].Also I get a huge amount of Object[]. I guess it's used internally by lwjgl or slick. Is there a way to find out where it's used?

@DrewLols
I never worked with the finalize method. In general, that's a good idea but in my case I don't have things like rooms that have to be removed. The whole map is created at the beginning and doesn't change at all.
Offline sproingie

JGO Kernel


Medals: 202



« Reply #5 - Posted 2013-01-25 18:11:13 »

char[] is usually the internal storage of String.  Permgen tends to have tons of them, but don't worry about those.  The heap profiler in jvisualvm doesn't always make that obvious, but if you take a heap snapshot, you can use the Eclipse Memory Analyzer Tool on it (it's a plugin or you can run it standalone) which gives you a much nicer view of things.  I'd try to get used to jvisualvm's output though, since MAT is not exactly fast.  I believe jvisualvm can give you some reference hierarchy details on a snapshot too ... it's been a while, I can't remember.

Object[] is typically the underlying storage of an ArrayList, and again something you need to find the containing object of to be meaningful.  Again, either use MAT or visualvm on a snapshot to chase up the reference hierarchy to find out who owns it.

Stick with the top few items.  Let it profile for a while, then go with the things that are much too large/numerous and don't worry about the rest.
Offline skappler

Senior Newbie





« Reply #6 - Posted 2013-01-25 18:48:21 »

This MAT is a neat little tool =)
It showed me that most of the memory goes to the storage of the chunks. That makes sense, because I have 64 of them. They take up 16MB of memory which isn't that much, right?
The char[] is the input queue of the keyboard.

Then again the weird thing is that it gets slower with the time. (In fact it gets slower, then when the gc runs, a little faster again)
How can I find out what exactly is removed by the gc?
Offline sproingie

JGO Kernel


Medals: 202



« Reply #7 - Posted 2013-01-25 18:58:35 »

Nothing's going to tell you exactly what got removed by gc when, but you can turn on verbose GC that tells you how much (and whether it was a full gc or not).  If you're steadily leaking memory, you trigger full gc's more often as the JVM searches for space, and it has a bigger heap to fruitlessly search every time, and eventually you get OOMEs as the gc thread times out looking for more space.  So if your memory usage graph doesn't eventually trend out to some steady baseline (I wish visualvm had trend lines), your performance will get worse and worse for those reasons.

BTW, profile memory after the program's run for a few minutes: the JIT takes up a fair chunk of ram itself, but it does get released eventually (you'll usually see the thread graph drop by 1 at that time).

Anyway, 16 megs of memory total is not a whole lot for chunks, and if that's not growing, I doubt that's your problem.  If chunk storage is your problem, it could be the way you're accessing them and have nothing to do with gc.
Offline skappler

Senior Newbie





« Reply #8 - Posted 2013-01-25 19:40:45 »

I made a heap dump closely before the memory peek and another directly after the gc and everything that got removed were some of those char[] and a few HashMap Entries.

All this is really weird. I know that 16MB is not much and I use a single array to store the data in the chunks, so I have constant time to acces the single Blocks inside the chunk. The chunks themself are stored in an ArrayList but there are only 64 of them so this shouldn't be too bad.

Thanks for your help! If anyone has another idea or took a look at the code and found something, please tell me!
Offline philfrei
« Reply #9 - Posted 2013-01-26 00:11:32 »

I'm wondering, is it purely a function of how long you play, or is it more about how far you advance into the game?

I'm going to be shallow and just speculate about things that cause slowups without looking at the code.

Some processes just scale badly. For example, if a brute force collision check is used, as the number of objects to be checked grows, the time required to process them will grow exponentially.

"Greetings my friends! We are all interested in the future, for that is where you and I are going to spend the rest of our lives!" -- The Amazing Criswell
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline skappler

Senior Newbie





« Reply #10 - Posted 2013-01-26 00:16:51 »

At the moment it's not a real game. It's only a world where I can walk around. And the longer I walk around, the slower everything gets.
The collision detection takes the Objects in the direct surrounding and only checks them. Afterwards the Objects are cleared again. So this should be the same every cycle too.
Offline Best Username Ever

Junior Member





« Reply #11 - Posted 2013-01-26 04:59:48 »

Maybe it's not memory related. Did you do execution time profiling or try printing the number of collision test per update?
Offline philfrei
« Reply #12 - Posted 2013-01-26 10:16:52 »

Quote
...this should be the same...

Maybe check it. I like Best's suggestion.

Your description suggests to me that "something" is growing exponentially while you walk around your world. Are you making a map of what your character sees, for example? (I see a reference to a labyrinth.) Maybe a long shot, but if there are algorithms that deal with a progressively larger map, that could cause an exponential growth.

"Greetings my friends! We are all interested in the future, for that is where you and I are going to spend the rest of our lives!" -- The Amazing Criswell
Offline skappler

Senior Newbie





« Reply #13 - Posted 2013-01-26 10:29:33 »

Well this is my collision code:
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
   public void checkCollision(){
     
      /*
       * -> get Blocks in question
       * -> check Collision for every block
       * -> if true set pos to oldPos
       *
       */

     
      if(currentChunk == null)
         return;
     
      //Get the player coordinates
     int x = (int)Math.floor(this.position.x);
      int y = (int)Math.floor(this.position.y);
      int z = (int)Math.floor(this.position.z);
      y--;
     
      // Get the blocks
     surrounding.add(currentChunk.getBlock(x-1, y+1, z-1));
      surrounding.add(currentChunk.getBlock(x-1, y+1, z));
      surrounding.add(currentChunk.getBlock(x-1, y+1, z+1));
      surrounding.add(currentChunk.getBlock(x, y+1, z-1));
      surrounding.add(currentChunk.getBlock(x, y+1, z));
      surrounding.add(currentChunk.getBlock(x, y+1, z+1));
      surrounding.add(currentChunk.getBlock(x+1, y+1, z-1));
      surrounding.add(currentChunk.getBlock(x+1, y+1, z));
      surrounding.add(currentChunk.getBlock(x+1, y+1, z+1));

      surrounding.add(currentChunk.getBlock(x-1, y+2, z-1));
      surrounding.add(currentChunk.getBlock(x-1, y+2, z));
      surrounding.add(currentChunk.getBlock(x-1, y+2, z+1));
      surrounding.add(currentChunk.getBlock(x, y+2, z-1));
      surrounding.add(currentChunk.getBlock(x, y+2, z));
      surrounding.add(currentChunk.getBlock(x, y+2, z+1));
      surrounding.add(currentChunk.getBlock(x+1, y+2, z-1));
      surrounding.add(currentChunk.getBlock(x+1, y+2, z));
      surrounding.add(currentChunk.getBlock(x+1, y+2, z+1));
     
      //Check collision for every block
     
      boolean collision = false;
     
      for(Block b : surrounding){
         if(b != null && this.getBounds().intersects(b.getBounds())){
            collision = true;
            break;
         }
      }
     
      surrounding.clear();
     
      //Reset position
     
      if(collision){
         this.position.x = oldPos.x;
         this.position.z = oldPos.z;
      }
     
   }


surrounding is a HashSet, so there are at most 18 checks every cycle (I know that this isn't optimal. I just did this to brute force a basic collision). I clear the Set afterwards so no growth here.

I never intentionally save any data for a longer period so there should be no growth especially no exponential Tongue
It has to be internally or I oversee something...

EDIT: I just found the source of the huge amount of char[]'s. It's because I set the title of my window to the current player position every update cycle. I removed it, but it's still the same problem.
Also the execution time profiling told me that most of the time goes into glCallList. That's reasonable because I call one list for every chunk, which is 64 lists every update. I probably should find an algorithm for that too. Still I don't think that this is the reason for the growth. The lists are generated only once in the beginning and then kept in the memory and never touched.
Offline Icecore

Senior Member


Medals: 5



« Reply #14 - Posted 2013-01-26 11:30:07 »

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
   long t1 = System.nanoTime();
   Label:{
      if(check_Block(x - 1, y + 1, z - 1)) break Label;
      if(check_Block(x - 1, y + 1, z    )) break Label;
      if(check_Block(x - 1, y + 1, z + 1)) break Label;
      if(check_Block(x    , y + 1, z - 1)) break Label;
      if(check_Block(x    , y + 1, z    )) break Label;
      if(check_Block(x    , y + 1, z + 1)) break Label;
      if(check_Block(x + 1, y + 1, z - 1)) break Label;
      if(check_Block(x + 1, y + 1, z    )) break Label;
      if(check_Block(x + 1, y + 1, z + 1)) break Label;

      if(check_Block(x - 1, y + 2, z - 1)) break Label;
      if(check_Block(x - 1, y + 2, z    )) break Label;
      if(check_Block(x - 1, y + 2, z + 1)) break Label;
      if(check_Block(x    , y + 2, z - 1)) break Label;
      if(check_Block(x    , y + 2, z    )) break Label;
      if(check_Block(x    , y + 2, z + 1)) break Label;
      if(check_Block(x + 1, y + 2, z - 1)) break Label;
      if(check_Block(x + 1, y + 2, z    )) break Label;
      if(check_Block(x + 1, y + 2, z + 1)) break Label;
   }
   long t2 = System.nanoTime();
   System.out.println("Debug Colis time: " + (t2 - t1));/////!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
   public boolean check_Block(int x, int y, int z){
      Block b = currentChunk.getBlock(x, y, z);
      if(b != null && this.getBounds().intersects(b.getBounds())){
         this.position.x = oldPos.x;
         this.position.z = oldPos.z;
         return true;
      }
      return false;
   }
Offline skappler

Senior Newbie





« Reply #15 - Posted 2013-01-26 12:05:00 »

Thanks for the code!
I tested it and all values are around 9000. There are some outliers around 28000 and a few even at 40000 but they are rare. That's not too bad, right?
Offline Icecore

Senior Member


Medals: 5



« Reply #16 - Posted 2013-01-26 12:53:47 »

Thanks for the code!
I tested it and all values are around 9000. There are some outliers around 28000 and a few even at 40000 but they are rare. That's not too bad, right?
Yes, problem somewhere else, you need check other main functions, you must look on 100 000ns +  that 0.1 ms
In game with 60 fps 1 frame take 15 ms – 15 000 000 ns

If you have random crazy slow down that don’t have direct place that maybe
Java  memory manager (GC, allocate more memory)
Its easy to detect: because you have crazy time on simple functions like 1ms+ or so.
Offline Icecore

Senior Member


Medals: 5



« Reply #17 - Posted 2013-01-26 14:31:31 »

You may use something like this

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
abstract public class D{
   static private long last_Time_N;
   static private long last_Time_M;
   
   static public long T(){
      return System.nanoTime();
   }
   static public void T_Start(){
      last_Time_N = System.nanoTime();
   }
   static public void T_Print(){
      System.out.println(System.nanoTime() - last_Time_N);
   }
   static public void T_Print(String text){
      System.out.println(text + (System.nanoTime() - last_Time_N));
   }
   
   static public long M(){
      return System.currentTimeMillis();
   }
   static public void M_Start(){
      last_Time_M = System.currentTimeMillis();
   }
   static public void M_Print(){
      System.out.println(System.currentTimeMillis() - last_Time_M);
   }
   static public void M_Print(String text){
      System.out.println(text + (System.currentTimeMillis() - last_Time_M));
   }
   
   static public long Mem(){
      Runtime runtime = Runtime.getRuntime();
      return runtime.totalMemory() - runtime.freeMemory();
   }
}

   {//game function
  D.T_Start();
   //code
  D.T_Print("code fifi time : ");
   }
Offline skappler

Senior Newbie





« Reply #18 - Posted 2013-01-26 16:05:21 »

Nice idea. I'll definitely will include this! Thanks =)
Offline philfrei
« Reply #19 - Posted 2013-01-27 05:47:03 »

Good stuff, Icecore!

I am reminded of making a similar helper class once. With something like this, you can test the timing metrics of different portions of the game loop, and progressively narrow down the location of the culprit.

Another idea: store a set of baseline readings and then have the code continue to monitor and only alert you if/when the readings drop significantly from the baseline (after playing the game a while). That way you don't have an overflowing amount of data to sort through.

"Greetings my friends! We are all interested in the future, for that is where you and I are going to spend the rest of our lives!" -- The Amazing Criswell
Offline Grunnt

JGO Wizard


Medals: 66
Projects: 8
Exp: 5 years


Complex != complicated


« Reply #20 - Posted 2013-01-27 07:40:48 »

And another word of advice: check your assumptions. You state that the game gets slower over time, but this is basically an assumption over the cause of the problem: that it has something to do with something happening over time. Such an assumption could make you blind to other causes of the problem. You should check this, try to measure it objectively. E.g. make fps measurements every few secs and create a table showing fps over time, or something similar.

In other words: be really sure what the problem is before looking for the solution. Maybe the problem is: whenever I walk far from my starting point the game slows down, or: when I start up fraps to record the slowdown the game slows down :-)

P.s. I've scanned through your github code but could not find anything obvious

Offline skappler

Senior Newbie





« Reply #21 - Posted 2013-01-27 08:25:11 »

The baseline reading idea is a good one. I think I'll implement some kind of debugging framework that let's me decide what data I want to take a look at.

I think my assumption is right. The heap monitor shows nearly the same curve every run no matter how I move around, where I look (that was one of my early assumptions) and even if I don't move at all.
I implement the FPS counter yesterday. The weird thing is, that since I did this, it seems to be better. The game doesn't get slower (or at least not that much) which doesn't make any sense because all I did was to add code that only counts the fps. The regulation still is handled by Display.sync() and I don't think that an overhead increases speed Tongue

Also thank you for your effort to look at my code! =)
Pages: [1]
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

CopyableCougar4 (14 views)
2014-08-22 19:31:30

atombrot (28 views)
2014-08-19 09:29:53

Tekkerue (25 views)
2014-08-16 06:45:27

Tekkerue (23 views)
2014-08-16 06:22:17

Tekkerue (15 views)
2014-08-16 06:20:21

Tekkerue (22 views)
2014-08-16 06:12:11

Rayexar (61 views)
2014-08-11 02:49:23

BurntPizza (39 views)
2014-08-09 21:09:32

BurntPizza (31 views)
2014-08-08 02:01:56

Norakomi (37 views)
2014-08-06 19:49:38
List of Learning Resources
by Longor1996
2014-08-16 10:40:00

List of Learning Resources
by SilverTiger
2014-08-05 19:33:27

Resources for WIP games
by CogWheelz
2014-08-01 16:20:17

Resources for WIP games
by CogWheelz
2014-08-01 16:19:50

List of Learning Resources
by SilverTiger
2014-07-31 16:29:50

List of Learning Resources
by SilverTiger
2014-07-31 16:26:06

List of Learning Resources
by SilverTiger
2014-07-31 11:54:12

HotSpot Options
by dleskov
2014-07-08 01:59: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!