129
creational performance

Object Pool

Reference Wikipedia ↗
Object Pool — class diagram
Plate 129 class diagram

The Object Pool pattern is a creational design pattern that aims to improve performance by reusing objects that are expensive to create. Instead of creating a new object each time one is needed, the pool maintains a collection of pre-initialized objects. When an object is required, it’s borrowed from the pool; when it’s no longer needed, it’s returned to the pool for later use, rather than being destroyed. This reduces the overhead of frequent object creation and destruction, especially valuable when dealing with resource-intensive objects.

This pattern is particularly useful when object instantiation is slow or limited by external resources (e.g., database connections, network sockets, threads). It can significantly reduce latency and improve system throughput in scenarios involving high object churn, and helps manage resource constraints effectively. By limiting the overall number of objects created, it also contributes to better resource utilization and stability.

Usage

The Object Pool pattern is widely used in systems requiring efficient management of costly resources:

  • Database Connection Pooling: Most database libraries and application servers utilize object pools to manage database connections. Establishing a database connection is a slow operation, so pooling these connections significantly improves performance.
  • Thread Pooling: Similar to database connections, creating and destroying threads is expensive. Thread pools are essential components of concurrent programming, reusing threads to handle multiple tasks efficiently.
  • Graphics and Game Development: Creating and disposing of graphical objects (textures, models, etc.) can be time-consuming. Object pools are used to reuse these objects, reducing lag and improving frame rates.
  • Network Socket Management: Managing a large number of network sockets can be resource-intensive. Pooling sockets allows for efficient reuse and reduces the overhead of connection establishment and teardown.

Examples

  1. Apache Commons Pool (Java): This library provides a generic object pooling framework for Java applications. It allows developers to easily create pools for various types of objects, including database connections, threads, and custom objects. Configuration options allow for controlling pool size, eviction policies, and validation logic. https://commons.apache.org/proper/commons-pool/

  2. HikariCP (Java): Specifically designed for database connection pooling, HikariCP is a high-performance JDBC connection pool. It emphasizes speed and minimizes overhead, making it a popular choice for modern Java applications. It offers advanced features like connection validation, timeout handling, and monitoring. https://github.com/brettwooldridge/HikariCP

  3. Unity Engine (C#): Unity uses object pooling extensively in game development for reusable game objects like bullets, enemies, and particle effects. The Object.Instantiate() and Object.Destroy() methods can be slow within a game loop; using a pool avoids this performance bottleneck. Unity provides built-in tools and community-created asset store packages to facilitate object pooling. https://docs.unity3d.com/Manual/ObjectPooling.html

Specimens

15 implementations
Specimen 129.01 Dart View specimen ↗

The Object Pool pattern manages a pool of reusable objects to reduce the cost of repeated object creation and destruction. It’s beneficial for heavy object creation scenarios, like database connections or complex calculations. The code implements a Pool class that holds a collection of PooledObject instances. request() and release() methods manage borrowing and returning objects to the pool. The PooledObject represents the objects being managed - in this case, simple objects with a given state. Using a Queue from Dart’s collection library offers an efficient FIFO structure to manage object availability. This implementation utilizes Dart’s class-based object-oriented structure for clarity and maintainability.

import 'dart:collection';

class PooledObject {
  String state;

  PooledObject(this.state);

  void reset() {
    state = ''; // Resets the object to a default state
  }

  @override
  String toString() => 'PooledObject(state: $state)';
}

class Pool {
  final int _maxSize;
  final Queue<PooledObject> _pool = Queue();

  Pool(this._maxSize) {
    // Initialize the pool with pre-created objects
    for (var i = 0; i < _maxSize; i++) {
      _pool.add(PooledObject('Initial State'));
    }
  }

  PooledObject request() {
    if (_pool.isEmpty) {
      print('Pool is empty. Creating a new object.');
      return PooledObject('New State'); // Or handle this differently, e.g., throw an error.
    }
    return _pool.removeFirst();
  }

  void release(PooledObject object) {
    object.reset();
    if (_pool.length < _maxSize) {
      _pool.addLast(object);
    }
    // Optionally, destroy objects if the pool is full
  }
}

void main() {
  var pool = Pool(3);

  var obj1 = pool.request();
  obj1.state = 'Object 1 in Use';
  print(obj1);

  var obj2 = pool.request();
  obj2.state = 'Object 2 in Use';
  print(obj2);
  
  pool.release(obj1);
  print('Object 1 released');

  var obj3 = pool.request();
  print(obj3); 

  pool.release(obj2);
  pool.release(obj3);

  print('Pool size after releases: ${pool.length}');
}