Is it possible to combine the "Pool Design Pattern" and the "Immutable Design Pattern" in JS?

I'm working within a complex application that uses a lot of new instances of a given class in big loops (multiple million iterations per frame for image processing). To mitigate the (huge) impact on performances that the use of the garbage collector would provoke I am using the "Pool Design Pattern" (tl;dr: reusing the same instances when they are not used anymore to prevent garbage collection and the creation of new instances). The issue is, I need to apply a lot a consecutive operations on my instances, so to make sure I'm using an instance that has the value that I need at any given moment I want to add Immutability to the mix (tl;dr: when created, an instance's values can't be modified). If I want the pool of object to work I need to send the instances back to it (recycling). The process would look something like :
const myObject = myObjectPool.getOne(/* some initialization parameters */);

const returnValue = myObject
.transformInSomeWay(someValue)
.transformInSomeOtherWay(someOtherValue)
.value;

myObjectPool.recycle(myObject);
/* do stuff with returnValue */
const myObject = myObjectPool.getOne(/* some initialization parameters */);

const returnValue = myObject
.transformInSomeWay(someValue)
.transformInSomeOtherWay(someOtherValue)
.value;

myObjectPool.recycle(myObject);
/* do stuff with returnValue */
That is all good if we assume that both .transformInSomeWay(someValue) and .transformInSomeOtherWay(someOtherValue) are mutating myObject (changing it's internal state). But that means, if I ever need to use retrieve an object, pass it to some other piece of code (maybe async code) and then do some other things with it, I need to be sure to copy that object before doing anything, otherwise I might be working with an object that has already been modified, leading to incorrect result (and it's a nightmare to debug). To prevent that, I want to use the Immutability Design Pattern (tl;dr: once created an object's state cannot be modified). Getting it to work with the instantiation and recycling process is rather trivial as I would not use Object.freeze() but simply make the state private in TS. [to be continued in thread...]
9 Replies
Rägnar O'ock
Rägnar O'ock17mo ago
The problem arises when I want to do something like :
const returnValue = myObject
.transformInSomeWay(someValue)
.transformInSomeOtherWay(someOtherValue)
.value;
const returnValue = myObject
.transformInSomeWay(someValue)
.transformInSomeOtherWay(someOtherValue)
.value;
If I use immutability, each method call returns a new instance of the class but I cannot recycle those instances, meaning they are taken out of the pool, but never returned to it to be reused. In of itself it's not too big of an issue, as they would simply be garbage collected, but that's precisely what I want to avoid by using an object pool in the first place. So don't know if there is a way to combine those 2 design patterns without making the code look like this :
const myObject = myObjectPool.getOne(/* some initialization parameters */);

const objectWithTranform = myObject.transformInSomeWay(someValue);
const objectWithOtherTransform = objectWithTransform.transformInSomeOtherWay(someOtherValue);
const theValue = objectWithOtherTransform.value;

myObjectPool.recycle(myObject, objectWithTranform, objectWithOtherTransform );
/* do stuff with returnValue */
const myObject = myObjectPool.getOne(/* some initialization parameters */);

const objectWithTranform = myObject.transformInSomeWay(someValue);
const objectWithOtherTransform = objectWithTransform.transformInSomeOtherWay(someOtherValue);
const theValue = objectWithOtherTransform.value;

myObjectPool.recycle(myObject, objectWithTranform, objectWithOtherTransform );
/* do stuff with returnValue */
because its just... a mess really...
Jochem
Jochem17mo ago
ugly and high performance usually go hand in hand I'm afraid... The main issue I see with the last bit of code is that you're potentially not getting the clones from the pool, so for every 1 object you process, three end up in the pool. One is reused, but two more are added could you do the recycling inside the method calls somehow?
Rägnar O'ock
Rägnar O'ock17mo ago
nah, but the object returned from the method would be built by the pool the object constructor would not be used directly
Jochem
Jochem17mo ago
ah, so you're calling myObjectPool.getOne inside transformInSomeWay?
Rägnar O'ock
Rägnar O'ock17mo ago
it would not be inaccessible because I don't think there is a wait to do that in TS/JS and still have a generic Pool class like I want to have tho (easier to maintain that way) yup
Jochem
Jochem17mo ago
so what's preventing you from recycling the intermediate objects back into the pool inside the methods too? ah, cause you can't recycle this hm
Rägnar O'ock
Rägnar O'ock17mo ago
well you could... but that would mean you can't do something like :
const myObject = pool.getOne(/* some initialization parameters */);

for (i = 0; i < nbPixels; i++) {
const objectAtPixel = myObject.adaptForPixel(i);
// some stuff with objectAtPixel
pool.recycle(objectAtPixel);
}
const myObject = pool.getOne(/* some initialization parameters */);

for (i = 0; i < nbPixels; i++) {
const objectAtPixel = myObject.adaptForPixel(i);
// some stuff with objectAtPixel
pool.recycle(objectAtPixel);
}
because myObject would have been recycled within .adaptForPixel and maybe reused somewhere else, so the values inside could not be trusted (completely defeats the point of adding immutability) plus, it would make the abstraction of the interface monstrously leaky because you would need to know if the method did or didn't recycle the object it's attached to. (or assume either way, and in one you waste the object and provoke GC)
Jochem
Jochem17mo ago
you've got to lose some flexibility to increase performance usually though, right? the GC is there so you can be messy with your memory use normally, and not really worry about it... but if you want to avoid it, you have to take some of that responsibility yourself so yeah, you'd have to lose some of the neatness or ease of use of your methods?
Rägnar O'ock
Rägnar O'ock17mo ago
that's what I think is bound to happen if I proceed with adding immutability into the mix.