0

I have created an immutable data type in Python where millions of objects are created and released every second. After profiling the code thoroughly, it looks like the constructor is where most of the time is spent.

The solution I thought of was to use an object pool so that reference counting and memory allocation is done all at once. I have looked at solutions like this, where acquire and release methods need to be called explicitly.

However, the class I have implemented is similar to Decimal class in Python where objects are created and released automatically by numpy. For example, a short piece of code that uses my class will look like this (I use Decimal instead of my own class):

import numpy as np
from decimal import Decimal

x = np.array([[Decimal(1), Decimal(2)], [Decimal(3), Decimal(4)]]
y = np.array([[Decimal(5), Decimal(6)], [Decimal(7), Decimal(8)]]

z = (x * y) + (2 * x) - (y ** 2) + (x ** 3)

Because the class is immutable, numpy needs to create a new object for each operation and this slows down the whole code. Additionally, because numpy is the code that is creating these objects, I do not think that I can explicitly call methods such as acquire or release.

Is there a better implementation of an object pool or some other method where a lot of objects are created all at once and later, the released objects are automatically placed back in the pool? In other words, is there another solution that avoids frequent object creation and destruction?

P.S. I understand that this is not a good way of using numpy. This is one of the first steps in my design and hopefully, numpy will be used more efficiently in next steps.

internet_user
  • 3,149
  • 1
  • 20
  • 29
Matt
  • 796
  • 12
  • 25

1 Answers1

2

Would something like this work?

class Pool():
  def __init__(self, type_, extra_alloc=1):
    self._objects = []
    self.type = type_
    self.extra_alloc = extra_alloc

  def allocate(self, size):
    self._objects.extend(object.__new__(self.type) for _ in range(size))

  def get_obj(self):
    print("Getting object")
    if not self._objects:
      self.allocate(self.extra_alloc)

    return self._objects.pop()

  def give_obj(self, obj):
    print("Object released")
    self._objects.append(obj)

class Thing(): # This can also be used as a base class
  pool = None

  def __new__(self, *args):
    return self.pool.get_obj()

  def __del__(self):
    self.pool.give_obj(self)

thing_pool = Pool(Thing)
Thing.pool = thing_pool

Thing()
# Getting object
# Object released

x = Thing()
# Getting object

del x
# Object released
internet_user
  • 3,149
  • 1
  • 20
  • 29