Difference between revisions of "Object pool"
(Created page with "The ''object pool'' is a design pattern capable of reducing the allocation/deallocation overhead by reusing pre-instanced resources on demand and restricting their simultaneou...") |
m (Improved readability.) |
||
(One intermediate revision by the same user not shown) | |||
Line 4: | Line 4: | ||
In trilogy chapters, pools come with (note numerical fields are all 32-bit signed integers): | In trilogy chapters, pools come with (note numerical fields are all 32-bit signed integers): | ||
− | * | + | * <code>object_list</code>, an array of variable-sized slots of same type; |
− | * | + | * <code>byte_map</code>, an array of slot states each of which holds a <code>seed</code> (in the lower 7 bits) to randomize handles (also known as references) by a conspicuous factor and a <code>free_flag</code> (in the sign-bit, 8th bit) to signal the emptiness of the slot itself; |
− | * | + | * <code>count</code>, the number of slots; |
− | * | + | * <code>first_free</code>, a hint where to look from for free slot searching; |
− | * | + | * <code>initialized_state</code>, a flag indicating if arrays were allocated. |
Pools are commonly implemented in the following way: | Pools are commonly implemented in the following way: | ||
− | * On pool initialization – < | + | * On pool initialization – <code>Initialise(count)</code>: |
− | ** | + | ** <code>object_list</code> and <code>byte_map</code> are allocated with a fixed number of slots; |
− | ** | + | ** <code>count</code> is set accordingly while <code>first_free</code> becomes <code>-1</code>; |
− | ** | + | ** <code>initialized_state</code> is switched to true and <code>byte_map</code> is cleared as below. |
− | * On pool clean up – < | + | * On pool clean up – <code>Clear()</code>: |
− | ** For each slot of | + | ** For each slot of <code>byte_map</code>, <code>seed</code> is zeroed while <code>free_flag</code> is set. |
− | * On pool shutdown – < | + | * On pool shutdown – <code>Flush()</code>: |
− | ** If | + | ** If <code>count</code> isn't equal to 0: |
− | *** If | + | *** If <code>initialized_state</code> is true, <code>object_list</code> and <code>byte_map</code> are deallocated and zeroed (null-pointer check involved); |
− | *** | + | *** <code>count</code> and <code>first_free</code> are zeroed. |
− | * On slot lookup – < | + | * On slot lookup – <code>GetSlot(index) -> object</code>: |
− | ** | + | ** <code>free_flag</code> nullity of <code>byte_map[index]</code> is verified: |
− | *** If true, | + | *** If true, the reference of <code>object_list[index]</code> is returned; |
*** If false, a null-pointer is returned. | *** If false, a null-pointer is returned. | ||
− | * On slot lookup – < | + | * On slot lookup – <code>GetAt(handle) -> object</code>: |
− | ** | + | ** <code>index</code> is extracted from <code>handle</code> by <code>index = handle >> 8</code> and <code>byte_map[index]</code> is compared to the lower 8 bits of <code>handle</code>: |
− | *** If equal, | + | *** If equal, the reference of <code>object_list[index]</code> is returned; |
*** If different, a null-pointer is returned. | *** If different, a null-pointer is returned. | ||
− | * On slot | + | * On slot index retrieval – <code>GetJustIndex(object) -> index</code>: |
− | ** | + | ** <code>index</code> is returned by <code>index = (object - object_list) / sizeof(object)</code>. |
− | * On slot index retrieval – < | + | * On slot reference index retrieval – <code>GetIndex(object) -> handle</code>: |
− | ** | + | ** <code>index</code> is computed as above and <code>handle</code> is returned by <code>handle = (index << 8) | byte_map[index]</code>. |
− | * On free | + | * On free slot request – <code>New() -> object</code>: |
** Inside loop: | ** Inside loop: | ||
− | *** | + | *** <code>first_free</code> is incremented at each iteration; |
− | *** If | + | *** If <code>count</code> is reached, <code>first_free</code> is zeroed and the loop continues below, till the top one more time again (pretty odd, stopping at initial <code>first_free + 1</code> would have made more sense); |
− | *** | + | *** <code>free_flag</code> of <code>byte_map[first_free]</code> is verified: |
− | **** If true, | + | **** If true, <code>free_flag</code> is unset while <code>seed</code> is advanced (overflow is solved by <code>seed = (seed + 1) % 128</code>) and the reference of <code>object_list[first_free]</code> is returned; |
**** If false, the loop continues to the next iteration. | **** If false, the loop continues to the next iteration. | ||
** Outside loop: | ** Outside loop: | ||
− | *** No free slot was found, hence null-pointer returned. | + | *** No free slot was found, hence a null-pointer is returned. |
− | * On slot reservation – < | + | * On slot reservation – <code>New(handle) -> object</code>: |
− | ** | + | ** <code>index</code> is extracted from <code>handle</code> while <code>free_flag</code> and <code>seed</code> of <code>byte_map[index]</code> are respectively unset and advanced (overflow solved as aforementioned); |
− | ** | + | ** <code>first_free</code> is then zeroed and incremented till <code>byte_map[first_free]</code> with <code>free_flag</code> unset; |
− | * On slot removal – < | + | ** Lastly, the reference of <code>object_list[first_free]</code> is returned. |
− | ** | + | * On slot removal – <code>Delete(object)</code>: |
− | ** If | + | ** <code>index</code> is extracted from <code>object</code> while <code>free_flag</code> of <code>byte_map[index]</code> is set; |
+ | ** If <code>index</code> is lesser than <code>first_free</code>, the latter is updated. | ||
==Handle uniqueness== | ==Handle uniqueness== | ||
− | The probability to encounter an object with a non-unique handle after an undefined amount of new/delete requests is given by | + | The probability to encounter an object with a non-unique handle after an undefined amount of new/delete requests is given by <code>cost = 1 / ((free_slot_count * 128) + busy_slot_count)</code>.<br/> |
− | + | In the worst case (assuming the last reserved object of a full pool is repeatedly released/re-added), it is <code>cost = 1 / (128 + (object_count - 1))</code>.<br/> | |
− | + | In the best case (assuming an empty pool is filled up to the maximum with no release), it is <code>cost = 1 / (object_count * 128)</code>. | |
==See also== | ==See also== | ||
− | * {{GTAF|777350|<nowiki>[TUT|DOC] SCM functions (including TraversePoolEntities algorithms) | + | * {{GTAF|777350|<nowiki>[TUT|DOC]</nowiki> SCM functions (including TraversePoolEntities algorithms)}} – Multi-purpose tutorial also showing a scripted implementation of pool traversal to catch any type of entity by handle. |
+ | |||
+ | [[Category:Documentation]] |
Latest revision as of 17:41, 28 April 2021
The object pool is a design pattern capable of reducing the allocation/deallocation overhead by reusing pre-instanced resources on demand and restricting their simultaneous usage at a given time. It is heavily employed when the bottleneck induced by the frequent creations/destructions of objects is a concern.
Practical implementation
In trilogy chapters, pools come with (note numerical fields are all 32-bit signed integers):
object_list
, an array of variable-sized slots of same type;byte_map
, an array of slot states each of which holds aseed
(in the lower 7 bits) to randomize handles (also known as references) by a conspicuous factor and afree_flag
(in the sign-bit, 8th bit) to signal the emptiness of the slot itself;count
, the number of slots;first_free
, a hint where to look from for free slot searching;initialized_state
, a flag indicating if arrays were allocated.
Pools are commonly implemented in the following way:
- On pool initialization –
Initialise(count)
:object_list
andbyte_map
are allocated with a fixed number of slots;count
is set accordingly whilefirst_free
becomes-1
;initialized_state
is switched to true andbyte_map
is cleared as below.
- On pool clean up –
Clear()
:- For each slot of
byte_map
,seed
is zeroed whilefree_flag
is set.
- For each slot of
- On pool shutdown –
Flush()
:- If
count
isn't equal to 0:- If
initialized_state
is true,object_list
andbyte_map
are deallocated and zeroed (null-pointer check involved); count
andfirst_free
are zeroed.
- If
- If
- On slot lookup –
GetSlot(index) -> object
:free_flag
nullity ofbyte_map[index]
is verified:- If true, the reference of
object_list[index]
is returned; - If false, a null-pointer is returned.
- If true, the reference of
- On slot lookup –
GetAt(handle) -> object
:index
is extracted fromhandle
byindex = handle >> 8
andbyte_map[index]
is compared to the lower 8 bits ofhandle
:- If equal, the reference of
object_list[index]
is returned; - If different, a null-pointer is returned.
- If equal, the reference of
- On slot index retrieval –
GetJustIndex(object) -> index
:index
is returned byindex = (object - object_list) / sizeof(object)
.
- On slot reference index retrieval –
GetIndex(object) -> handle
:index
is computed as above andhandle
is returned byhandle = (index << 8) | byte_map[index]
.
- On free slot request –
New() -> object
:- Inside loop:
first_free
is incremented at each iteration;- If
count
is reached,first_free
is zeroed and the loop continues below, till the top one more time again (pretty odd, stopping at initialfirst_free + 1
would have made more sense); free_flag
ofbyte_map[first_free]
is verified:- If true,
free_flag
is unset whileseed
is advanced (overflow is solved byseed = (seed + 1) % 128
) and the reference ofobject_list[first_free]
is returned; - If false, the loop continues to the next iteration.
- If true,
- Outside loop:
- No free slot was found, hence a null-pointer is returned.
- Inside loop:
- On slot reservation –
New(handle) -> object
:index
is extracted fromhandle
whilefree_flag
andseed
ofbyte_map[index]
are respectively unset and advanced (overflow solved as aforementioned);first_free
is then zeroed and incremented tillbyte_map[first_free]
withfree_flag
unset;- Lastly, the reference of
object_list[first_free]
is returned.
- On slot removal –
Delete(object)
:index
is extracted fromobject
whilefree_flag
ofbyte_map[index]
is set;- If
index
is lesser thanfirst_free
, the latter is updated.
Handle uniqueness
The probability to encounter an object with a non-unique handle after an undefined amount of new/delete requests is given by cost = 1 / ((free_slot_count * 128) + busy_slot_count)
.
In the worst case (assuming the last reserved object of a full pool is repeatedly released/re-added), it is cost = 1 / (128 + (object_count - 1))
.
In the best case (assuming an empty pool is filled up to the maximum with no release), it is cost = 1 / (object_count * 128)
.
See also
- GTAForums: [TUT|DOC] SCM functions (including TraversePoolEntities algorithms) – Multi-purpose tutorial also showing a scripted implementation of pool traversal to catch any type of entity by handle.