Java-Gaming.org Hi !
Featured games (83)
games approved by the League of Dukes
Games in Showcase (527)
Games in Android Showcase (127)
games submitted by our members
Games in WIP (594)
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  
  Object Pooling  (Read 4017 times)
0 Members and 1 Guest are viewing this topic.
Offline DrHalfway
« Posted 2012-08-19 07:45:15 »

It is interesting that there are not many good pooling tutorials available on the internet, This small tutorial will introduce one of the many methods of implementing a simple, general purpose object pool that is relatively fast.

My experience with Java is somewhat limited, I am still learning the ins and outs of doing things the java-way. Our team has been working on a Game Engine for the Android and the PC for the past 7-8 months and one of the problems we encountered in our early tests in the Android platform was the Garbage Collector. As objects were created and destroyed and re-created during every iteration of the game loop, made the Garbage Collector... lets just say, very unfriendly.

One way to combat this was the early concept of reusing objects as much as possible, during the development of the engine we had specific pools for specific objects, lets just say that things got a little bit "messy". This tutorial is dedicated to the painful code cleanup that was performed in our engine to incorporate a general purpose "Pooling" solution that could not only provide pooling, but also work as a factory class to return platform-specific objects. Talk about killing two birds with one stone right? =]

This tutorial assumes some basic Java Programming concepts, it also assumes kowledge about Java Generics. By the end of this tutorial the user would have written a basic yet functional Generic Pool.

Now then, let us get started, we will start off by defining the interfaces

1  
2  
3  
4  
5  
6  
7  
public interface Pool<Type> {
   public void recycle(final Type data);
   public Type get();  
   public void setFactory(final PoolObjectFactory<Type> factory);
   public void reset();
   public String debug();
}


1  
2  
3  
public interface PoolObjectFactory<Type> {
   public Type newObject();
}


Let us implement our Pool<Type> Interface. Our pool implementation is as follows.

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  
public class ObjectPool<Type> implements Pool<Type> {

   private final Stack<Type> freeObjects;
   private PoolObjectFactory<Type> factory;
   
   public ObjectPool() {
      freeObjects = new Stack<Type>();
   }

   public void recycle(final Type data) {
      freeObjects.push(data);
   }

   public Type get() {
      if (freeObjects.isEmpty()) {
         return factory.newObject();
      }
     
      return freeObjects.pop();
   }

   public void setFactory(final PoolObjectFactory<Type> factory) {
      this.factory = factory;
   }

   public void reset() {
      freeObjects.clear();
   }

   public String debug() {
      return "Current Pool Size: " + freeObjects.size();
   }
}


A fairly straightforward Pool Implementation, You grab objects from the pool via the get() function. The get() function checks to see if any objects are available, if not, it uses the factory to create one, if yes, then it simply returns an object in the stack.

So how do you use the Pool? Lets say you have a Vector, possibly one of the most used Objects in both Engine Envirnoment aswell as Game Programming Environment.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
public class Vector {
   public float x;
   public float y;
   public float z;
   
   public Vector() {
      x = 0.0f;
      y = 0.0f;
      z = 0.0f;
   }
}


Not the greatest of Vector implementations, but for this example, it will do! First, we implement our Factory interface.

1  
2  
3  
4  
5  
public class VectorFactory implements PoolObjectFactory<Vector> {
   public Vector newObject() {
      return new Vector();
   }
}


And this small program shows how the Pool can be used.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
public class TestPool {
   public static void main(String[] args) {
      Pool<Vector> vectorPool = new ObjectPool<Vector>();
      vectorPool.setFactory(new VectorFactory());
      // get a new Vector. This operation replaces the new operator
      Vector vec = vectorPool.get();
      // perform an operation on vec here
     
     
      // once we are done, recycle this vector
      vectorPool.recycle(vec);
      // for safety reasons, point vec to null
      vec = null;
   }
}


So how does the pool work? All the magic happens during the get() method. The function first checks to see if there are any available free objects, if not, a new one is created from the Factory, otherwise a new object is returned from the Stack.

This simple yet effective pooling method was used in our engine for a very long time untill the relatively large code cleanup. Currently, when an object is removed from the pool, the pool no longer has a reference to that object which has both ups and downs. An upside is that control of the object is now passed onto the caller function and the object won't be reused untill its passed into the pool via the recycle() function. The downside is that the object will constantly need to be removed and added to the stack at every get() and recycle() function call.

One solution would be to allow the Pool to keep a reference of the objects even after they are "removed" from the Pool and instead use the stack to keep values of free Object ID's which represent the cell that the object is stored in the pool. This type of design will allow the creation of a "temporary object" pool, which instead can be reset at end of game loop. One example would be Vector objects, which are only used for Math operations and discarded afterwards. These Vectors can be grabbed from the Pool during the computation and automatically recycled at end of game loop. This also makes object storage more cache friendly, as it ensures they are created and stored closer together. This is the method currently used in our engine and one possible way to implement such a pool is below.

The PoolObjectFactory Interfaces remain the same, we now have a new Generic Interface called Poolable<Type>.

1  
2  
3  
4  
5  
6  
public interface Poolable<Type> {
   public void setPoolID(final int id);
   public int getPoolID();
   public Type get();
   public void clean();
}


The Pool interface now changes to accept Poolable<Type> objects instead.

1  
2  
3  
4  
5  
6  
7  
public interface Pool<Type> {
   public void recycle(final Poolable<Type> data);
   public Type get();  
   public void setFactory(final PoolObjectFactory<Type> factory);
   public void reset();
   public String debug();
}


Below is the new implementation of the Vector Object.

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  
public class Vector implements Poolable<Vector> {
   public float x;
   public float y;
   public float z;
   
   private int poolID;
   
   public Vector() {
      x = 0.0f;
      y = 0.0f;
      z = 0.0f;
   }

   public void setPoolID(int id) {
      poolID = id;
   }

   public int getPoolID() {
      return poolID;
   }

   public Vector get() {
      return this;
   }

   public void clean() {
      x = 0.0f;
      y = 0.0f;
      z = 0.0f;
   }
}


The Implementation of the pool now becomes as follows

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  
public class ObjectPool<Type> implements Pool<Type> {

   private final Stack<Integer> freeObjectsID;
   private final ArrayList<Poolable<Type>> objects;
   private PoolObjectFactory<Type> factory;
   
   public ObjectPool() {
      freeObjectsID = new Stack<Integer>();
      objects = new ArrayList<Poolable<Type>>();
   }

   public void recycle(final Poolable<Type> data) {
      freeObjectsID.push(data.getPoolID());
   }

   @SuppressWarnings("unchecked")
   public Type get() {
      if (freeObjectsID.isEmpty()) {
         
         final Poolable<Type> obj = (Poolable<Type>) factory.newObject();
         obj.setPoolID(objects.size());
         objects.add(obj);
         
         return obj.get();
      }
     
      final Poolable<Type> obj = objects.get(freeObjectsID.pop());
      obj.clean();

      return obj.get();
   }

   public void setFactory(final PoolObjectFactory<Type> factory) {
      this.factory = factory;
   }

   public void reset() {
      freeObjectsID.clear();
      final Iterator<Poolable<Type>> it = objects.iterator();
     
      while (it.hasNext()) {
         freeObjectsID.push(it.next().getPoolID());
      }
   }

   public String debug() {
      return "Current Pool Size: " + freeObjectsID.size();
   }
}


So the new Pool has quite a bit more going on. The overall solution is not as optimised as it could be, for example, switching the ArrayList and Stack to custom-made containers designed specifically for the tasks the pool provides will give a small increase in performance and memory usage.

Overall, as mentioned before, the new Pool does not remove the objects from inside, this can be very dangerous. If the object is not recycled() it is essentially forever lost in the Pool, you can consider this a C++ equivalent of a memory leak, so ensure you recycle after use, although as mentioned before, if the pool is considered a "temporary object pool" it can always be reset at end of frame, which means that objects don't need to be recycled.

For those of you wondering, Pooling is not meant to be easy to use, it is not meant to be used by your game programmers using your engine to make games, although some smarter interfaces can allow for such a thing. This is mainly there to help speedup the internals of the numerous engine components, such as the collision detectors, Maths libraries etc via reusing old objects which can and will get created in the packs of hundreds and thousands per frame (depending on game complexity).

This simple test program simulates a simple Game Loop.

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  
public class TestPool {
   public static void main(String[] args) {
      Pool<Vector> vectorPool = new ObjectPool<Vector>();
      vectorPool.setFactory(new VectorFactory());

      // this loop will run 100,000 times, consider it a limited "fake" game loop
      for (int i = 0; i < 100000; i++) {

         // pool debug data after reset
         System.out.println("Debug Data AFTER reset: " + vectorPool.debug());

         for (int j = 0; j < 100; j++) {
            // perform some long and tiresome Vector operation here

            Vector vec1 = vectorPool.get();
            Vector vec2 = vectorPool.get();

            // do something silly..
            vec1.x = i;
            vec1.y = j;
            vec2.x = j;
            vec1.y = i;
           
            Vector vec3 = vectorPool.get();
            vec3.x = vec1.x + vec2.x;
            vec3.y = vec1.y + vec2.y;

            System.out.println("Some Silly Vector Math X: " + vec3.x + " Y: " + vec3.y);
         }
         // pool debug data before reset
         System.out.println("Debug Data BEFORE reset: " + vectorPool.debug());
         // reset the vectorPool, it is considered a temporary pool, all objects will become reusable
         vectorPool.reset();
      }
   }
}


I would like to thank you for reading my very first somewhat technical java-tutorial and wish you good luck in your future engine-designing adventures! Any comments/suggestions are more than welcome!

Offline Riven
« League of Dukes »

« JGO Overlord »


Medals: 834
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #1 - Posted 2012-08-19 08:39:06 »

You really want the objects that come out of your pool, to be in a known state. Whatever code put an instance of Vector3f in your pool, when you pull it out, it must have its fields (x,y,z) zeroed out. This means your code will be a lot more predictable, as it will not cause nasty, hard to reproduce problems.

Therefore, my Pool doesn't just have a PoolFactory, it has a PoolHandler:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
public interface PoolHandler<T>() {
   public T create();
   public void clean(T item);
}

public class Pool<T> {
   public T aquire() {
      T item = this.released.isEmpty() ? this.handler.create() : this.released.pop();
      this.handler.clean(item);
      return item;
   }
}


Hope this helps!

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline DrHalfway
« Reply #2 - Posted 2012-08-19 09:08:07 »

Cheers for the reply, I didn't think of resetting objects from the factory, can always do that aswell, currently the objects are cleared from the Poolable<Type> Interface.

Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline Riven
« League of Dukes »

« JGO Overlord »


Medals: 834
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #3 - Posted 2012-08-19 09:35:29 »

You can't really ask the Vector type to be pool-aware (through the poolable interface). It will require you make most types in your classpath to be pool-aware, which creates a mess. And what about classes that are not under your control? Let's say you want to pool java.nio.ByteBuffer -- the only solution you'd have would be to wrap the ByteBuffer type into PoolableByteBuffer, and implement the clean() method. That seems like a struggle for a common use case.

With a PoolHandler, you'd end up with:
1  
2  
3  
4  
5  
6  
7  
8  
9  
PoolHandler<ByteBuffer> poolHandler = new PoolHandler<ByteBuffer>() {
   public ByteBuffer create() {
      return ByteBuffer.allocateDirect(4*1024);
   }

   public void clean(ByteBuffer item) {
      item.clear();
   }
};

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

JGO Knight


Medals: 30
Exp: 18 years


Computers can do that?


« Reply #4 - Posted 2012-08-19 11:08:26 »

There should be some discussion on when you should use object pooling. These days with the jre 1.6 and 1.7 they should really only be used for large objects or expensive to create objects (database connections). Things like vector3f won't give any performance increase even if its in a tight loop for the most part. In fact pooling can even slow things down by pushing more objects into the 2nd generation that are not really 2nd generation objects.

Of course there are exceptions to this, such as the fairly slow java RT on android. But it should be clear that pooling is a optimization that is best applied after its been determined to needed. 

I have no special talents. I am only passionately curious.--Albert Einstein
Offline Riven
« League of Dukes »

« JGO Overlord »


Medals: 834
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #5 - Posted 2012-08-19 12:22:30 »

This is not really the place to have such a discussion. Let's discuss the code instead.

If somebody *needs* pooling, then it's nice to have such a code snippet.

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
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.

PocketCrafter7 (12 views)
2014-11-28 16:25:35

PocketCrafter7 (7 views)
2014-11-28 16:25:09

PocketCrafter7 (8 views)
2014-11-28 16:24:29

toopeicgaming1999 (75 views)
2014-11-26 15:22:04

toopeicgaming1999 (65 views)
2014-11-26 15:20:36

toopeicgaming1999 (15 views)
2014-11-26 15:20:08

SHC (29 views)
2014-11-25 12:00:59

SHC (27 views)
2014-11-25 11:53:45

Norakomi (32 views)
2014-11-25 11:26:43

Gibbo3771 (28 views)
2014-11-24 19:59:16
Understanding relations between setOrigin, setScale and setPosition in libGdx
by mbabuskov
2014-10-09 22:35:00

Definite guide to supporting multiple device resolutions on Android (2014)
by mbabuskov
2014-10-02 22:36:02

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
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!