0

In my game engine, there are Particles, all of these particles have a slightly different lifespan. Every update, their life is shortened and when it reaches 0 it is removed from an ArrayList meaning it will no longer be updated or rendered. Although under normal game play I experience no problems with this approach, when I have a large large number of particles (around 100,000) there is major lag when creating and removing (but not updating) the particles.

Because of that lag, I am looking to improve the efficiency of this setup. I have tried only removing so many per update and leaving the rest for the next update but that didn't provide too great of results.

package com.burksnet.code.games.rain.entity.particle;

import com.burksnet.code.games.rain.entity.Entity;
import com.burksnet.code.games.rain.graphics.Screen;
import com.burksnet.code.games.rain.graphics.Sprite;
import com.burksnet.code.games.rain.level.Level;

public class Particle extends Entity {

    private Sprite sprite;
    private int life;

    private double speed = 2;

    public static int maxParticleCount = 2500000;

    protected double xa, ya;
    //Life is how long it lives, x, y, and level are obvious.
    public Particle(int x, int y, int life, Level level) {
        super(level);
        //The sprite to be rendered.
        sprite = Sprite.particleNormal;
        this.x = x;
        this.y = y;
        this.life = life;

        //how it will move. effectively randomly.
        this.xa = random.nextGaussian() /2 * speed;
        this.ya = random.nextGaussian() /2 * speed;

    }

    //Renders the particle on screen.
    public void render(Screen screen) {
        if(life <= 0 ) return;
        screen.renderSprite(x, y, sprite, true);
    }

    //Updates it.
    public void update() {
        if(life <= 0 ){
            //Removes the item from the level, making it no longer be rendered or updated. See the method below.
            level.remove(this);
        }
        this.x += xa;
        this.y += ya;
        life--;
    }

}

Level.remove(Particle e) below

public void remove(Particle e) {
    particles.remove(e);
}

Level.add(Particle e) below. Is used when a particle is created so it may be updated and rendered.

public void add(Particle e) {
    particles.add(e);
}

I guess it is definitely possible this is just a pure limitation that cannot be overcome but I hope that is not the case.

halfer
  • 19,824
  • 17
  • 99
  • 186
Mr. Pasta
  • 35
  • 7
  • I think that ArrayList is rather slow at removing elements from the middle (or not from the beginning and end). A LinkedList might be faster... In this comparison of Oracles [list implementations](http://docs.oracle.com/javase/tutorial/collections/implementations/list.html) they describe it a little better. – tuberains Jul 25 '17 at 05:16
  • tuberains. After looking at https://analyzejava.wordpress.com/2015/01/29/arraylist-vs-linkedlist/ it appears a linked list would in whole be worse due to the fact i use the get method heavily (since I need to update and render them hundreds to thousands of times throughout their life). – Mr. Pasta Jul 25 '17 at 05:20
  • When you call remove(e) the ArrayList first has to locate e in the array, which is a linear search. It then has to move all subsequent particles 1 to the left. These are slow operations. There are other data-structures you could consider (e.g. priority queues), but first you could try sorting the ArrayList after every round of updates, then find the index of the first particle you want to keep and use removeRange to remove all particles up to that point. Sorting a mostly ordered list is quite fast, so as long as update doesn't change the order too much it may be acceptable. – RaffleBuffle Jul 25 '17 at 05:31
  • I have a class that is the only way particles are spawned. Possibly after it adds any particles it sorts the list. From there, linerally iterate up to the first particle to keep and remove all below it... That is a good idea, at least it seems so. – Mr. Pasta Jul 25 '17 at 05:35

4 Answers4

1

Let's take a look at what happens when you remove an item from the middle of an ArrayList.

       +---+---+---+---+---+
Start  | 1 | 2 | 3 | 4 | 5 |
       +---+---+---+---+---+
Step 1 | 1 | 2 |   | 4 | 5 |
       +---+---+---+---+---+
Step 2 | 1 | 2 | 4 |   | 5 |
       +---+---+---+---+---+
Step 3 | 1 | 2 | 4 | 5 |   |
       +---+---+---+---+---+

As you can see, most of the time spent is not in removin the item itself, but moving the items that come after it into their new places. This is why, for a list of 100,000 records, removing will take a long time.

If you don't need random access (the use of get(i)), then you can look into using a LinkedList instead. If you do need get(i), then you're going to need to make some tough decisions.

Joe C
  • 15,324
  • 8
  • 38
  • 50
  • I do need get(i) very very heavily since I am updating and rendering them thousands of times throughout their life. I am getting the instance to update through get(i). Would it be possible to use another method to use sequential access? I don't believe its ever random, it should always be linearly going through all of the particles. – Mr. Pasta Jul 25 '17 at 05:22
  • Can you do it through a for-each loop or an iterator? – Joe C Jul 25 '17 at 05:23
  • rather than remove, can you not update it to `deleted` (as a flag) ? – Scary Wombat Jul 25 '17 at 05:27
  • Scary Wombat, I was thinking of that earlier, the problem with that is the memory of it would never be cleared and the List would grow insanely large. – Mr. Pasta Jul 25 '17 at 05:28
  • Joe C, I'm not sure. If i did a for loop I myself would still use get(i) which would be the problem with a LinkedList. I have never used Iterators so i am not sure if that would solve it or not. – Mr. Pasta Jul 25 '17 at 05:30
  • Look up the difference between a for loop and a for-each loop (which uses iterators under the covers). – Joe C Jul 25 '17 at 05:38
  • Years programming with java and never heard of it... I guess I've been living under a rock. – Mr. Pasta Jul 25 '17 at 05:42
  • *it would never be cleared and the List would grow insanely large* rather than trying to remove in real-time, set to `deleted` and then find a convenient time in your program to clear them out – Scary Wombat Jul 25 '17 at 05:51
0

The problem seems to lie in the way that you're iterating and updating the particles. You'll want to use a LinkedList and iterate over it in your Level. I would recommend refactoring the update() method to return a boolean to signal when the Entity should be removed from the level.

public class Particle {
    public boolean update() {
        if (life <= 0) {
            return false; // remove me!
        }
        this.x += xa;
        this.y += ya;
        life--;
        return true; // update successful
    }
}

public class Level {
    public boolean update() {
        Iterator<Particle> it = particles.iterator();
        while (it.hasNext()) {
            Particle p = it.next();
            if (!p.update()) {
                it.remove(); // Remove is O(1) with LinkedList
            }
        }
        return true; // update successful
    }
}
Jake Holzinger
  • 5,783
  • 2
  • 19
  • 33
  • I actually really like this, it appears it will be easy to implement and (to my bad eyes) very good code. – Mr. Pasta Jul 25 '17 at 05:48
  • Haha! you did huh? Did you find it through my email? or how? – Mr. Pasta Jul 25 '17 at 05:53
  • Exception in thread "Game" java.util.ConcurrentModificationException at java.util.LinkedList$ListItr.checkForComodification(Unknown Source) at java.util.LinkedList$ListItr.next(Unknown Source) at com.burksnet.code.games.rain.level.Level.update(Level.java:149) at com.burksnet.code.games.rain.Game.updateWithoutMenu(Game.java:408) at com.burksnet.code.games.rain.Game.update(Game.java:384) at com.burksnet.code.games.rain.Game.run(Game.java:282) at java.lang.Thread.run(Unknown Source) – Mr. Pasta Jul 25 '17 at 06:03
  • basically copied, its the it.next(); line thats throwing it – Mr. Pasta Jul 25 '17 at 06:04
  • Looks like you'll need a thread-safe `LinkedList`. `LinkedList particles = Collections.synchronizedList(new LinkedList<>());` – Jake Holzinger Jul 25 '17 at 06:06
  • You'll also need to manually synchronize the list before doing the iteration. https://docs.oracle.com/javase/7/docs/api/java/util/Collections.html#synchronizedList(java.util.List) – Jake Holzinger Jul 25 '17 at 06:11
  • Hmm, while its working for small amounts of particles, it basically crashed when I added my normal test of 100,000 particles – Mr. Pasta Jul 25 '17 at 06:14
  • Did you change all of your loops to use iterators or for-each loops? You shouldn't be using `List.get()` anymore. – Jake Holzinger Jul 25 '17 at 06:15
  • Hmm, in this setup, only spawning in 10,000 particles results in severe lag for over 1 second. With an arraylist there was no lag at this point – Mr. Pasta Jul 25 '17 at 06:16
  • My bad, forgot to change it under render. – Mr. Pasta Jul 25 '17 at 06:19
  • However there is now a delay when spawning in large numbers of particles – Mr. Pasta Jul 25 '17 at 06:20
  • Okay, i was kindof wrong. (I have a watchdog thread that restarts my main thread if it crashes so what i was actually seeing was that) due to an exception. Exception in thread "Thread-34" java.util.ConcurrentModificationException at java.util.LinkedList$ListItr.checkForComodification(Unknown Source) at java.util.LinkedList$ListItr.next(Unknown Source) at com.burksnet.code.games.rain.level.Level.render(Level.java:223) at com.burksnet.code.games.rain.Game.render(Game.java:321) at com.burksnet.code.games.rain.Game.run(Game.java:287) at java.lang.Thread.run(Unknown Source) – Mr. Pasta Jul 25 '17 at 06:24
  • so, as you can see, it is now crashing when rendering them because of that exception. – Mr. Pasta Jul 25 '17 at 06:24
  • Are you `synchronizing` the list before iterating it? https://docs.oracle.com/javase/7/docs/api/java/util/Collections.html#synchronizedList(java.util.List) – Jake Holzinger Jul 25 '17 at 06:27
  • Iterator it = Collections.synchronizedList(particles).iterator(); while (it.hasNext()) { Particle p = it.next(); if (!p.update()) { it.remove(); // Remove is O(1) with LinkedList } } – Mr. Pasta Jul 25 '17 at 06:31
  • Thats the current code where an exception is thrown – Mr. Pasta Jul 25 '17 at 06:31
  • The list should be wrapped in `Collections.synchronizedList()` when it is declared. Then you need to wrap the wrap the iteration code in a `synchronized` block as explained in the docs. `synchronized(particles){..iteration code...}`. – Jake Holzinger Jul 25 '17 at 06:35
  • okay, damn... that works really well. (Ruins the thread I had rendering it... but it works better than that thread xd) thanks a lot – Mr. Pasta Jul 25 '17 at 06:42
  • Hmm, it has a weird bug were when spawning a shit shit shit ton it kindof pulses. Like instead of spawning all at once it spawns 1/10th at 10 dif times – Mr. Pasta Jul 25 '17 at 06:50
  • Hmm, I'm not sure. My 2¢, try to avoid modifying the game simulation data (e.g. the lists) from another thread and drop the `synchronized` stuff. When you're ready to render the scene make a non-mutable shallow copy (e.g. only the stuff needed to render) of all the render-able entities and pass them to the render thread to render. – Jake Holzinger Jul 25 '17 at 06:52
  • well, the render and update thread are one. (Come on man i thought u looked at the github!) but due to the fact particles were slowing me down, I made a thread to handle their updating. I really should work on making a render and a update thread setup... – Mr. Pasta Jul 25 '17 at 07:01
  • I know they are, but they should ideally be separated if you want to improve the performance. The game loop should update the simulation and only pass a "view" (ideally static) of the simulation data to the renderer to paint the screen. I only took a quick look at the project so I don't know what that would take to do, but it's pretty standard game design. Anyway, good luck with your game. – Jake Holzinger Jul 25 '17 at 07:05
  • Thanks man! Thanks for your help! If you dont mind how long have you been programming? – Mr. Pasta Jul 25 '17 at 07:08
  • Thats really cool. Good luck man. Thanks for the help. I've only really been programming for about 2 years now, so i guess i miss a few things still :P – Mr. Pasta Jul 25 '17 at 15:45
0

I am guessing there are several things that are contributing to the lag you are seeing.

The size of your data structure

Each double takes up 8 bytes of memory. Do you really need a 64 bit floating point value for your coordinates and your speed?

I don't know what's in your Entity class, but a quick computation leads me to (conservatively) guess that each Particle object will take up at least 32 bytes of memory. This will be roughly 3 MB of heap for 100,000 Particles. If the Particle objects are removed often during gameplay, the JVM's garbage collector could contribute a significant amount to the lag.

Your use of floating point math

This is used when calculating your xa and ya coordinates

Consider using integer math instead as this could provide a noticable improvement to the performance of your game.

Adding and removing from an ArrayList

As you've pointed out, adding and removing from an ArrayList can be expensive. For add, there is the potential to have to reallocate memory to accommodate the newly added element. For remove, all of the elements that came after the one removed have to be copied back one place in the list.

One thing you could do if you choose to stay with ArrayList is to construct one with an initial capacity that is close to whatever you think your max number of particles is. This has the downside of reserving a big chunk of heap right away, but will avoid having to reallocate often. The default initial capacity for an ArrayList is 10.

You might consider a different List implementation that provides better performance for adds/removes. LinkedList fits the bill as does possibly an ordered Map like a TreeMap.

Gather solid data about your app by profiling it

One of the best things you can do is to profile your app while it is running to see where the CPU cycles are being spent and heap is being allocated. Don't pre-optimize if you can help it. Measure first! Good luck!

Michael Krause
  • 4,689
  • 1
  • 21
  • 25
  • How can I profile it, I am using eclipse and have searched for ways to do so and cant find anything – Mr. Pasta Jul 25 '17 at 06:07
  • In response to the floating point math stuff, if I don't use floating point math, than a particle must at least move one pixel per update, as this is a low res engine this isnt something that I want. I want the ability for a particle to only move 4 pixels in 180 updates if thats the random number it gets. – Mr. Pasta Jul 25 '17 at 06:09
  • It's been awhile since I've used Eclipse, but I used [JProbe](https://marketplace.eclipse.org/content/jprobe) for profiling and memory analysis. It's not free though. – Michael Krause Jul 25 '17 at 06:12
  • Have a look at [this](https://stackoverflow.com/questions/948549/open-source-java-profilers) SO post regarding free Java profilers. – Michael Krause Jul 25 '17 at 06:26
  • Michael, thanks. After profiling my program, each particle took up over 70 bytes! primarily due to one item that should and could be static not being static. – Mr. Pasta Jul 25 '17 at 15:41
-1
class Array {
  public Static void main(string args[]) {
    ArrayList<String> lst=new ArrayList<String>();
    lst.add("abc");
    lst.add("xyz");
    lst.add("pqr");
    Iterator<String> itr=lst.Iterator();
    while(itr.hasNext()) {
      String ele=itr.next();
      if(ele.equals("xyz") {
        itr.remove();
      }
      System.out.println(ele);  
    }
  }
}
Jozef Dúc
  • 965
  • 2
  • 18
  • 29
Ajith K M
  • 7
  • 2
  • Although I mostly get where you are going, and how you are tellng me to use an iterator, you dont inform if that actually would be faster or explain your code in any way... – Mr. Pasta Jul 25 '17 at 05:37