4

The main language I'm interested in doing object pooling in is AS3, which is known to need it in many cases. In the past I've always made a brand new factory class for every type I was going to try to pool, the parameters of its "object-producing" function being the same as those of that type's constructor. Essentially:

static public function produce(<list of paramaters>):<type>
{
    if (m_arryPool.length)
    {
        var obj:<type> = m_arryPool.pop();
        // do something with the arguments passed to produce()
        return obj;
    }
    return new obj(<arguments passed to produce()>);
}

But recently I've been trying to think of a way to avoid re-writing this sort of boiler-plate code for every single type I want to pool, but probably without forfeiting the idea of passing arguments around to the returned objects as in the above example, and definitely without putting so much dynamic functionality and stuff in there that it just winds up slowing stuff down too much.

I'm still a little green at object-pooling. I know a one-size-fits-all pooling factory will probably not work out in all cases (AS3's Function.apply(), for instance, doesn't support constructors), but when does it get to the point that you're just passing in so many arguments to the produce() function, using so many untyped variables, going through so many dictionaries and lists, etc., that it just isn't hardly worth it anymore?

I know this question may be slightly subjective, but I do believe there are largely objective answers to it. I'm mainly interested in how this would apply to AS3, but I'm also interested in (and asking for) a general answer. Is the best way to handle it generally just to go ahead and write boilerplate code for every type you plan to pool or something like that? Thanks!

1
  • 3
    Possibly what you're worrying about is the "inner platform effect" - if the factory has to support all the flexibility of creating things that you had without the factory, the complexity probably destroys all the benefits, so what you end up is another (probably slow, clunky and broken) way to do what you could have done anyway. If your goal is to replace something (like object construction) that you can already do, the key is to specialize. But that can be taken too far too. As you clearly already know, there's a balance - but where? That's a judgement call.
    – Steve314
    Oct 7 '13 at 2:13
2
+50

You will need some type of initialization method to get the arguments to the instance. It might be good to put that in a namespace.
Using some untyped voodoo is not good as untyped code is a lot slower - we're talking orders of magnitude in some cases (if you do that there's simply not point in having a pool in the first place). There's simply a lot of overhead involved in it. However a typed method invocation or field access can get performances near to C++ method calls (still factor 2-3 is to be expected).

So just have an initialization method and the you can have a plain constructor.

Reasons to leave the constructor blank:

  1. Constructors are not JITed on the AVM2. Whatever you do in there is going to be slower.
  2. If you have blank constructors, it is cheap to pre-allocate instances in batches. For one, it helps preventing memory fragmentation. But also this is something you can do while your app has low CPU load. Say at the beginning, say when you're loading assets, you can use the free time to preallocate some stuff. And when you need the instances, they are already there, which can result in a smoother experience overall.

And as a side note: Do not push and pop - for some reasons they are very slow, even on arrays.

The best is to maintain an index for an ample fixed size Vector. It takes 4 bytes per entry (due to FlashPlayer's 32 bit references). So 50000 slots will take up 200KB memory which is really not much compared to how much memory sounds or textures take when uncompressed to memory. Still, if you know less is fine, then go with less. That's something you will have to profile.

This is what it would look like:

var max:int = 50000;
var pool:Vector.<Type> = new Vector.<Type>(max, true);
var count:int = 0;
public function free(obj:Type):void {
    if (count < max - 1)
        pool[count++] = obj;
}
public function alloc():Type {
    return (count > 0) ? pool[--count] else new Type();
}

Lastly, avoid static or global methods. They are also orders of magnitude slower.

The pool would be an instance (good use for a singleton) and whoever is responsible for getting objects from it or releasing them to it should be holding a reference to that object.

2
  • If you don't mind me asking, I've heard before that static methods tend to improve efficiency when stuff isn't instance-specific, presumably since they're not getting copied around or anything like that. What would cause them to be a lot slower? Also is using things like arry[arry.length] = something and arry.splice(arry.length - 1, 1) faster than using push and pop on an Array? Oct 9 '13 at 23:43
  • @Panzercrisis: When in doubt, benchmark. That also applies to modifying arrays. Again, because you have a pool, you do not need to even get the value out of the array, so there's no point in splicing or popping. Just remember at which point you separate references that are eligible for serving from garbage references that you have already served. Oh and splicing is very slow if you must know.
    – back2dos
    Oct 10 '13 at 13:33
5

I don't know AS3, but I can offer you some design advice. If you are passing in so many arguments, you might want to rethink your design.

Why not seperate the creation and initialization? You pass in one argument which would be the type id of the object that you need. Each class has its own static type id to represent it, and you access it by calling something like getID().

You use the type id as a hash or index into the object pool to get the object. The pool for each object is contained within 1 dynamic array index by the type id.

The type id is used as the method to determine the object. In a seperate function, such as the calling function, you call a function on the object to initialize the object.

No matter what you decide, you should stress test AC3 to set appropiate limits in your code before you go live with a solution.

5
  • What is the advantage to not passing arguments around to a constructor in the factory, aside from just not putting as much stuff on the call stack? You're not the first person I've seen mention or demonstrate something like that. Oct 8 '13 at 17:58
  • It eliminates all the extra logic that would be required by passing the arguments. You'd need to create a constructor for all occations, and that is just a waste.
    – Joe McCay
    Oct 8 '13 at 21:30
  • Is it just bad to do this in languages that support constructor overloading, or is it generally even bad to do this in a language that doesn't (but which might support optional parameters)? Oct 9 '13 at 16:30
  • 1
    This is more a personal comment at this point, but as you add new classes in the future, the logic and functions start to get overwhelming. You'd have to put a cut off somewhere. It is better to design it for expansion now when there is less legacy code.
    – Joe McCay
    Oct 9 '13 at 20:03
  • Ah, I think I see. Thank you very much for your answer. Oct 11 '13 at 12:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.