├── .editorconfig ├── .gitignore ├── LICENSE ├── Open.Disposable.ObjectPools.sln ├── README.md ├── benchmarking ├── Benchmark.cs ├── BenchmarkResult.csv ├── BenchmarkResult.txt ├── ConsoleReport.cs ├── DefaultObjectPool.cs ├── GlobalSuppressions.cs ├── OldTest.cs ├── Open.Disposable.ObjectPools.Benchmarking.csproj └── Program.cs ├── source ├── Array │ ├── InterlockedArrayObjectPool.cs │ └── OptimisticArrayObjectPool.cs ├── Channels │ ├── ChannelObjectPool.cs │ └── ChanneledRecycler.cs ├── Collection │ ├── CollectionWrapperObjectPool.cs │ ├── ConcurrentQueueObjectPool.cs │ ├── ConcurrentQueueObjectPoolSlim.cs │ ├── ConcurrentQueueObjectPoolSlimBase.cs │ ├── ConcurrentStackObjectPool.cs │ ├── QueueObjectPool.cs │ └── StackObjectPool.cs ├── Constants.cs ├── CountTrackedObjectPoolBase.cs ├── IObjectPool.cs ├── IRecyclable.cs ├── IRecycler.cs ├── ITrimmableObjectPool.cs ├── ObjectPoolAutoTrimmer.cs ├── ObjectPoolBase.cs ├── ObjectPoolCompatibilityExtensions.cs ├── ObjectPoolResizeEvent.cs ├── Open.Disposable.ObjectPools.csproj ├── RecycleHelper.cs ├── Recycler.cs ├── RecyclerBase.cs ├── ReferenceContainer.cs ├── SharedPools.cs ├── TrimmableCollectionObjectPoolBase.cs ├── TrimmableObjectPoolBase.cs └── logo.png └── tests ├── ObjectPoolSmokeTests.cs └── Open.Disposable.ObjectPools.Tests.csproj /.editorconfig: -------------------------------------------------------------------------------- 1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories 2 | root = true 3 | 4 | # C# files 5 | [*.cs] 6 | 7 | #### Core EditorConfig Options #### 8 | 9 | # Indentation and spacing 10 | indent_size = 4 11 | indent_style = tab 12 | tab_width = 4 13 | 14 | # New line preferences 15 | end_of_line = crlf 16 | insert_final_newline = false 17 | 18 | #### .NET Coding Conventions #### 19 | 20 | # Organize usings 21 | dotnet_separate_import_directive_groups = false 22 | dotnet_sort_system_directives_first = false 23 | file_header_template = unset 24 | 25 | # this. and Me. preferences 26 | dotnet_style_qualification_for_event = false 27 | dotnet_style_qualification_for_field = false 28 | dotnet_style_qualification_for_method = false 29 | dotnet_style_qualification_for_property = false 30 | 31 | # Language keywords vs BCL types preferences 32 | dotnet_style_predefined_type_for_locals_parameters_members = true 33 | dotnet_style_predefined_type_for_member_access = true 34 | 35 | # Parentheses preferences 36 | dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary 37 | dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary 38 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary 39 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity 40 | 41 | # Modifier preferences 42 | dotnet_style_require_accessibility_modifiers = for_non_interface_members 43 | 44 | # Expression-level preferences 45 | dotnet_style_coalesce_expression = true 46 | dotnet_style_collection_initializer = true 47 | dotnet_style_explicit_tuple_names = true 48 | dotnet_style_namespace_match_folder = true 49 | dotnet_style_null_propagation = true 50 | dotnet_style_object_initializer = true 51 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 52 | dotnet_style_prefer_auto_properties = true 53 | dotnet_style_prefer_compound_assignment = true 54 | dotnet_style_prefer_conditional_expression_over_assignment = true 55 | dotnet_style_prefer_conditional_expression_over_return = true 56 | dotnet_style_prefer_inferred_anonymous_type_member_names = true 57 | dotnet_style_prefer_inferred_tuple_names = true 58 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true 59 | dotnet_style_prefer_simplified_boolean_expressions = true 60 | dotnet_style_prefer_simplified_interpolation = true 61 | 62 | # Field preferences 63 | dotnet_style_readonly_field = true 64 | 65 | # Parameter preferences 66 | dotnet_code_quality_unused_parameters = all 67 | 68 | # Suppression preferences 69 | dotnet_remove_unnecessary_suppression_exclusions = none 70 | 71 | # New line preferences 72 | dotnet_style_allow_multiple_blank_lines_experimental = true 73 | dotnet_style_allow_statement_immediately_after_block_experimental = true 74 | 75 | #### C# Coding Conventions #### 76 | 77 | # var preferences 78 | csharp_style_var_elsewhere = true 79 | csharp_style_var_for_built_in_types = true 80 | csharp_style_var_when_type_is_apparent = true 81 | 82 | # Expression-bodied members 83 | csharp_style_expression_bodied_accessors = true:silent 84 | csharp_style_expression_bodied_constructors = when_on_single_line:silent 85 | csharp_style_expression_bodied_indexers = true:silent 86 | csharp_style_expression_bodied_lambdas = true:silent 87 | csharp_style_expression_bodied_local_functions = true:silent 88 | csharp_style_expression_bodied_methods = true:silent 89 | csharp_style_expression_bodied_operators = when_on_single_line:silent 90 | csharp_style_expression_bodied_properties = true:silent 91 | 92 | # Pattern matching preferences 93 | csharp_style_pattern_matching_over_as_with_null_check = true 94 | csharp_style_pattern_matching_over_is_with_cast_check = true 95 | csharp_style_prefer_not_pattern = true 96 | csharp_style_prefer_pattern_matching = true 97 | csharp_style_prefer_switch_expression = true 98 | 99 | # Null-checking preferences 100 | csharp_style_conditional_delegate_call = true 101 | 102 | # Modifier preferences 103 | csharp_prefer_static_local_function = true 104 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async 105 | 106 | # Code-block preferences 107 | csharp_prefer_braces = when_multiline:silent 108 | csharp_prefer_simple_using_statement = true:suggestion 109 | csharp_style_namespace_declarations = file_scoped:silent 110 | 111 | # Expression-level preferences 112 | csharp_prefer_simple_default_expression = true 113 | csharp_style_deconstructed_variable_declaration = true 114 | csharp_style_implicit_object_creation_when_type_is_apparent = true 115 | csharp_style_inlined_variable_declaration = true 116 | csharp_style_pattern_local_over_anonymous_function = true 117 | csharp_style_prefer_index_operator = true 118 | csharp_style_prefer_null_check_over_type_check = true 119 | csharp_style_prefer_range_operator = true 120 | csharp_style_throw_expression = true 121 | csharp_style_unused_value_assignment_preference = discard_variable 122 | csharp_style_unused_value_expression_statement_preference = discard_variable 123 | 124 | # 'using' directive preferences 125 | csharp_using_directive_placement = outside_namespace:silent 126 | 127 | # New line preferences 128 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true 129 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true 130 | csharp_style_allow_embedded_statements_on_same_line_experimental = true 131 | 132 | #### C# Formatting Rules #### 133 | 134 | # New line preferences 135 | csharp_new_line_before_catch = true 136 | csharp_new_line_before_else = true 137 | csharp_new_line_before_finally = true 138 | csharp_new_line_before_members_in_anonymous_types = true 139 | csharp_new_line_before_members_in_object_initializers = true 140 | csharp_new_line_before_open_brace = all 141 | csharp_new_line_between_query_expression_clauses = true 142 | 143 | # Indentation preferences 144 | csharp_indent_block_contents = true 145 | csharp_indent_braces = false 146 | csharp_indent_case_contents = true 147 | csharp_indent_case_contents_when_block = true 148 | csharp_indent_labels = one_less_than_current 149 | csharp_indent_switch_labels = true 150 | 151 | # Space preferences 152 | csharp_space_after_cast = false 153 | csharp_space_after_colon_in_inheritance_clause = true 154 | csharp_space_after_comma = true 155 | csharp_space_after_dot = false 156 | csharp_space_after_keywords_in_control_flow_statements = true 157 | csharp_space_after_semicolon_in_for_statement = true 158 | csharp_space_around_binary_operators = before_and_after 159 | csharp_space_around_declaration_statements = false 160 | csharp_space_before_colon_in_inheritance_clause = true 161 | csharp_space_before_comma = false 162 | csharp_space_before_dot = false 163 | csharp_space_before_open_square_brackets = false 164 | csharp_space_before_semicolon_in_for_statement = false 165 | csharp_space_between_empty_square_brackets = false 166 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 167 | csharp_space_between_method_call_name_and_opening_parenthesis = false 168 | csharp_space_between_method_call_parameter_list_parentheses = false 169 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 170 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 171 | csharp_space_between_method_declaration_parameter_list_parentheses = false 172 | csharp_space_between_parentheses = false 173 | csharp_space_between_square_brackets = false 174 | 175 | # Wrapping preferences 176 | csharp_preserve_single_line_blocks = true 177 | csharp_preserve_single_line_statements = true 178 | 179 | #### Naming styles #### 180 | 181 | # Naming rules 182 | 183 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 184 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 185 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 186 | 187 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 188 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 189 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 190 | 191 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 192 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 193 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 194 | 195 | # Symbol specifications 196 | 197 | dotnet_naming_symbols.interface.applicable_kinds = interface 198 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 199 | dotnet_naming_symbols.interface.required_modifiers = 200 | 201 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 202 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 203 | dotnet_naming_symbols.types.required_modifiers = 204 | 205 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 206 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 207 | dotnet_naming_symbols.non_field_members.required_modifiers = 208 | 209 | # Naming styles 210 | 211 | dotnet_naming_style.pascal_case.required_prefix = 212 | dotnet_naming_style.pascal_case.required_suffix = 213 | dotnet_naming_style.pascal_case.word_separator = 214 | dotnet_naming_style.pascal_case.capitalization = pascal_case 215 | 216 | dotnet_naming_style.begins_with_i.required_prefix = I 217 | dotnet_naming_style.begins_with_i.required_suffix = 218 | dotnet_naming_style.begins_with_i.word_separator = 219 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 220 | 221 | dotnet_diagnostic.HAA0303.severity = silent 222 | dotnet_diagnostic.HAA0401.severity = silent 223 | dotnet_diagnostic.HAA0501.severity = silent 224 | dotnet_diagnostic.HAA0502.severity = silent 225 | dotnet_diagnostic.HAA0601.severity = silent 226 | csharp_style_prefer_method_group_conversion = true:silent 227 | csharp_style_prefer_top_level_statements = true:silent 228 | csharp_style_prefer_primary_constructors = true:suggestion 229 | csharp_prefer_system_threading_lock = true:suggestion 230 | [*.{cs,vb}] 231 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 232 | tab_width = 4 233 | indent_size = 4 234 | end_of_line = crlf 235 | dotnet_style_coalesce_expression = true:suggestion 236 | dotnet_style_null_propagation = true:suggestion 237 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 238 | dotnet_style_prefer_auto_properties = true:silent 239 | dotnet_style_object_initializer = true:suggestion 240 | dotnet_diagnostic.CA2007.severity = error -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | 42 | # Created by https://www.gitignore.io/api/visualstudiocode 43 | 44 | ### VisualStudioCode ### 45 | .vscode/* 46 | !.vscode/settings.json 47 | !.vscode/tasks.json 48 | !.vscode/launch.json 49 | !.vscode/extensions.json 50 | .history 51 | 52 | # End of https://www.gitignore.io/api/visualstudiocode 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # .NET Core 58 | project.lock.json 59 | project.fragment.lock.json 60 | artifacts/ 61 | **/Properties/launchSettings.json 62 | 63 | *_i.c 64 | *_p.c 65 | *_i.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.pch 70 | *.pdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # TFS 2012 Local Workspace 109 | $tf/ 110 | 111 | # Guidance Automation Toolkit 112 | *.gpState 113 | 114 | # ReSharper is a .NET coding add-in 115 | _ReSharper*/ 116 | *.[Rr]e[Ss]harper 117 | *.DotSettings.user 118 | 119 | # JustCode is a .NET coding add-in 120 | .JustCode 121 | 122 | # TeamCity is a build add-in 123 | _TeamCity* 124 | 125 | # DotCover is a Code Coverage Tool 126 | *.dotCover 127 | 128 | # Visual Studio code coverage results 129 | *.coverage 130 | *.coveragexml 131 | 132 | # NCrunch 133 | _NCrunch_* 134 | .*crunch*.local.xml 135 | nCrunchTemp_* 136 | 137 | # MightyMoose 138 | *.mm.* 139 | AutoTest.Net/ 140 | 141 | # Web workbench (sass) 142 | .sass-cache/ 143 | 144 | # Installshield output folder 145 | [Ee]xpress/ 146 | 147 | # DocProject is a documentation generator add-in 148 | DocProject/buildhelp/ 149 | DocProject/Help/*.HxT 150 | DocProject/Help/*.HxC 151 | DocProject/Help/*.hhc 152 | DocProject/Help/*.hhk 153 | DocProject/Help/*.hhp 154 | DocProject/Help/Html2 155 | DocProject/Help/html 156 | 157 | # Click-Once directory 158 | publish/ 159 | 160 | # Publish Web Output 161 | *.[Pp]ublish.xml 162 | *.azurePubxml 163 | # TODO: Comment the next line if you want to checkin your web deploy settings 164 | # but database connection strings (with potential passwords) will be unencrypted 165 | *.pubxml 166 | *.publishproj 167 | 168 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 169 | # checkin your Azure Web App publish settings, but sensitive information contained 170 | # in these scripts will be unencrypted 171 | PublishScripts/ 172 | 173 | # NuGet Packages 174 | *.nupkg 175 | # The packages folder can be ignored because of Package Restore 176 | **/packages/* 177 | # except build/, which is used as an MSBuild target. 178 | !**/packages/build/ 179 | # Uncomment if necessary however generally it will be regenerated when needed 180 | #!**/packages/repositories.config 181 | # NuGet v3's project.json files produces more ignorable files 182 | *.nuget.props 183 | *.nuget.targets 184 | 185 | # Microsoft Azure Build Output 186 | csx/ 187 | *.build.csdef 188 | 189 | # Microsoft Azure Emulator 190 | ecf/ 191 | rcf/ 192 | 193 | # Windows Store app package directories and files 194 | AppPackages/ 195 | BundleArtifacts/ 196 | Package.StoreAssociation.xml 197 | _pkginfo.txt 198 | 199 | # Visual Studio cache files 200 | # files ending in .cache can be ignored 201 | *.[Cc]ache 202 | # but keep track of directories ending in .cache 203 | !*.[Cc]ache/ 204 | 205 | # Others 206 | ClientBin/ 207 | ~$* 208 | *~ 209 | *.dbmdl 210 | *.dbproj.schemaview 211 | *.jfm 212 | *.pfx 213 | *.publishsettings 214 | orleans.codegen.cs 215 | 216 | # Since there are multiple workflows, uncomment next line to ignore bower_components 217 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 218 | #bower_components/ 219 | 220 | # RIA/Silverlight projects 221 | Generated_Code/ 222 | 223 | # Backup & report files from converting an old project file 224 | # to a newer Visual Studio version. Backup files are not needed, 225 | # because we have git ;-) 226 | _UpgradeReport_Files/ 227 | Backup*/ 228 | UpgradeLog*.XML 229 | UpgradeLog*.htm 230 | 231 | # SQL Server files 232 | *.mdf 233 | *.ldf 234 | *.ndf 235 | 236 | # Business Intelligence projects 237 | *.rdl.data 238 | *.bim.layout 239 | *.bim_*.settings 240 | 241 | # Microsoft Fakes 242 | FakesAssemblies/ 243 | 244 | # GhostDoc plugin setting file 245 | *.GhostDoc.xml 246 | 247 | # Node.js Tools for Visual Studio 248 | .ntvs_analysis.dat 249 | node_modules/ 250 | 251 | # Typescript v1 declaration files 252 | typings/ 253 | 254 | # Visual Studio 6 build log 255 | *.plg 256 | 257 | # Visual Studio 6 workspace options file 258 | *.opt 259 | 260 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 261 | *.vbw 262 | 263 | # Visual Studio LightSwitch build output 264 | **/*.HTMLClient/GeneratedArtifacts 265 | **/*.DesktopClient/GeneratedArtifacts 266 | **/*.DesktopClient/ModelManifest.xml 267 | **/*.Server/GeneratedArtifacts 268 | **/*.Server/ModelManifest.xml 269 | _Pvt_Extensions 270 | 271 | # Paket dependency manager 272 | .paket/paket.exe 273 | paket-files/ 274 | 275 | # FAKE - F# Make 276 | .fake/ 277 | 278 | # JetBrains Rider 279 | .idea/ 280 | *.sln.iml 281 | 282 | # CodeRush 283 | .cr/ 284 | 285 | # Python Tools for Visual Studio (PTVS) 286 | __pycache__/ 287 | *.pyc 288 | 289 | # Cake - Uncomment if you are using it 290 | # tools/** 291 | # !tools/packages.config 292 | 293 | # Telerik's JustMock configuration file 294 | *.jmconfig 295 | 296 | # BizTalk build output 297 | *.btp.cs 298 | *.btm.cs 299 | *.odx.cs 300 | *.xsd.cs 301 | .vscode/launch.json 302 | .vscode/tasks.json 303 | /source/nuget.config 304 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 electricessence (Oren F.) All rights reserved 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Open.Disposable.ObjectPools.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31717.71 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Open.Disposable.ObjectPools.Benchmarking", "benchmarking\Open.Disposable.ObjectPools.Benchmarking.csproj", "{8404875C-ED46-4057-8D97-598B4D7B8040}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Open.Disposable.ObjectPools", "source\Open.Disposable.ObjectPools.csproj", "{7DF5DA35-AB11-479D-BD87-27461AC3F5A8}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Open.Disposable.ObjectPools.Tests", "tests\Open.Disposable.ObjectPools.Tests.csproj", "{507DBB8A-A662-4D16-A375-48470A578B50}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4E2B0ECD-63D2-42CF-91D2-53917748B856}" 13 | ProjectSection(SolutionItems) = preProject 14 | .editorconfig = .editorconfig 15 | EndProjectSection 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Release|Any CPU = Release|Any CPU 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {8404875C-ED46-4057-8D97-598B4D7B8040}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {8404875C-ED46-4057-8D97-598B4D7B8040}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {8404875C-ED46-4057-8D97-598B4D7B8040}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {8404875C-ED46-4057-8D97-598B4D7B8040}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {7DF5DA35-AB11-479D-BD87-27461AC3F5A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {7DF5DA35-AB11-479D-BD87-27461AC3F5A8}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {7DF5DA35-AB11-479D-BD87-27461AC3F5A8}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {7DF5DA35-AB11-479D-BD87-27461AC3F5A8}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {507DBB8A-A662-4D16-A375-48470A578B50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {507DBB8A-A662-4D16-A375-48470A578B50}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {507DBB8A-A662-4D16-A375-48470A578B50}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {507DBB8A-A662-4D16-A375-48470A578B50}.Release|Any CPU.Build.0 = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(SolutionProperties) = preSolution 37 | HideSolutionNode = FALSE 38 | EndGlobalSection 39 | GlobalSection(ExtensibilityGlobals) = postSolution 40 | SolutionGuid = {86DED392-9553-4DB9-9EC3-BEBE4F470390} 41 | EndGlobalSection 42 | EndGlobal 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Open.Disposable.ObjectPools 2 | A set of variations on ObjectPool implementations with differing underlying collections. 3 | 4 | [![NuGet](https://img.shields.io/nuget/v/Open.Disposable.ObjectPools.svg)](https://www.nuget.org/packages/Open.Disposable.ObjectPools/) 5 | -------------------------------------------------------------------------------- /benchmarking/Benchmark.cs: -------------------------------------------------------------------------------- 1 | using Open.Diagnostics; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | #if DEBUG 7 | using System.Diagnostics; 8 | #endif 9 | 10 | namespace Open.Disposable.ObjectPools; 11 | 12 | public class Benchmark : BenchmarkBase>> 13 | where T : class 14 | { 15 | public Benchmark(uint size, uint repeat, Func> poolFactory) : base(size, repeat, poolFactory) 16 | { 17 | // Because some pools do comparison checks on values, we have have unique instances/values. 18 | _items = new T[(int)size]; 19 | var pool = Param(); 20 | Parallel.For(0, TestSize, i => _items[i] = pool.Take()); 21 | } 22 | 23 | readonly T[] _items; 24 | 25 | protected override IEnumerable TestOnceInternal() 26 | { 27 | var pool = Param() ?? throw new NullReferenceException(); 28 | using var _ = pool as IDisposable; 29 | //yield return TimedResult.Measure("Take From Empty (In Parallel)", () => 30 | //{ 31 | // Parallel.For(0, TestSize, i => _items[i] = pool.Take()); 32 | //}); 33 | 34 | yield return TimedResult.Measure("Give To (In Parallel)", () => 35 | { 36 | // ReSharper disable once AccessToDisposedClosure 37 | Parallel.For(0, TestSize, i => pool.Give(_items[i])); 38 | #if DEBUG 39 | if (pool is DefaultObjectPool) return; 40 | var count = pool.Count; 41 | Debug.Assert(pool is OptimisticArrayObjectPool || count == TestSize, $"Expected {TestSize}, acutal count: {count}"); 42 | #endif 43 | }); 44 | 45 | yield return TimedResult.Measure("Mixed 90%-Take/10%-Give (In Parallel)", () => 46 | { 47 | Parallel.For(0, TestSize, i => 48 | { 49 | if (i % 10 == 0) 50 | pool.Give(_items[i]); 51 | else 52 | _items[i] = pool.Take(); 53 | }); 54 | }); 55 | 56 | yield return TimedResult.Measure("Mixed 50%-Take/50%-Give (In Parallel)", () => 57 | { 58 | Parallel.For(0, TestSize, i => 59 | { 60 | if (i % 2 == 0) 61 | _items[i] = pool.Take(); 62 | else 63 | pool.Give(_items[i]); 64 | }); 65 | }); 66 | 67 | yield return TimedResult.Measure("Mixed 10%-Take/90%-Give (In Parallel)", () => 68 | { 69 | Parallel.For(0, TestSize, i => 70 | { 71 | if (i % 10 == 0) 72 | _items[i] = pool.Take(); 73 | else 74 | pool.Give(_items[i]); 75 | }); 76 | }); 77 | 78 | //if (pool is DefaultObjectPool) yield break; 79 | //yield return TimedResult.Measure("Empty Pool (.TryTake())", () => 80 | //{ 81 | 82 | // while (pool.TryTake() is not null) 83 | // { 84 | // // remaining++; 85 | // } 86 | //}); 87 | } 88 | 89 | public static TimedResult[] Results(uint size, uint repeat, Func> poolFactory) => (new Benchmark(size, repeat, poolFactory)).Result; 90 | } 91 | -------------------------------------------------------------------------------- /benchmarking/BenchmarkResult.csv: -------------------------------------------------------------------------------- 1 | Batch,Pool Type," To (In Parallel)",d 90%-Take/10%-Give (In Parallel),d 50%-Take/50%-Give (In Parallel),d 10%-Take/90%-Give (In Parallel),L, 2 | Repeat 2400000 for size 4,Microsoft.Extensions.ObjectPool.DefaultObjectPool,00:00:03.8980333,00:00:03.9366455,00:00:03.9072529,00:00:03.8850033,00:00:15.6269350, 3 | Repeat 2400000 for size 4,ConcurrentQueueObjectPoolSlim,00:00:03.8494058,00:00:03.9230416,00:00:03.8901577,00:00:03.8867848,00:00:15.5493899, 4 | Repeat 2400000 for size 4,ConcurrentQueueObjectPool,00:00:03.9883122,00:00:04.0076765,00:00:03.9682549,00:00:03.9925014,00:00:15.9567450, 5 | Repeat 2400000 for size 4,OptimisticArrayObjectPool,00:00:03.9568135,00:00:04.2170893,00:00:04.0558332,00:00:03.9863018,00:00:16.2160378, 6 | Repeat 2400000 for size 4,InterlockedArrayObjectPool,00:00:03.9797846,00:00:04.2078410,00:00:04.0431276,00:00:03.9695552,00:00:16.2003084, 7 | Repeat 960000 for size 10,Microsoft.Extensions.ObjectPool.DefaultObjectPool,00:00:02.4087556,00:00:02.5079212,00:00:02.4920962,00:00:02.4665527,00:00:09.8753257, 8 | Repeat 960000 for size 10,ConcurrentQueueObjectPoolSlim,00:00:02.4513135,00:00:02.6173013,00:00:02.5037361,00:00:02.5337465,00:00:10.1060974, 9 | Repeat 960000 for size 10,ConcurrentQueueObjectPool,00:00:02.5503897,00:00:02.6621958,00:00:02.5548883,00:00:02.5976682,00:00:10.3651420, 10 | Repeat 960000 for size 10,OptimisticArrayObjectPool,00:00:02.4579233,00:00:02.7069571,00:00:02.4949206,00:00:02.4763234,00:00:10.1361244, 11 | Repeat 960000 for size 10,InterlockedArrayObjectPool,00:00:02.5692140,00:00:02.7507886,00:00:02.5367601,00:00:02.5870651,00:00:10.4438278, 12 | Repeat 288000 for size 50,Microsoft.Extensions.ObjectPool.DefaultObjectPool,00:00:01.3692996,00:00:01.3587399,00:00:01.0988709,00:00:01.2363235,00:00:05.0632339, 13 | Repeat 288000 for size 50,ConcurrentQueueObjectPoolSlim,00:00:01.4405049,00:00:01.4442130,00:00:01.2083265,00:00:01.2814624,00:00:05.3745068, 14 | Repeat 288000 for size 50,ConcurrentQueueObjectPool,00:00:01.5758473,00:00:01.5021273,00:00:01.3193568,00:00:01.3970673,00:00:05.7943987, 15 | Repeat 288000 for size 50,OptimisticArrayObjectPool,00:00:01.0780899,00:00:01.2999000,00:00:01.1287828,00:00:01.1037828,00:00:04.6105555, 16 | Repeat 288000 for size 50,InterlockedArrayObjectPool,00:00:01.1225109,00:00:01.2954306,00:00:01.1756768,00:00:01.1486593,00:00:04.7422776, 17 | Repeat 192000 for size 100,Microsoft.Extensions.ObjectPool.DefaultObjectPool,00:00:01.8633666,00:00:01.6135337,00:00:01.0777082,00:00:01.4542875,00:00:06.0088960, 18 | Repeat 192000 for size 100,ConcurrentQueueObjectPoolSlim,00:00:01.9024162,00:00:01.6790522,00:00:01.2862753,00:00:01.5210385,00:00:06.3887822, 19 | Repeat 192000 for size 100,ConcurrentQueueObjectPool,00:00:02.0542650,00:00:01.7484990,00:00:01.4418580,00:00:01.6489656,00:00:06.8935876, 20 | Repeat 192000 for size 100,OptimisticArrayObjectPool,00:00:00.8471340,00:00:01.0865546,00:00:00.9761332,00:00:00.8651384,00:00:03.7749602, 21 | Repeat 192000 for size 100,InterlockedArrayObjectPool,00:00:00.9431637,00:00:01.1474065,00:00:01.0727406,00:00:00.9714928,00:00:04.1348036, 22 | Repeat 153600 for size 250,Microsoft.Extensions.ObjectPool.DefaultObjectPool,00:00:04.0471422,00:00:03.2658468,00:00:01.8253285,00:00:03.0743397,00:00:12.2126572, 23 | Repeat 153600 for size 250,ConcurrentQueueObjectPoolSlim,00:00:04.0982231,00:00:03.2845860,00:00:02.1882361,00:00:03.0712649,00:00:12.6423101, 24 | Repeat 153600 for size 250,ConcurrentQueueObjectPool,00:00:04.1283099,00:00:03.2866676,00:00:02.3430258,00:00:03.1057312,00:00:12.8637345, 25 | Repeat 153600 for size 250,OptimisticArrayObjectPool,00:00:00.8952769,00:00:01.2805030,00:00:01.2960246,00:00:01.0256400,00:00:04.4974445, 26 | Repeat 153600 for size 250,InterlockedArrayObjectPool,00:00:01.0063385,00:00:01.3870075,00:00:01.4698480,00:00:01.1912251,00:00:05.0544191, 27 | 0:02.0767858,00:00:00.6488593,00:00:09.9478764, 28 | Repeat 12800 for size 2000,ConcurrentStackObjectPool,00:00:00.6708805,00:00:02.5628201,00:00:04.7978301,00:00:04.7563902,00:00:03.4723738,00:00:00.6475781,00:00:16.9078728, 29 | Repeat 12800 for size 2000,OptimisticArrayObjectPool,00:00:00.7184635,00:00:07.2485577,00:00:07.4304114,00:00:01.1244839,00:00:04.7572699,00:00:15.8850338,00:00:37.1642202, 30 | Repeat 12800 for size 2000,InterlockedArrayObjectPool,00:00:01.2202062,00:00:19.5837353,00:00:08.9766760,00:00:01.0202604,00:00:12.8356061,00:01:23.7957059,00:02:07.4321899, 31 | 0:00:00.7628823,00:00:01.6182833,00:00:01.4624384,00:00:00.8960254,00:00:04.7396294, 32 | Repeat 14400 for size 500,ConcurrentQueueObjectPool,00:00:00.8285180,00:00:00.9902911,00:00:00.8006946,00:00:01.1172107,00:00:03.7367144, 33 | Repeat 14400 for size 500,ConcurrentStackObjectPool,00:00:00.6978419,00:00:02.2585037,00:00:02.4760289,00:00:02.8394301,00:00:08.2718046, 34 | Repeat 14400 for size 500,OptimisticArrayObjectPool,00:00:01.0721065,00:00:05.6089585,00:00:00.5478712,00:00:22.2804453,00:00:29.5093815, 35 | Repeat 14400 for size 500,InterlockedArrayObjectPool,00:00:01.1338229,00:00:12.8073240,00:00:00.7613453,00:00:32.4547747,00:00:47.1572669, 36 | -------------------------------------------------------------------------------- /benchmarking/BenchmarkResult.txt: -------------------------------------------------------------------------------- 1 | Repeat 2400000 for size 4 2 | ------------------------------------ 3 | 4 | Microsoft.Extensions.ObjectPool.DefaultObjectPool....... 5 | 00:00:03.8980333 Give To (In Parallel) 6 | 00:00:03.9366455 Mixed 90%-Take/10%-Give (In Parallel) 7 | 00:00:03.9072529 Mixed 50%-Take/50%-Give (In Parallel) 8 | 00:00:03.8850033 Mixed 10%-Take/90%-Give (In Parallel) 9 | 00:00:15.6269350 TOTAL 10 | 11 | ConcurrentQueueObjectPoolSlim........................... 12 | 00:00:03.8494058 Give To (In Parallel) 13 | 00:00:03.9230416 Mixed 90%-Take/10%-Give (In Parallel) 14 | 00:00:03.8901577 Mixed 50%-Take/50%-Give (In Parallel) 15 | 00:00:03.8867848 Mixed 10%-Take/90%-Give (In Parallel) 16 | 00:00:15.5493899 TOTAL 17 | 18 | ConcurrentQueueObjectPool............................... 19 | 00:00:03.9883122 Give To (In Parallel) 20 | 00:00:04.0076765 Mixed 90%-Take/10%-Give (In Parallel) 21 | 00:00:03.9682549 Mixed 50%-Take/50%-Give (In Parallel) 22 | 00:00:03.9925014 Mixed 10%-Take/90%-Give (In Parallel) 23 | 00:00:15.9567450 TOTAL 24 | 25 | OptimisticArrayObjectPool............................... 26 | 00:00:03.9568135 Give To (In Parallel) 27 | 00:00:04.2170893 Mixed 90%-Take/10%-Give (In Parallel) 28 | 00:00:04.0558332 Mixed 50%-Take/50%-Give (In Parallel) 29 | 00:00:03.9863018 Mixed 10%-Take/90%-Give (In Parallel) 30 | 00:00:16.2160378 TOTAL 31 | 32 | InterlockedArrayObjectPool.............................. 33 | 00:00:03.9797846 Give To (In Parallel) 34 | 00:00:04.2078410 Mixed 90%-Take/10%-Give (In Parallel) 35 | 00:00:04.0431276 Mixed 50%-Take/50%-Give (In Parallel) 36 | 00:00:03.9695552 Mixed 10%-Take/90%-Give (In Parallel) 37 | 00:00:16.2003084 TOTAL 38 | 39 | 40 | Repeat 960000 for size 10 41 | ------------------------------------ 42 | 43 | Microsoft.Extensions.ObjectPool.DefaultObjectPool....... 44 | 00:00:02.4087556 Give To (In Parallel) 45 | 00:00:02.5079212 Mixed 90%-Take/10%-Give (In Parallel) 46 | 00:00:02.4920962 Mixed 50%-Take/50%-Give (In Parallel) 47 | 00:00:02.4665527 Mixed 10%-Take/90%-Give (In Parallel) 48 | 00:00:09.8753257 TOTAL 49 | 50 | ConcurrentQueueObjectPoolSlim........................... 51 | 00:00:02.4513135 Give To (In Parallel) 52 | 00:00:02.6173013 Mixed 90%-Take/10%-Give (In Parallel) 53 | 00:00:02.5037361 Mixed 50%-Take/50%-Give (In Parallel) 54 | 00:00:02.5337465 Mixed 10%-Take/90%-Give (In Parallel) 55 | 00:00:10.1060974 TOTAL 56 | 57 | ConcurrentQueueObjectPool............................... 58 | 00:00:02.5503897 Give To (In Parallel) 59 | 00:00:02.6621958 Mixed 90%-Take/10%-Give (In Parallel) 60 | 00:00:02.5548883 Mixed 50%-Take/50%-Give (In Parallel) 61 | 00:00:02.5976682 Mixed 10%-Take/90%-Give (In Parallel) 62 | 00:00:10.3651420 TOTAL 63 | 64 | OptimisticArrayObjectPool............................... 65 | 00:00:02.4579233 Give To (In Parallel) 66 | 00:00:02.7069571 Mixed 90%-Take/10%-Give (In Parallel) 67 | 00:00:02.4949206 Mixed 50%-Take/50%-Give (In Parallel) 68 | 00:00:02.4763234 Mixed 10%-Take/90%-Give (In Parallel) 69 | 00:00:10.1361244 TOTAL 70 | 71 | InterlockedArrayObjectPool.............................. 72 | 00:00:02.5692140 Give To (In Parallel) 73 | 00:00:02.7507886 Mixed 90%-Take/10%-Give (In Parallel) 74 | 00:00:02.5367601 Mixed 50%-Take/50%-Give (In Parallel) 75 | 00:00:02.5870651 Mixed 10%-Take/90%-Give (In Parallel) 76 | 00:00:10.4438278 TOTAL 77 | 78 | 79 | Repeat 288000 for size 50 80 | ------------------------------------ 81 | 82 | Microsoft.Extensions.ObjectPool.DefaultObjectPool....... 83 | 00:00:01.3692996 Give To (In Parallel) 84 | 00:00:01.3587399 Mixed 90%-Take/10%-Give (In Parallel) 85 | 00:00:01.0988709 Mixed 50%-Take/50%-Give (In Parallel) 86 | 00:00:01.2363235 Mixed 10%-Take/90%-Give (In Parallel) 87 | 00:00:05.0632339 TOTAL 88 | 89 | ConcurrentQueueObjectPoolSlim........................... 90 | 00:00:01.4405049 Give To (In Parallel) 91 | 00:00:01.4442130 Mixed 90%-Take/10%-Give (In Parallel) 92 | 00:00:01.2083265 Mixed 50%-Take/50%-Give (In Parallel) 93 | 00:00:01.2814624 Mixed 10%-Take/90%-Give (In Parallel) 94 | 00:00:05.3745068 TOTAL 95 | 96 | ConcurrentQueueObjectPool............................... 97 | 00:00:01.5758473 Give To (In Parallel) 98 | 00:00:01.5021273 Mixed 90%-Take/10%-Give (In Parallel) 99 | 00:00:01.3193568 Mixed 50%-Take/50%-Give (In Parallel) 100 | 00:00:01.3970673 Mixed 10%-Take/90%-Give (In Parallel) 101 | 00:00:05.7943987 TOTAL 102 | 103 | OptimisticArrayObjectPool............................... 104 | 00:00:01.0780899 Give To (In Parallel) 105 | 00:00:01.2999000 Mixed 90%-Take/10%-Give (In Parallel) 106 | 00:00:01.1287828 Mixed 50%-Take/50%-Give (In Parallel) 107 | 00:00:01.1037828 Mixed 10%-Take/90%-Give (In Parallel) 108 | 00:00:04.6105555 TOTAL 109 | 110 | InterlockedArrayObjectPool.............................. 111 | 00:00:01.1225109 Give To (In Parallel) 112 | 00:00:01.2954306 Mixed 90%-Take/10%-Give (In Parallel) 113 | 00:00:01.1756768 Mixed 50%-Take/50%-Give (In Parallel) 114 | 00:00:01.1486593 Mixed 10%-Take/90%-Give (In Parallel) 115 | 00:00:04.7422776 TOTAL 116 | 117 | 118 | Repeat 192000 for size 100 119 | ------------------------------------ 120 | 121 | Microsoft.Extensions.ObjectPool.DefaultObjectPool....... 122 | 00:00:01.8633666 Give To (In Parallel) 123 | 00:00:01.6135337 Mixed 90%-Take/10%-Give (In Parallel) 124 | 00:00:01.0777082 Mixed 50%-Take/50%-Give (In Parallel) 125 | 00:00:01.4542875 Mixed 10%-Take/90%-Give (In Parallel) 126 | 00:00:06.0088960 TOTAL 127 | 128 | ConcurrentQueueObjectPoolSlim........................... 129 | 00:00:01.9024162 Give To (In Parallel) 130 | 00:00:01.6790522 Mixed 90%-Take/10%-Give (In Parallel) 131 | 00:00:01.2862753 Mixed 50%-Take/50%-Give (In Parallel) 132 | 00:00:01.5210385 Mixed 10%-Take/90%-Give (In Parallel) 133 | 00:00:06.3887822 TOTAL 134 | 135 | ConcurrentQueueObjectPool............................... 136 | 00:00:02.0542650 Give To (In Parallel) 137 | 00:00:01.7484990 Mixed 90%-Take/10%-Give (In Parallel) 138 | 00:00:01.4418580 Mixed 50%-Take/50%-Give (In Parallel) 139 | 00:00:01.6489656 Mixed 10%-Take/90%-Give (In Parallel) 140 | 00:00:06.8935876 TOTAL 141 | 142 | OptimisticArrayObjectPool............................... 143 | 00:00:00.8471340 Give To (In Parallel) 144 | 00:00:01.0865546 Mixed 90%-Take/10%-Give (In Parallel) 145 | 00:00:00.9761332 Mixed 50%-Take/50%-Give (In Parallel) 146 | 00:00:00.8651384 Mixed 10%-Take/90%-Give (In Parallel) 147 | 00:00:03.7749602 TOTAL 148 | 149 | InterlockedArrayObjectPool.............................. 150 | 00:00:00.9431637 Give To (In Parallel) 151 | 00:00:01.1474065 Mixed 90%-Take/10%-Give (In Parallel) 152 | 00:00:01.0727406 Mixed 50%-Take/50%-Give (In Parallel) 153 | 00:00:00.9714928 Mixed 10%-Take/90%-Give (In Parallel) 154 | 00:00:04.1348036 TOTAL 155 | 156 | 157 | Repeat 153600 for size 250 158 | ------------------------------------ 159 | 160 | Microsoft.Extensions.ObjectPool.DefaultObjectPool....... 161 | 00:00:04.0471422 Give To (In Parallel) 162 | 00:00:03.2658468 Mixed 90%-Take/10%-Give (In Parallel) 163 | 00:00:01.8253285 Mixed 50%-Take/50%-Give (In Parallel) 164 | 00:00:03.0743397 Mixed 10%-Take/90%-Give (In Parallel) 165 | 00:00:12.2126572 TOTAL 166 | 167 | ConcurrentQueueObjectPoolSlim........................... 168 | 00:00:04.0982231 Give To (In Parallel) 169 | 00:00:03.2845860 Mixed 90%-Take/10%-Give (In Parallel) 170 | 00:00:02.1882361 Mixed 50%-Take/50%-Give (In Parallel) 171 | 00:00:03.0712649 Mixed 10%-Take/90%-Give (In Parallel) 172 | 00:00:12.6423101 TOTAL 173 | 174 | ConcurrentQueueObjectPool............................... 175 | 00:00:04.1283099 Give To (In Parallel) 176 | 00:00:03.2866676 Mixed 90%-Take/10%-Give (In Parallel) 177 | 00:00:02.3430258 Mixed 50%-Take/50%-Give (In Parallel) 178 | 00:00:03.1057312 Mixed 10%-Take/90%-Give (In Parallel) 179 | 00:00:12.8637345 TOTAL 180 | 181 | OptimisticArrayObjectPool............................... 182 | 00:00:00.8952769 Give To (In Parallel) 183 | 00:00:01.2805030 Mixed 90%-Take/10%-Give (In Parallel) 184 | 00:00:01.2960246 Mixed 50%-Take/50%-Give (In Parallel) 185 | 00:00:01.0256400 Mixed 10%-Take/90%-Give (In Parallel) 186 | 00:00:04.4974445 TOTAL 187 | 188 | InterlockedArrayObjectPool.............................. 189 | 00:00:01.0063385 Give To (In Parallel) 190 | 00:00:01.3870075 Mixed 90%-Take/10%-Give (In Parallel) 191 | 00:00:01.4698480 Mixed 50%-Take/50%-Give (In Parallel) 192 | 00:00:01.1912251 Mixed 10%-Take/90%-Give (In Parallel) 193 | 00:00:05.0544191 TOTAL 194 | 195 | 196 | -------------------------------------------------------------------------------- /benchmarking/ConsoleReport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace Open.Disposable.ObjectPools; 6 | 7 | public class ConsoleReport(TextWriter output = null) 8 | : Diagnostics.BenchmarkConsoleReport>>(ITERATIONS, output, Benchmark.Results) 9 | where T : class 10 | { 11 | const int ITERATIONS = 100000; 12 | 13 | public ConsoleReport(StringBuilder output) 14 | : this(new StringWriter(output)) 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /benchmarking/DefaultObjectPool.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.ObjectPool; 2 | using System; 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | #nullable enable 6 | 7 | namespace Open.Disposable.ObjectPools; 8 | 9 | public class DefaultObjectPool(Func factory, int capacity = 64) 10 | : Microsoft.Extensions.ObjectPool.DefaultObjectPool(new Policy(factory), capacity), IObjectPool 11 | where T : class 12 | { 13 | class Policy(Func factory) : IPooledObjectPolicy 14 | { 15 | private readonly Func _factory = factory ?? throw new ArgumentNullException(nameof(factory)); 16 | 17 | public T Create() => _factory(); 18 | 19 | public bool Return(T obj) 20 | // recycler?.Invoke(obj); 21 | => true; 22 | } 23 | 24 | public int Capacity { get; } 25 | 26 | public int Count => throw new NotImplementedException(); 27 | 28 | [SuppressMessage("Usage", "CA1816:Dispose methods should call SuppressFinalize")] 29 | public void Dispose() 30 | { 31 | } 32 | 33 | public T Generate() => factory(); 34 | 35 | public void Give(T item) => Return(item); 36 | 37 | public T Take() => Get(); 38 | 39 | public bool TryTake([NotNullWhen(true)] out T item) 40 | { 41 | item = Get(); 42 | return true; 43 | } 44 | 45 | public T? TryTake() => Get(); 46 | } 47 | -------------------------------------------------------------------------------- /benchmarking/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "", Scope = "member", Target = "~M:Open.Disposable.ObjectPools.DefaultObjectPool`1.Dispose")] 9 | -------------------------------------------------------------------------------- /benchmarking/OldTest.cs: -------------------------------------------------------------------------------- 1 | using Open.Collections; 2 | using System; 3 | using System.Collections.Concurrent; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace Open.Disposable 8 | { 9 | public class OldTest 10 | { 11 | static void Test() 12 | { 13 | var ts = new CancellationTokenSource(); 14 | ts.Cancel(); 15 | var task = Task.FromResult(ts.Token); 16 | 17 | 18 | var pool = BufferBlockObjectPool.Create(() => new object(), 1024); 19 | var trimmer = new ObjectPoolAutoTrimmer(20, pool); 20 | var clearer = new ObjectPoolAutoTrimmer(0, pool, TimeSpan.FromSeconds(5)); 21 | var tank = new ConcurrentBag(); 22 | 23 | int count = 0; 24 | while (true) 25 | { 26 | Console.WriteLine("Before {0}: {1}, {2}", count, pool.Count, tank.Count); 27 | 28 | count++; 29 | tank.Add(pool.Take()); 30 | count++; 31 | tank.Add(new object()); 32 | count++; 33 | tank.Add(new object()); 34 | foreach (var o in tank.TryTakeWhile(c => c.Count > 30)) 35 | pool.Give(o); 36 | 37 | Console.WriteLine("After {0}: {1}, {2}", count, pool.Count, tank.Count); 38 | 39 | if (count % 30 == 0) 40 | { 41 | Thread.Sleep(1000); 42 | } 43 | 44 | if (count % 40 == 0) 45 | { 46 | Console.WriteLine("-----------------"); 47 | Thread.Sleep(11000); 48 | } 49 | 50 | Console.WriteLine(); 51 | 52 | } 53 | 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /benchmarking/Open.Disposable.ObjectPools.Benchmarking.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | Open.Disposable 7 | 1.0.0 8 | IDE0130; 9 | 10 | 11 | 12 | latest 13 | 14 | 15 | 16 | latest 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /benchmarking/Program.cs: -------------------------------------------------------------------------------- 1 | using Open.Text.CSV; 2 | using System; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace Open.Disposable.ObjectPools.Benchmarks; 7 | 8 | static class Program 9 | { 10 | static void Main() 11 | { 12 | Console.Write("Initializing..."); 13 | 14 | var sb = new StringBuilder(); 15 | var report = new ConsoleReport(sb); 16 | 17 | /* 18 | * Notes: 19 | * 1) ConcurrentBag seems to be PAINFULLY slow compared to ConcurrentQueue. 20 | * 2) The speed gap between an optimistic array and its interlocked couterpart grows large very quickly (as expected). 21 | * 3) A sync locked Queue is faster than a sync locked LinkedList. 22 | * 4) ConcurrentQueue seems to be the overall winner when dealing with pools larger than 100 but is the clear loser for very small sizes. 23 | */ 24 | 25 | // Start with a baseline... 26 | //report.AddBenchmark("QueueObjectPool", // Note, that this one isn't far off from the following in peformance. 27 | // count => () => QueueObjectPool.Create((int)count * 2)); 28 | 29 | report.AddBenchmark("Microsoft.Extensions.ObjectPool.DefaultObjectPool", 30 | count => () => new DefaultObjectPool(() => new object(), (int)count * 2)); 31 | 32 | report.AddBenchmark("ConcurrentQueueObjectPoolSlim", 33 | count => () => ConcurrentQueueObjectPoolSlim.Create((int)count * 2)); 34 | 35 | report.AddBenchmark("ConcurrentQueueObjectPool", 36 | count => () => ConcurrentQueueObjectPool.Create((int)count * 2)); 37 | 38 | //report.AddBenchmark("ConcurrentStackObjectPool", 39 | // count => () => ConcurrentStackObjectPool.Create((int)count * 2)); 40 | 41 | report.AddBenchmark("OptimisticArrayObjectPool", 42 | count => () => OptimisticArrayObjectPool.Create((int)count * 2)); 43 | 44 | // Is ineveitably slower than the above but should be enabled for testing code changes. 45 | report.AddBenchmark("InterlockedArrayObjectPool", 46 | count => () => InterlockedArrayObjectPool.Create((int)count * 2)); 47 | 48 | report.Pretest(200, 200); // Run once through first to scramble/warm-up initial conditions. 49 | 50 | Console.SetCursorPosition(0, Console.CursorTop); 51 | 52 | const int loopMultiple = 12; 53 | report.Test(4, 8 * loopMultiple); 54 | report.Test(10, 8 * loopMultiple); 55 | report.Test(50, 12 * loopMultiple); 56 | report.Test(100, 16 * loopMultiple); 57 | report.Test(250, 32 * loopMultiple); 58 | //report.Test(2000, 64 * loopMultiple); 59 | 60 | File.WriteAllText("./BenchmarkResult.txt", sb.ToString()); 61 | using (var fs = File.OpenWrite("./BenchmarkResult.csv")) 62 | using (var sw = new StreamWriter(fs)) 63 | using (var csv = new CsvWriter(sw)) 64 | { 65 | csv.WriteRow(report.ResultLabels); 66 | csv.WriteRows(report.Results); 67 | } 68 | 69 | Console.Beep(); 70 | Console.WriteLine("(press any key when finished)"); 71 | Console.ReadKey(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /source/Array/InterlockedArrayObjectPool.cs: -------------------------------------------------------------------------------- 1 | /* Based on Roslyn's ObjectPool */ 2 | 3 | using System; 4 | using System.Runtime.CompilerServices; 5 | using System.Threading; 6 | 7 | namespace Open.Disposable; 8 | 9 | /// 10 | /// An extremely fast ObjectPool when the capacity is in the low 100s. 11 | /// 12 | public class InterlockedArrayObjectPool 13 | : ObjectPoolBase 14 | where T : class 15 | { 16 | public InterlockedArrayObjectPool( 17 | Func factory, 18 | Action? recycler, 19 | Action? disposer, 20 | int capacity = DEFAULT_CAPACITY) 21 | : base(factory, recycler, disposer, capacity) 22 | => Pool = new ReferenceContainer[capacity - 1]; 23 | 24 | public InterlockedArrayObjectPool( 25 | Func factory, 26 | int capacity = DEFAULT_CAPACITY) 27 | : this(factory, null, null, capacity) { } 28 | 29 | protected Memory> Pool; 30 | 31 | // Sets a limit on what has been stored yet to prevent over searching the array unnecessarily.. 32 | protected int MaxStored; 33 | protected const int MaxStoredIncrement = 5; // Instead of every one. 34 | 35 | public override int Count 36 | { 37 | get 38 | { 39 | var p = Pool.Span; 40 | int count = PocketCount; 41 | foreach(var e in p) 42 | if (e.Value is not null) count++; 43 | 44 | return count; 45 | } 46 | } 47 | 48 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 49 | protected virtual bool Store(ReadOnlySpan> p, T item, int index) 50 | => p[index].TrySave(item); 51 | 52 | protected override bool Receive(T item) 53 | { 54 | var elements = Pool; 55 | var len = elements.Length; 56 | var span = elements.Span; 57 | 58 | for (var i = 0; i < len; i++) 59 | { 60 | if (!Store(span, item, i)) continue; 61 | var m = MaxStored; 62 | if (i >= m) Interlocked.CompareExchange(ref MaxStored, m + MaxStoredIncrement, m); 63 | return true; 64 | } 65 | 66 | return false; 67 | } 68 | 69 | protected override T? TryRelease() 70 | { 71 | // We missed getting the first item or it wasn't there. 72 | var elements = Pool.Span; 73 | var len = elements.Length; 74 | 75 | for (var i = 0; i < len && i < MaxStored; i++) 76 | { 77 | var item = elements[i].TryRetrieve(); 78 | if (item is not null) return item; 79 | } 80 | 81 | return null; 82 | } 83 | 84 | protected override void OnDispose() 85 | { 86 | base.OnDispose(); 87 | Pool = Array.Empty>(); 88 | MaxStored = 0; 89 | } 90 | } 91 | 92 | public static class InterlockedArrayObjectPool 93 | { 94 | public static InterlockedArrayObjectPool Create(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 95 | where T : class => new(factory, capacity); 96 | 97 | public static InterlockedArrayObjectPool Create(int capacity = Constants.DEFAULT_CAPACITY) 98 | where T : class, new() => Create(() => new T(), capacity); 99 | 100 | public static InterlockedArrayObjectPool CreateAutoRecycle(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 101 | where T : class, IRecyclable => new(factory, Recycler.Recycle, null, capacity); 102 | 103 | public static InterlockedArrayObjectPool CreateAutoRecycle(int capacity = Constants.DEFAULT_CAPACITY) 104 | where T : class, IRecyclable, new() => CreateAutoRecycle(() => new T(), capacity); 105 | 106 | public static InterlockedArrayObjectPool CreateAutoDisposal(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 107 | where T : class, IDisposable => new(factory, null, d => d.Dispose(), capacity); 108 | 109 | public static InterlockedArrayObjectPool CreateAutoDisposal(int capacity = Constants.DEFAULT_CAPACITY) 110 | where T : class, IDisposable, new() => CreateAutoDisposal(() => new T(), capacity); 111 | } 112 | -------------------------------------------------------------------------------- /source/Array/OptimisticArrayObjectPool.cs: -------------------------------------------------------------------------------- 1 | /* Based on Roslyn's ObjectPool */ 2 | 3 | using System; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace Open.Disposable; 7 | 8 | /// 9 | /// An extremely fast ObjectPool when the capacity is in the low 100s. 10 | /// 11 | public class OptimisticArrayObjectPool 12 | : InterlockedArrayObjectPool 13 | where T : class 14 | { 15 | public OptimisticArrayObjectPool( 16 | Func factory, 17 | Action? recycler, 18 | int capacity = DEFAULT_CAPACITY) 19 | : base(factory, recycler, null /* disposer not applicable here */, capacity) { } 20 | 21 | public OptimisticArrayObjectPool( 22 | Func factory, 23 | int capacity = DEFAULT_CAPACITY) 24 | : this(factory, null, capacity) { } 25 | 26 | // As suggested by Roslyn's implementation, don't worry about interlocking here. It's okay if a few get loose. 27 | 28 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 29 | protected override bool SaveToPocket(T item) 30 | => Pocket.SetIfNull(item); 31 | 32 | // As suggested by Roslyn's implementation, don't worry about interlocking here. It's okay if a few get loose. 33 | 34 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 35 | protected override bool Store(ReadOnlySpan> p, T item, int index) 36 | => p[index].SetIfNull(item); 37 | } 38 | 39 | public static class OptimisticArrayObjectPool 40 | { 41 | public static OptimisticArrayObjectPool Create(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 42 | where T : class => new(factory, capacity); 43 | 44 | public static OptimisticArrayObjectPool Create(int capacity = Constants.DEFAULT_CAPACITY) 45 | where T : class, new() => Create(() => new T(), capacity); 46 | 47 | public static OptimisticArrayObjectPool CreateAutoRecycle(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 48 | where T : class, IRecyclable => new(factory, Recycler.Recycle, capacity); 49 | 50 | public static OptimisticArrayObjectPool CreateAutoRecycle(int capacity = Constants.DEFAULT_CAPACITY) 51 | where T : class, IRecyclable, new() => CreateAutoRecycle(() => new T(), capacity); 52 | } 53 | -------------------------------------------------------------------------------- /source/Channels/ChannelObjectPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Channels; 3 | 4 | namespace Open.Disposable 5 | { 6 | public sealed class ChannelObjectPool : ObjectPoolBase 7 | where T : class 8 | { 9 | public ChannelObjectPool(Func factory, Action recycler, Action disposer, int capacity = DEFAULT_CAPACITY) 10 | : base(factory, recycler, disposer, capacity) 11 | { 12 | Pool = Channel.CreateBounded(new BoundedChannelOptions(capacity) { FullMode = BoundedChannelFullMode.DropWrite }); 13 | } 14 | 15 | public ChannelObjectPool(Func factory, int capacity = DEFAULT_CAPACITY) 16 | : this(factory, null, null, capacity) 17 | { } 18 | 19 | Channel Pool; 20 | 21 | public override int Count => -1; 22 | 23 | protected override void OnDispose(bool calledExplicitly) 24 | { 25 | Pool?.Writer.TryComplete(); 26 | Pool = null; 27 | } 28 | 29 | protected override bool Receive(T item) 30 | => Pool?.Writer.TryWrite(item) ?? false; 31 | 32 | protected override T TryRelease() 33 | { 34 | T item = null; 35 | Pool?.Reader.TryRead(out item); 36 | return item; 37 | } 38 | } 39 | 40 | 41 | public static class ChannelObjectPool 42 | { 43 | public static ChannelObjectPool Create(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 44 | where T : class 45 | { 46 | return new ChannelObjectPool(factory, capacity); 47 | } 48 | 49 | 50 | public static ChannelObjectPool Create(int capacity = Constants.DEFAULT_CAPACITY) 51 | where T : class, new() 52 | { 53 | return Create(() => new T(), capacity); 54 | } 55 | 56 | public static ChannelObjectPool Create(Func factory, bool autoRecycle, int capacity = Constants.DEFAULT_CAPACITY) 57 | where T : class, IRecyclable 58 | { 59 | Action recycler = null; 60 | if (autoRecycle) recycler = Recycler.Recycle; 61 | return new ChannelObjectPool(factory, recycler, null, capacity); 62 | } 63 | 64 | public static ChannelObjectPool Create(bool autoRecycle, int capacity = Constants.DEFAULT_CAPACITY) 65 | where T : class, IRecyclable, new() 66 | { 67 | return Create(() => new T(), autoRecycle, capacity); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /source/Channels/ChanneledRecycler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Channels; 3 | using System.Threading.Tasks; 4 | 5 | namespace Open.Disposable 6 | { 7 | /// 8 | /// This class is provided as an asynchronous queue for recycling instead of using a recycle delegate with an object pool and calling GiveAsync() which could pile up unnecessarily. 9 | /// So if recycling an object takes extra time, this might be a good way to toss objects away and not have to worry about the heavy cost as they will one by one be processed back into the target pool. 10 | /// 11 | public sealed class ChanneledRecycler : RecyclerBase 12 | where T : class 13 | { 14 | // Something else could be used and could be more performant. 15 | // But it's an ideal interface for what's needed. And the point is that the recyler should not take up too much cpu time. 16 | Channel _bin; 17 | 18 | internal ChanneledRecycler( 19 | IObjectPool target, 20 | Action recycleFunction, 21 | ushort limit = Constants.DEFAULT_CAPACITY) : base(target, recycleFunction) 22 | { 23 | var b = _bin = Channel.CreateBounded(new BoundedChannelOptions(limit) 24 | { 25 | FullMode = BoundedChannelFullMode.DropWrite 26 | }); 27 | 28 | async Task ProcessAsync() 29 | { 30 | var reader = _bin.Reader; 31 | while (Target != null 32 | && await reader.WaitToReadAsync().ConfigureAwait(false)) 33 | { 34 | while (Target != null 35 | && reader.TryRead(out T item)) 36 | { 37 | recycleFunction(item); 38 | Target?.Give(item); 39 | } 40 | } 41 | } 42 | 43 | Completion = ProcessAsync().ContinueWith( 44 | t => t.IsCompleted 45 | ? b.Reader.Completion 46 | : t) 47 | .Unwrap(); 48 | } 49 | 50 | internal ChanneledRecycler( 51 | ushort limit, 52 | IObjectPool pool, 53 | Action recycleFunction) : this(pool, recycleFunction, limit) 54 | { 55 | 56 | } 57 | 58 | public override bool Recycle(T item) 59 | => _bin?.Writer.TryWrite(item) ?? false; 60 | 61 | protected override void OnCloseRequested() 62 | => _bin?.Writer.Complete(); 63 | 64 | protected override void OnDispose(bool calledExplicitly) 65 | { 66 | _bin?.Writer.Complete(); 67 | _bin = null; 68 | Target = null; 69 | } 70 | } 71 | 72 | public static class ChanneledRecycler 73 | { 74 | public static ChanneledRecycler CreateRecycler( 75 | this IObjectPool pool, 76 | Action recycleFunction, 77 | ushort limit = Constants.DEFAULT_CAPACITY) 78 | where T : class 79 | { 80 | return new ChanneledRecycler(pool, recycleFunction, limit); 81 | } 82 | 83 | public static ChanneledRecycler CreateRecycler( 84 | this IObjectPool pool, 85 | ushort limit, 86 | Action recycleFunction) 87 | where T : class 88 | { 89 | return new ChanneledRecycler(pool, recycleFunction, limit); 90 | } 91 | 92 | public static void Recycle(IRecyclable r) 93 | { 94 | r.Recycle(); 95 | } 96 | 97 | public static ChanneledRecycler CreateRecycler( 98 | this IObjectPool pool, 99 | ushort limit = Constants.DEFAULT_CAPACITY) 100 | where T : class, IRecyclable 101 | { 102 | return new ChanneledRecycler(pool, Recycle, limit); 103 | } 104 | 105 | } 106 | 107 | 108 | } 109 | -------------------------------------------------------------------------------- /source/Collection/CollectionWrapperObjectPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Open.Disposable; 6 | 7 | public class CollectionWrapperObjectPool 8 | : TrimmableGenericCollectionObjectPoolBase 9 | where T : class 10 | where TCollection : class, ICollection 11 | { 12 | public CollectionWrapperObjectPool( 13 | TCollection pool, 14 | Func factory, 15 | Action? recycler, 16 | Action? disposer, 17 | int capacity = DEFAULT_CAPACITY, 18 | bool countTrackingEnabled = true) 19 | : base(pool, factory, recycler, disposer, capacity, countTrackingEnabled) { } 20 | 21 | public CollectionWrapperObjectPool( 22 | TCollection pool, 23 | Func factory, 24 | int capacity = DEFAULT_CAPACITY, 25 | bool countTrackingEnabled = true) 26 | : this(pool, factory, null, null, capacity, countTrackingEnabled) { } 27 | 28 | protected override bool Receive(T item) 29 | { 30 | var p = Pool; 31 | if (p is null) return false; 32 | lock (SyncRoot) 33 | { 34 | // It's possible that the count could exceed MaxSize here, but the risk is negligble as a few over the limit won't hurt. 35 | // The lock operation should be quick enough to not pile up too many items. 36 | p.Add(item); 37 | } 38 | return true; 39 | } 40 | 41 | protected override T? TryRelease() 42 | { 43 | retry: 44 | 45 | var p = Pool; 46 | var item = p?.FirstOrDefault(); 47 | if (item is null) return null; 48 | /* Removing the first item is typically horribly inefficient but we can't make assumptions about the implementation here. 49 | * It's a trade off between potentially iterating the entire collection before removing the last item, or relying on the underlying implementation. 50 | * This implementation is in place for reference more than practice. Sub classes should override. */ 51 | 52 | bool wasRemoved; 53 | lock (SyncRoot) wasRemoved = p!.Remove(item); 54 | if (!wasRemoved) goto retry; 55 | 56 | return item; 57 | } 58 | } 59 | 60 | public class CollectionWrapperObjectPool 61 | : CollectionWrapperObjectPool> 62 | where T : class 63 | { 64 | public CollectionWrapperObjectPool( 65 | ICollection pool, 66 | Func factory, 67 | int capacity = DEFAULT_CAPACITY) 68 | : base(pool, factory, capacity) { } 69 | } 70 | -------------------------------------------------------------------------------- /source/Collection/ConcurrentQueueObjectPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace Open.Disposable; 6 | 7 | public class ConcurrentQueueObjectPool 8 | : TrimmableCollectionObjectPoolBase> 9 | where T : class 10 | { 11 | public ConcurrentQueueObjectPool( 12 | Func factory, 13 | Action? recycler, 14 | Action? disposer, 15 | int capacity = DEFAULT_CAPACITY) 16 | : base(new ConcurrentQueue(), factory, recycler, disposer, capacity) { } 17 | 18 | public ConcurrentQueueObjectPool( 19 | Func factory, 20 | int capacity = DEFAULT_CAPACITY) 21 | : this(factory, null, null, capacity) { } 22 | 23 | /* 24 | * NOTE: ConcurrentQueue is very fast and will perform quite well without using the 'Pocket' feature. 25 | * Benchmarking reveals that mixed read/writes (what really matters) are still faster with the pocket enabled so best to keep it so. 26 | */ 27 | 28 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 29 | protected override bool Receive(T item) 30 | { 31 | var p = Pool; 32 | if (p is null) return false; 33 | p.Enqueue(item); // It's possible that the count could exceed MaxSize here, but the risk is negligble as a few over the limit won't hurt. 34 | return true; 35 | } 36 | 37 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 38 | protected override T? TryRelease() 39 | { 40 | var p = Pool; 41 | if (p is null) return null; 42 | p.TryDequeue(out var item); 43 | return item; 44 | } 45 | } 46 | public static class ConcurrentQueueObjectPool 47 | { 48 | public static ConcurrentQueueObjectPool Create(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 49 | where T : class => new(factory, capacity); 50 | 51 | public static ConcurrentQueueObjectPool Create(int capacity = Constants.DEFAULT_CAPACITY) 52 | where T : class, new() => Create(() => new T(), capacity); 53 | 54 | public static ConcurrentQueueObjectPool CreateAutoRecycle(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 55 | where T : class, IRecyclable => new(factory, Recycler.Recycle, null, capacity); 56 | 57 | public static ConcurrentQueueObjectPool CreateAutoRecycle(int capacity = Constants.DEFAULT_CAPACITY) 58 | where T : class, IRecyclable, new() => CreateAutoRecycle(() => new T(), capacity); 59 | 60 | public static ConcurrentQueueObjectPool CreateAutoDisposal(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 61 | where T : class, IDisposable => new(factory, null, d => d.Dispose(), capacity); 62 | 63 | public static ConcurrentQueueObjectPool CreateAutoDisposal(int capacity = Constants.DEFAULT_CAPACITY) 64 | where T : class, IDisposable, new() => CreateAutoDisposal(() => new T(), capacity); 65 | } 66 | -------------------------------------------------------------------------------- /source/Collection/ConcurrentQueueObjectPoolSlim.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Open.Disposable; 4 | 5 | public sealed class ConcurrentQueueObjectPoolSlim 6 | : ConcurrentQueueObjectPoolSlimBase 7 | where T : class 8 | { 9 | public ConcurrentQueueObjectPoolSlim( 10 | Func factory, 11 | Action? recycler, 12 | Action? disposer, 13 | int capacity = DEFAULT_CAPACITY - 10) 14 | : base(factory, recycler, disposer, capacity) { } 15 | 16 | public ConcurrentQueueObjectPoolSlim( 17 | Func factory, 18 | int capacity = DEFAULT_CAPACITY - 10) 19 | : this(factory, null, null, capacity) { } 20 | } 21 | 22 | public static class ConcurrentQueueObjectPoolSlim 23 | { 24 | public static ConcurrentQueueObjectPoolSlim Create(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 25 | where T : class => new(factory, capacity); 26 | 27 | public static ConcurrentQueueObjectPoolSlim Create(int capacity = Constants.DEFAULT_CAPACITY) 28 | where T : class, new() => Create(() => new T(), capacity); 29 | 30 | public static ConcurrentQueueObjectPoolSlim CreateAutoRecycle(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 31 | where T : class, IRecyclable => new(factory, Recycler.Recycle, null, capacity); 32 | 33 | public static ConcurrentQueueObjectPoolSlim CreateAutoRecycle(int capacity = Constants.DEFAULT_CAPACITY) 34 | where T : class, IRecyclable, new() => CreateAutoRecycle(() => new T(), capacity); 35 | 36 | public static ConcurrentQueueObjectPoolSlim CreateAutoDisposal(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 37 | where T : class, IDisposable => new(factory, null, d => d.Dispose(), capacity); 38 | 39 | public static ConcurrentQueueObjectPoolSlim CreateAutoDisposal(int capacity = Constants.DEFAULT_CAPACITY) 40 | where T : class, IDisposable, new() => CreateAutoDisposal(() => new T(), capacity); 41 | } 42 | -------------------------------------------------------------------------------- /source/Collection/ConcurrentQueueObjectPoolSlimBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace Open.Disposable; 6 | 7 | public abstract class ConcurrentQueueObjectPoolSlimBase 8 | : CountTrackedObjectPoolBase 9 | where T : class 10 | { 11 | protected ConcurrentQueueObjectPoolSlimBase( 12 | Func factory, 13 | Action? recycler, 14 | Action? disposer, 15 | int capacity = DEFAULT_CAPACITY - 10) 16 | : base(factory, recycler, disposer, capacity) 17 | => Pool = new(); 18 | 19 | ConcurrentQueue Pool; 20 | 21 | /* 22 | * NOTE: ConcurrentQueue is very fast and will perform quite well without using the 'Pocket' feature. 23 | * Benchmarking reveals that mixed read/writes (what really matters) are still faster with the pocket enabled so best to keep it so. 24 | */ 25 | 26 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 27 | protected override bool Receive(T item) 28 | { 29 | var p = Pool; 30 | if (p is null) return false; 31 | p.Enqueue(item); // It's possible that the count could exceed MaxSize here, but the risk is negligble as a few over the limit won't hurt. 32 | return true; 33 | } 34 | 35 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 36 | protected override T? TryRelease() 37 | { 38 | var p = Pool; 39 | if (p is null) return null; 40 | p.TryDequeue(out var item); 41 | return item; 42 | } 43 | 44 | protected override void OnDispose() 45 | { 46 | base.OnDispose(); 47 | Pool = null!; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /source/Collection/ConcurrentStackObjectPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | 4 | namespace Open.Disposable; 5 | 6 | public sealed class ConcurrentStackObjectPool 7 | : TrimmableCollectionObjectPoolBase> 8 | where T : class 9 | { 10 | public ConcurrentStackObjectPool( 11 | Func factory, 12 | Action? recycler, 13 | Action? disposer, 14 | int capacity = DEFAULT_CAPACITY) 15 | : base(new ConcurrentStack(), factory, recycler, disposer, capacity) { } 16 | 17 | public ConcurrentStackObjectPool(Func factory, int capacity = DEFAULT_CAPACITY) 18 | : this(factory, null, null, capacity) { } 19 | 20 | protected override bool Receive(T item) 21 | { 22 | var p = Pool; 23 | if (p is null) return false; 24 | p.Push(item); // It's possible that the count could exceed MaxSize here, but the risk is negligble as a few over the limit won't hurt. 25 | return true; 26 | } 27 | 28 | protected override T? TryRelease() 29 | { 30 | var p = Pool; 31 | if (p is null) return null; 32 | p.TryPop(out var item); 33 | return item; 34 | } 35 | } 36 | 37 | public static class ConcurrentStackObjectPool 38 | { 39 | public static ConcurrentStackObjectPool Create(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 40 | where T : class => new(factory, capacity); 41 | 42 | public static ConcurrentStackObjectPool Create(int capacity = Constants.DEFAULT_CAPACITY) 43 | where T : class, new() => Create(() => new T(), capacity); 44 | 45 | public static ConcurrentStackObjectPool CreateAutoRecycle(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 46 | where T : class, IRecyclable => new(factory, Recycler.Recycle, null, capacity); 47 | 48 | public static ConcurrentStackObjectPool CreateAutoRecycle(int capacity = Constants.DEFAULT_CAPACITY) 49 | where T : class, IRecyclable, new() => CreateAutoRecycle(() => new T(), capacity); 50 | 51 | public static ConcurrentStackObjectPool CreateAutoDisposal(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 52 | where T : class, IDisposable => new(factory, null, d => d.Dispose(), capacity); 53 | 54 | public static ConcurrentStackObjectPool CreateAutoDisposal(int capacity = Constants.DEFAULT_CAPACITY) 55 | where T : class, IDisposable, new() => CreateAutoDisposal(() => new T(), capacity); 56 | } 57 | -------------------------------------------------------------------------------- /source/Collection/QueueObjectPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Open.Disposable; 5 | 6 | public sealed class QueueObjectPool 7 | : TrimmableCollectionObjectPoolBase> 8 | where T : class 9 | { 10 | public QueueObjectPool( 11 | Func factory, 12 | Action? recycler, 13 | Action? disposer, 14 | int capacity = DEFAULT_CAPACITY) 15 | : base( 16 | new Queue(Math.Min(DEFAULT_CAPACITY, capacity)) /* Very very slight speed improvment when capacity is initially set. */, 17 | factory, recycler, disposer, capacity, false) 18 | { } 19 | 20 | public QueueObjectPool( 21 | Func factory, 22 | int capacity = DEFAULT_CAPACITY) 23 | : this(factory, null, null, capacity) { } 24 | 25 | protected override bool Receive(T item) 26 | { 27 | var p = Pool; 28 | if (p is not null) 29 | { 30 | // It's possible that the count could exceed MaxSize here, but the risk is negligble as a few over the limit won't hurt. 31 | // The lock operation should be quick enough to not pile up too many items. 32 | lock (SyncRoot) p.Enqueue(item); 33 | return true; 34 | } 35 | 36 | return false; 37 | } 38 | 39 | protected override T? TryRelease() 40 | { 41 | var p = Pool; 42 | if (p is not null && p.Count != 0) 43 | { 44 | lock (SyncRoot) 45 | { 46 | if (p.Count != 0) 47 | return p.Dequeue(); 48 | } 49 | } 50 | 51 | return null; 52 | } 53 | } 54 | 55 | public static class QueueObjectPool 56 | { 57 | public static QueueObjectPool Create(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 58 | where T : class => new(factory, capacity); 59 | 60 | public static QueueObjectPool Create(int capacity = Constants.DEFAULT_CAPACITY) 61 | where T : class, new() => Create(() => new T(), capacity); 62 | 63 | public static QueueObjectPool CreateAutoRecycle(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 64 | where T : class, IRecyclable => new(factory, Recycler.Recycle, null, capacity); 65 | 66 | public static QueueObjectPool CreateAutoRecycle(int capacity = Constants.DEFAULT_CAPACITY) 67 | where T : class, IRecyclable, new() => CreateAutoRecycle(() => new T(), capacity); 68 | 69 | public static QueueObjectPool CreateAutoDisposal(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 70 | where T : class, IDisposable => new(factory, null, d => d.Dispose(), capacity); 71 | 72 | public static QueueObjectPool CreateAutoDisposal(int capacity = Constants.DEFAULT_CAPACITY) 73 | where T : class, IDisposable, new() => CreateAutoDisposal(() => new T(), capacity); 74 | } 75 | -------------------------------------------------------------------------------- /source/Collection/StackObjectPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Open.Disposable; 5 | 6 | public sealed class StackObjectPool 7 | : TrimmableCollectionObjectPoolBase> 8 | where T : class 9 | { 10 | public StackObjectPool( 11 | Func factory, 12 | Action? recycler, 13 | Action? disposer, 14 | int capacity = DEFAULT_CAPACITY) 15 | : base( 16 | new Stack(Math.Min(DEFAULT_CAPACITY, capacity)) /* Very very slight speed improvment when capacity is set. */, 17 | factory, recycler, disposer, capacity, false) 18 | { } 19 | 20 | public StackObjectPool( 21 | Func factory, 22 | int capacity = DEFAULT_CAPACITY) 23 | : this(factory, null, null, capacity) { } 24 | 25 | protected override bool Receive(T item) 26 | { 27 | var p = Pool; 28 | if (p is not null) 29 | { 30 | // It's possible that the count could exceed MaxSize here, but the risk is negligble as a few over the limit won't hurt. 31 | // The lock operation should be quick enough to not pile up too many items. 32 | lock (SyncRoot) p.Push(item); 33 | return true; 34 | } 35 | 36 | return false; 37 | } 38 | 39 | protected override T? TryRelease() 40 | { 41 | var p = Pool; 42 | if (p is null || p.Count == 0) 43 | return null; 44 | 45 | lock (SyncRoot) 46 | { 47 | if (p.Count != 0) 48 | return p.Pop(); 49 | } 50 | 51 | return null; 52 | } 53 | } 54 | 55 | public static class StackObjectPool 56 | { 57 | public static StackObjectPool Create(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 58 | where T : class => new(factory, capacity); 59 | 60 | public static StackObjectPool Create(int capacity = Constants.DEFAULT_CAPACITY) 61 | where T : class, new() => Create(() => new T(), capacity); 62 | 63 | public static StackObjectPool CreateAutoRecycle(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 64 | where T : class, IRecyclable => new(factory, Recycler.Recycle, null, capacity); 65 | 66 | public static StackObjectPool CreateAutoRecycle(int capacity = Constants.DEFAULT_CAPACITY) 67 | where T : class, IRecyclable, new() => CreateAutoRecycle(() => new T(), capacity); 68 | 69 | public static StackObjectPool CreateAutoDisposal(Func factory, int capacity = Constants.DEFAULT_CAPACITY) 70 | where T : class, IDisposable => new(factory, null, d => d.Dispose(), capacity); 71 | 72 | public static StackObjectPool CreateAutoDisposal(int capacity = Constants.DEFAULT_CAPACITY) 73 | where T : class, IDisposable, new() => CreateAutoDisposal(() => new T(), capacity); 74 | } 75 | -------------------------------------------------------------------------------- /source/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace Open.Disposable; 2 | 3 | internal static class Constants 4 | { 5 | internal const int DEFAULT_CAPACITY = 64; // Should accomodate all object pools without decreasing their effectiveness. 6 | } 7 | -------------------------------------------------------------------------------- /source/CountTrackedObjectPoolBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Threading; 4 | 5 | namespace Open.Disposable; 6 | 7 | public abstract class CountTrackedObjectPoolBase 8 | : ObjectPoolBase 9 | where T : class 10 | { 11 | protected CountTrackedObjectPoolBase( 12 | Func factory, 13 | Action? recycler, 14 | Action? disposer, 15 | int capacity = DEFAULT_CAPACITY) 16 | : base(factory, recycler, disposer, capacity) { } 17 | 18 | int _count; 19 | /// 20 | public override int Count => _count; 21 | 22 | protected override bool CanReceive => _count < MaxSize; 23 | 24 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 25 | protected override void OnReleased() => Interlocked.Decrement(ref _count); 26 | 27 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 28 | protected override void OnReceived() => Interlocked.Increment(ref _count); 29 | } 30 | -------------------------------------------------------------------------------- /source/IObjectPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.Contracts; 4 | using System.Threading.Tasks; 5 | 6 | namespace Open.Disposable; 7 | 8 | public interface IObjectPool 9 | where T : class 10 | { 11 | /// 12 | /// Defines the maximum at which the pool can grow. 13 | /// 14 | int Capacity { get; } 15 | 16 | /// 17 | /// Directly calls the underlying factory that generates the items. (No pool interaction.) 18 | /// 19 | T Generate(); 20 | 21 | /// 22 | /// Receives an item and adds it to the pool. Ignores null references.
23 | /// WARNING: The item is considered 'dead' but resurrectable so be sure not to hold on to the item's reference. 24 | ///
25 | /// The item to give up to the pool. 26 | void Give(T item); 27 | 28 | /// 29 | /// If the pool has an item currently avaialable, removes it from the pool and provides it as the out parameter. 30 | /// 31 | /// The item to return if available. Will be null if none avaialable. 32 | /// True if an item is provided. 33 | #if NETSTANDARD2_1 34 | bool TryTake([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out T? item); 35 | #else 36 | bool TryTake(out T? item); 37 | #endif 38 | 39 | /// 40 | /// If the pool has an item currently avaialable, removes it from the pool and returns it. 41 | /// 42 | /// The item to return if available. Will be null if none avaialable. 43 | T? TryTake(); 44 | 45 | /// 46 | /// If the pool has an item currently avaialable, removes it from the pool and returns it. 47 | /// If none is available, it generates one. 48 | /// 49 | /// An item removed from the pool or generated. Should never be null. 50 | T Take(); 51 | 52 | /// 53 | /// Total number of items in the pool.
54 | /// Depending on the implementation could be an O(n) operation and should only be used for debugging. 55 | ///
56 | int Count { get; } 57 | } 58 | 59 | public static class ObjectPoolExtensions 60 | { 61 | /// 62 | /// Receives items and iteratively adds them to the pool.
63 | /// WARNING: These items are considered 'dead' but resurrectable so be sure not to hold on to their reference. 64 | ///
65 | /// The pool to give to. 66 | /// The items to give up to the pool. 67 | public static void Give(this IObjectPool target, IEnumerable items) 68 | where T : class 69 | { 70 | if (target is null) throw new ArgumentNullException(nameof(target)); 71 | Contract.EndContractBlock(); 72 | 73 | if (items is null) return; 74 | foreach (var i in items) 75 | target.Give(i); 76 | } 77 | 78 | /// 79 | /// Receives items and iteratively adds them to the pool.
80 | /// WARNING: These items are considered 'dead' but resurrectable so be sure not to hold on to their reference. 81 | ///
82 | /// The pool to give to. 83 | /// The first item to give up to the pool. 84 | /// The second item to give up to the pool. 85 | /// The remaining items to give up to the pool. 86 | public static void Give(this IObjectPool target, T item1, T item2, params T[] items) 87 | where T : class 88 | { 89 | if (target is null) throw new ArgumentNullException(nameof(target)); 90 | Contract.EndContractBlock(); 91 | 92 | target.Give(item1); 93 | target.Give(item2); 94 | target.Give(items); 95 | } 96 | 97 | /// 98 | /// Provides a disposable RecycleHelper that contains an item from the pool.
99 | /// Will be returned to the pool once .Dispose() is called. 100 | ///
101 | /// The object type from the pool. 102 | /// The object pool. 103 | /// A RecycleHelper containing an item from the pool. 104 | public static RecycleHelper Rent(this IObjectPool source) 105 | where T : class => new(source); 106 | 107 | /// 108 | /// Provides an item from the pool and returns it when the action sucessfully completes. 109 | /// 110 | /// The object type from the pool. 111 | /// The object pool. 112 | /// The action to execute. 113 | public static void Rent(this IObjectPool source, Action action) 114 | where T : class 115 | { 116 | if (source is null) throw new ArgumentNullException(nameof(source)); 117 | if (action is null) throw new ArgumentNullException(nameof(action)); 118 | Contract.EndContractBlock(); 119 | 120 | var item = source.Take(); 121 | action(item); 122 | source.Give(item); 123 | } 124 | 125 | /// 126 | /// Provides an item from the pool and returns it when the action sucessfully completes. 127 | /// 128 | /// The object type from the pool. 129 | /// The object pool. 130 | /// The action to execute. 131 | /// The value from the action. 132 | public static TResult Rent(this IObjectPool source, Func action) 133 | where T : class 134 | { 135 | if (source is null) throw new ArgumentNullException(nameof(source)); 136 | if (action is null) throw new ArgumentNullException(nameof(action)); 137 | Contract.EndContractBlock(); 138 | 139 | var item = source.Take(); 140 | var result = action(item); 141 | source.Give(item); 142 | return result; 143 | } 144 | 145 | /// 146 | /// Provides an item from the pool and returns it when the action sucessfully completes. 147 | /// 148 | /// The object type from the pool. 149 | /// The object pool. 150 | /// The action to execute. 151 | public static async ValueTask RentAsync(this IObjectPool source, Func action) 152 | where T : class 153 | { 154 | if (source is null) throw new ArgumentNullException(nameof(source)); 155 | if (action is null) throw new ArgumentNullException(nameof(action)); 156 | Contract.EndContractBlock(); 157 | 158 | var item = source.Take(); 159 | await action(item).ConfigureAwait(false); 160 | source.Give(item); 161 | } 162 | 163 | /// 164 | /// Provides an item from the pool and returns it when the action sucessfully completes. 165 | /// 166 | /// The object type from the pool. 167 | /// The object pool. 168 | /// The action to execute. 169 | /// The value from the action. 170 | public static async ValueTask RentAsync(this IObjectPool source, Func> action) 171 | where T : class 172 | { 173 | if (source is null) throw new ArgumentNullException(nameof(source)); 174 | if (action is null) throw new ArgumentNullException(nameof(action)); 175 | Contract.EndContractBlock(); 176 | 177 | var item = source.Take(); 178 | var result = await action(item).ConfigureAwait(false); 179 | source.Give(item); 180 | return result; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /source/IRecyclable.cs: -------------------------------------------------------------------------------- 1 | namespace Open.Disposable; 2 | 3 | public interface IRecyclable 4 | { 5 | /// 6 | /// Signals the item should be recycled. 7 | /// 8 | void Recycle(); 9 | } 10 | -------------------------------------------------------------------------------- /source/IRecycler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Open.Disposable; 5 | 6 | public interface IRecycler 7 | : IDisposable 8 | where T : class 9 | { 10 | /// 11 | /// Recycles the item. 12 | /// 13 | bool Recycle(T item); 14 | 15 | /// 16 | /// Closes the recycling. No more should be recycled. 17 | /// 18 | Task Close(); 19 | } 20 | -------------------------------------------------------------------------------- /source/ITrimmableObjectPool.cs: -------------------------------------------------------------------------------- 1 | namespace Open.Disposable; 2 | 3 | public interface ITrimmableObjectPool 4 | { 5 | event ObjectPoolResizeEvent Received; 6 | event ObjectPoolResizeEvent Released; 7 | 8 | void TrimTo(int targetSize); 9 | } 10 | -------------------------------------------------------------------------------- /source/ObjectPoolAutoTrimmer.cs: -------------------------------------------------------------------------------- 1 | using Open.Threading.Tasks; 2 | using System; 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | namespace Open.Disposable; 6 | 7 | [SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "Micro-optimization for retrieving this value as read-only is slightly slower")] 8 | [SuppressMessage("Roslynator", "RCS1169:Make field read-only.")] 9 | class ObjectPoolAutoTrimmer 10 | : DisposableBase 11 | { 12 | ITrimmableObjectPool _pool; 13 | ActionRunner _trimmer; 14 | 15 | // Micro-optimization for retrieving this value as read-only is slightly slower. 16 | ushort _trimmedSize; 17 | TimeSpan _trimDelay; 18 | 19 | /// 20 | /// Max size that trimming will allow. 21 | /// 22 | // ReSharper disable once NotAccessedField.Global 23 | public readonly ushort TrimmedSize; 24 | 25 | /// 26 | /// Time to wait/defer trimming. Default is 500 milliSeconds. 27 | /// 28 | // ReSharper disable once NotAccessedField.Global 29 | public readonly TimeSpan TrimDelay; 30 | 31 | /// 32 | /// Constructs an auto-trimming ObjectPool helper. 33 | /// 34 | /// The target size to limit to after a half second timeout. Allowing the pool to still grow to the max size until the trim occurs. 35 | /// The governable object pool to maintain. 36 | /// The amount of time to wait/defer trimming. 37 | public ObjectPoolAutoTrimmer( 38 | ushort trimmedSize, 39 | ITrimmableObjectPool pool, 40 | TimeSpan? trimDelay = null) 41 | { 42 | _pool = pool ?? throw new ArgumentNullException(nameof(pool)); 43 | 44 | if (pool is DisposableBase d) 45 | { 46 | if (d.WasDisposed) throw new ArgumentException("Cannot trim for an object pool that is already disposed."); 47 | d.BeforeDispose += Pool_BeforeDispose; 48 | } 49 | 50 | TrimmedSize = _trimmedSize = trimmedSize; 51 | TrimDelay = _trimDelay = trimDelay ?? TimeSpan.FromMilliseconds(500); 52 | 53 | _trimmer = new ActionRunner(TrimInternal); 54 | 55 | pool.Received += Target_GivenTo; 56 | pool.Released += Target_TakenFrom; 57 | } 58 | 59 | protected virtual void Target_GivenTo(int newSize) 60 | { 61 | if (newSize >= 0 && newSize > _trimmedSize) 62 | _trimmer?.Defer(_trimDelay, false); 63 | } 64 | 65 | protected virtual void Target_TakenFrom(int newSize) 66 | { 67 | if (newSize >= 0 && newSize <= _trimmedSize) 68 | _trimmer?.Cancel(); 69 | } 70 | 71 | void Pool_BeforeDispose(object? sender, EventArgs e) => Dispose(); 72 | 73 | protected virtual void TrimInternal() 74 | { 75 | _trimmer?.Cancel(); 76 | _pool?.TrimTo(_trimmedSize); 77 | } 78 | 79 | protected override void OnDispose() 80 | { 81 | DisposeOf(ref _trimmer); 82 | 83 | var target = Nullify(ref _pool); 84 | target.Received -= Target_GivenTo; 85 | target.Released -= Target_TakenFrom; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /source/ObjectPoolBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace Open.Disposable; 6 | 7 | public abstract class ObjectPoolBase 8 | : DisposableBase, IObjectPool 9 | where T : class 10 | { 11 | protected const int DEFAULT_CAPACITY = Constants.DEFAULT_CAPACITY; 12 | 13 | protected ObjectPoolBase( 14 | Func factory, 15 | Action? recycler, 16 | Action? disposer, 17 | int capacity = DEFAULT_CAPACITY) 18 | { 19 | if (capacity < 1) 20 | throw new ArgumentOutOfRangeException(nameof(capacity), capacity, "Must be at least 1."); 21 | Factory = factory ?? throw new ArgumentNullException(nameof(factory)); 22 | MaxSize = capacity; 23 | Recycler = recycler; 24 | OnDiscarded = disposer; 25 | } 26 | 27 | // Not read-only due to quirk where read-only is slower than not. 28 | protected int MaxSize; 29 | public int Capacity => MaxSize; 30 | 31 | /// 32 | /// Total number of items in the pool. 33 | /// 34 | /// 35 | public abstract int Count { get; } 36 | protected int PocketCount => Pocket.Value is null ? 0 : 1; 37 | 38 | protected readonly Action? Recycler; // Before entering the pool. 39 | protected readonly Action? OnDiscarded; // When not able to be used. 40 | 41 | // Read-only because if Take() is called after disposal, this still facilitates returing an object. 42 | // Allow the GC to do the final cleanup after dispose. 43 | protected readonly Func Factory; 44 | 45 | public T Generate() => Factory(); 46 | 47 | // ReSharper disable once UnassignedField.Global 48 | protected ReferenceContainer Pocket; // Default struct constructs itself. 49 | 50 | #region Receive (.Give(T item)) 51 | protected virtual bool CanReceive => true; // A default of true is acceptable, enabling the Receive method to do the actual deciding. 52 | 53 | protected bool PrepareToReceive(T item) 54 | { 55 | if (!CanReceive) return false; 56 | 57 | Debug.Assert(item is not null); 58 | var r = Recycler; 59 | if (r is null) return true; 60 | 61 | r(item!); 62 | // Did the recycle take so long that the state changed? 63 | return CanReceive; 64 | } 65 | 66 | // Contract should be that no item can be null here. 67 | protected abstract bool Receive(T item); 68 | 69 | protected virtual void OnReceived() 70 | { 71 | } 72 | 73 | /// 74 | public void Give(T item) 75 | { 76 | if (item is null) return; 77 | if (PrepareToReceive(item) 78 | && (SaveToPocket(item) 79 | || Receive(item))) 80 | { 81 | OnReceived(); 82 | } 83 | else 84 | { 85 | OnDiscarded?.Invoke(item); 86 | } 87 | } 88 | #endregion 89 | 90 | #region Release (.Take()) 91 | /// 92 | public virtual T Take() => TryTake() ?? Factory(); 93 | 94 | /// 95 | #if NETSTANDARD2_1_OR_GREATER 96 | public bool TryTake([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out T? item) 97 | #else 98 | public bool TryTake(out T? item) 99 | #endif 100 | => (item = TryTake()) is not null; 101 | 102 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 103 | protected virtual bool SaveToPocket(T item) => Pocket.TrySave(item); 104 | 105 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 106 | protected T? TakeFromPocket() => Pocket.TryRetrieve(); 107 | 108 | protected abstract T? TryRelease(); 109 | 110 | /// 111 | public T? TryTake() 112 | { 113 | var item = TakeFromPocket() ?? TryRelease(); 114 | if (item is not null) OnReleased(); 115 | return item; 116 | } 117 | 118 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 119 | protected virtual void OnReleased() 120 | { 121 | } 122 | #endregion 123 | 124 | protected override void OnBeforeDispose() => MaxSize = 0; 125 | 126 | protected override void OnDispose() 127 | { 128 | if (OnDiscarded is null) return; 129 | 130 | T? d; 131 | while ((d = TryRelease()) is not null) OnDiscarded(d); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /source/ObjectPoolCompatibilityExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Open.Disposable.ObjectPoolCompatibility; 2 | 3 | /// 4 | /// Provided as means for compatiibility with object pools 5 | /// that have the same interface as the Microsoft Extensions version. 6 | /// 7 | public static class ObjectPoolCompatibilityExtensions 8 | { 9 | /// 10 | public static T Get(this IObjectPool pool) 11 | where T : class => pool.Take(); 12 | 13 | /// 14 | public static void Return(this IObjectPool pool, T item) 15 | where T : class => pool.Give(item); 16 | } 17 | -------------------------------------------------------------------------------- /source/ObjectPoolResizeEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Open.Disposable; 2 | 3 | public delegate void ObjectPoolResizeEvent(int newSize); 4 | -------------------------------------------------------------------------------- /source/Open.Disposable.ObjectPools.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;netstandard2.1;net9.0 5 | latest 6 | enable 7 | true 8 | Open.Disposable 9 | True 10 | electricessence 11 | 12 | A set of variations on ObjectPool implementations with differing underlying collections. 13 | 14 | Part of the "Open" set of libraries. 15 | 16 | © electricessence (Oren F.) All rights reserved. 17 | https://github.com/Open-NET-Libraries/Open.Disposable.ObjectPools/ 18 | https://github.com/Open-NET-Libraries/Open.Disposable.ObjectPools/ 19 | git 20 | objectpool;idisposable;thread safe 21 | 3.0.2 22 | 23 | MIT 24 | true 25 | true 26 | snupkg 27 | logo.png 28 | README.md 29 | IDE0290;CA1510;IDE0130;IDE0301; 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | True 38 | \ 39 | 40 | 41 | True 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | all 53 | runtime; build; native; contentfiles; analyzers; buildtransitive 54 | 55 | 56 | all 57 | runtime; build; native; contentfiles; analyzers; buildtransitive 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /source/RecycleHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Open.Disposable; 4 | 5 | /// 6 | /// Allows for the 'using' syntax to be used with any object to return it to the pool. 7 | /// 8 | public struct RecycleHelper : IDisposable 9 | where T : class 10 | { 11 | private readonly IObjectPool _pool; 12 | 13 | private RecycleHelper(IObjectPool pool, T item) 14 | { 15 | _pool = pool; 16 | _item = item; 17 | } 18 | 19 | public RecycleHelper(IObjectPool pool) 20 | : this(pool ?? throw new ArgumentNullException(nameof(pool)), pool.Take()) 21 | { 22 | } 23 | 24 | private T? _item; 25 | public readonly T Item 26 | => _item ?? throw new ObjectDisposedException(GetType().ToString()); 27 | 28 | public void Dispose() 29 | { 30 | var i = _item; 31 | _item = null; 32 | if (i is null) return; 33 | _pool.Give(i); 34 | } 35 | 36 | public static implicit operator T(RecycleHelper helper) => helper.Item; 37 | } 38 | -------------------------------------------------------------------------------- /source/Recycler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Channels; 3 | using System.Threading.Tasks; 4 | 5 | namespace Open.Disposable; 6 | 7 | /// 8 | public class Recycler : RecyclerBase 9 | where T : class 10 | { 11 | Channel _bin; 12 | 13 | internal Recycler( 14 | IObjectPool target, 15 | Action recycleFunction, 16 | ushort limit = Constants.DEFAULT_CAPACITY) : base(target, recycleFunction) 17 | { 18 | _bin = Channel.CreateBounded(limit); 19 | Completion = Processor(recycleFunction); 20 | } 21 | 22 | async Task Processor(Action recycleFunction) 23 | { 24 | var bin = _bin; 25 | do 26 | { 27 | while (bin.Reader.TryRead(out var item)) 28 | { 29 | recycleFunction(item); 30 | Target?.Give(item); 31 | } 32 | } 33 | while (await bin.Reader.WaitToReadAsync().ConfigureAwait(false)); 34 | } 35 | 36 | internal Recycler( 37 | ushort limit, 38 | IObjectPool pool, 39 | Action recycleFunction) : this(pool, recycleFunction, limit) { } 40 | 41 | /// 42 | public override bool Recycle(T item) => _bin?.Writer.TryWrite(item) ?? false; 43 | 44 | protected override void OnCloseRequested() => _bin?.Writer.Complete(); 45 | 46 | protected override void OnDispose() 47 | { 48 | base.OnDispose(); 49 | 50 | _bin.Writer.TryComplete(); 51 | _bin = null!; 52 | } 53 | } 54 | 55 | public static class Recycler 56 | { 57 | public static Recycler CreateRecycler( 58 | this IObjectPool pool, 59 | Action recycleFunction, 60 | ushort limit = Constants.DEFAULT_CAPACITY) 61 | where T : class => new(pool, recycleFunction, limit); 62 | 63 | public static Recycler CreateRecycler( 64 | this IObjectPool pool, 65 | ushort limit, 66 | Action recycleFunction) 67 | where T : class => new(pool, recycleFunction, limit); 68 | 69 | private static Action? _recycleDelegate; 70 | // A predefined delegate instead of a method to avoid additional allocations. 71 | public static Action Recycle 72 | => _recycleDelegate ??= (IRecyclable r) 73 | => (r ?? throw new ArgumentNullException(nameof(r))).Recycle(); 74 | 75 | public static Recycler CreateRecycler( 76 | this IObjectPool pool, 77 | ushort limit = Constants.DEFAULT_CAPACITY) 78 | where T : class, IRecyclable => new(pool, Recycle, limit); 79 | } 80 | -------------------------------------------------------------------------------- /source/RecyclerBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.Contracts; 3 | using System.Threading.Tasks; 4 | 5 | namespace Open.Disposable; 6 | 7 | /// 8 | /// This class is provided as an asynchronous queue for recycling instead of using a recycle delegate with an object pool and calling GiveAsync() which could pile up unnecessarily. 9 | /// So if recycling an object takes extra time, this might be a good way to toss objects away and not have to worry about the heavy cost as they will one by one be processed back into the target pool. 10 | /// 11 | // ReSharper disable once InheritdocConsiderUsage 12 | public abstract class RecyclerBase : DisposableBase, IRecycler 13 | where T : class 14 | { 15 | protected IObjectPool Target; 16 | 17 | protected RecyclerBase( 18 | IObjectPool target, 19 | Action recycleFunction) 20 | { 21 | if (recycleFunction is null) throw new ArgumentNullException(nameof(recycleFunction)); 22 | Target = target ?? throw new ArgumentNullException(nameof(target)); 23 | Contract.EndContractBlock(); 24 | 25 | if (target is not DisposableBase d) return; 26 | if (d.WasDisposed) throw new ArgumentException("Cannot recycle for an object pool that is already disposed."); 27 | d.BeforeDispose += Pool_BeforeDispose; 28 | // Could possibly dispose before this line somewhere... But that's just nasty. :P 29 | } 30 | 31 | void Pool_BeforeDispose(object? sender, EventArgs e) => Dispose(); 32 | 33 | public abstract bool Recycle(T item); 34 | 35 | protected abstract void OnCloseRequested(); 36 | 37 | // ReSharper disable once MemberCanBeProtected.Global 38 | public Task Completion { get; protected set; } = Task.CompletedTask; 39 | 40 | /// 41 | public Task Close() 42 | { 43 | OnCloseRequested(); 44 | return Completion; 45 | } 46 | 47 | protected override void OnDispose() 48 | { 49 | OnCloseRequested(); 50 | Target = null!; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /source/ReferenceContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.CompilerServices; 3 | using System.Threading; 4 | 5 | namespace Open.Disposable; 6 | 7 | public interface IReferenceContainer 8 | where T : class 9 | { 10 | int Capacity { get; } 11 | bool SetIfNull(T value); 12 | bool TrySave(T value); 13 | T? TryRetrieve(); 14 | } 15 | 16 | [DebuggerDisplay("{Value,nq}")] 17 | public struct ReferenceContainer : IReferenceContainer 18 | where T : class 19 | { 20 | public readonly int Capacity => 1; 21 | 22 | T? _value; 23 | 24 | /// 25 | /// The value contained. 26 | /// 27 | public T? Value 28 | { 29 | readonly get => _value; 30 | set => _value = value; 31 | } 32 | 33 | /// 34 | /// Sets the value if it is currently null without interlocking. 35 | /// 36 | /// true if the value was set; otherwise false. 37 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 38 | public bool SetIfNull(T value) 39 | { 40 | if (_value is not null) return false; 41 | _value = value; 42 | return true; 43 | } 44 | 45 | /// 46 | /// Tries to atomically store the value. 47 | /// 48 | /// true if the value was set; otherwise false. 49 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 50 | public bool TrySave(T value) 51 | => _value is null && Interlocked.CompareExchange(ref _value, value, null) == null; 52 | 53 | /// 54 | /// Tries to atomically retrieve the value. 55 | /// 56 | /// The value retrieved; otherwise null. 57 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 58 | public T? TryRetrieve() 59 | { 60 | var item = _value; 61 | return (item is not null 62 | && item == Interlocked.CompareExchange(ref _value, null, item)) 63 | ? item 64 | : null; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /source/SharedPools.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Open.Disposable; 6 | 7 | public sealed class SharedPool( 8 | Func factory, 9 | Action? recycler, 10 | int capacity) 11 | : OptimisticArrayObjectPool(factory, recycler, capacity) 12 | where T : class 13 | { 14 | [Obsolete("Shared pools do not support disposal.")] 15 | public new void Dispose() 16 | => throw new NotSupportedException("Shared pools cannot be disposed."); 17 | 18 | protected override void OnDispose() 19 | => throw new NotSupportedException("Shared pools cannot be disposed."); 20 | } 21 | 22 | public static class ListPool 23 | { 24 | /// 25 | /// A shared object pool for use with lists. 26 | /// The list is cleared after being returned. 27 | /// The max size of the pool is 64 which should suffice for most use cases. 28 | /// 29 | public static readonly SharedPool> Shared = Create(); 30 | 31 | /// 32 | /// Creates an object pool for use with lists. 33 | /// The list is cleared after being returned. 34 | /// 35 | public static SharedPool> Create(int capacity = Constants.DEFAULT_CAPACITY) => new(() => [], h => 36 | { 37 | h.Clear(); 38 | if (h.Capacity > 16) h.Capacity = 16; 39 | }, capacity); 40 | 41 | /// 42 | /// Provides a disposable RecycleHelper that contains an item from the pool.
43 | /// Will be returned to the pool once .Dispose() is called. 44 | ///
45 | /// A RecycleHelper containing an item from the pool. 46 | public static RecycleHelper> Rent() => Shared.Rent(); 47 | } 48 | 49 | public static class HashSetPool 50 | { 51 | /// 52 | /// A shared object pool for use with hash-sets. 53 | /// The hash-set is cleared after being returned. 54 | /// The max size of the pool is 64 which should suffice for most use cases. 55 | /// 56 | public static readonly SharedPool> Shared = Create(); 57 | 58 | /// 59 | /// Creates an object pool for use with hash-sets. 60 | /// The hash-set is cleared after being returned. 61 | /// 62 | public static SharedPool> Create(int capacity = Constants.DEFAULT_CAPACITY) 63 | => new(() => [], h => h.Clear(), capacity); 64 | 65 | /// 66 | public static RecycleHelper> Rent() => Shared.Rent(); 67 | } 68 | 69 | public static class StringBuilderPool 70 | { 71 | /// 72 | /// A shared object pool for use with StringBuilders. 73 | /// The StringBuilder is cleared after being returned. 74 | /// The max size of the pool is 64 which should suffice for most use cases. 75 | /// 76 | public static readonly SharedPool Shared = Create(); 77 | 78 | /// 79 | /// Creates an object pool for use with StringBuilders. 80 | /// The StringBuilder is cleared after being returned. 81 | /// 82 | public static SharedPool Create(int capacity = Constants.DEFAULT_CAPACITY) 83 | => new(() => new(), sb => sb.Clear(), capacity); 84 | 85 | /// 86 | /// Provides a StringBuilder to be used for processing and finalizes by calling .ToString() and returning the value. 87 | /// 88 | /// If either the pool or action are null. 89 | public static string RentToString(this IObjectPool pool, Action action) 90 | { 91 | if (pool is null) throw new ArgumentNullException(nameof(pool)); 92 | if (action is null) throw new ArgumentNullException(nameof(action)); 93 | 94 | var sb = pool.Take(); 95 | action(sb); 96 | var result = sb.ToString(); 97 | pool.Give(sb); 98 | return result; 99 | } 100 | 101 | /// If the action is null. 102 | /// 103 | public static string RentToString(Action action) 104 | => Shared.RentToString(action); 105 | 106 | /// 107 | /// Provides a disposable RecycleHelper that contains a StringBuilder from the pool.
108 | /// Will be returned to the pool once .Dispose() is called. 109 | ///
110 | /// A RecycleHelper containing a StringBuilder from the pool. 111 | public static RecycleHelper Rent() => Shared.Rent(); 112 | } 113 | 114 | public static class DictionaryPool 115 | where TKey : notnull 116 | { 117 | /// 118 | /// A shared object pool for use with dictionaries. 119 | /// The dictionary is cleared after being returned. 120 | /// The max size of the pool is 64 which should suffice for most use cases. 121 | /// 122 | public static readonly SharedPool> Shared = Create(); 123 | 124 | /// 125 | /// Creates an object pool for use with dictionaries. 126 | /// The dictionary is cleared after being returned. 127 | /// 128 | public static SharedPool> Create(int capacity = Constants.DEFAULT_CAPACITY) 129 | => new(() => [], h => h.Clear(), capacity); 130 | 131 | /// 132 | /// Provides a disposable RecycleHelper that contains an item from the pool.
133 | /// Will be returned to the pool once .Dispose() is called. 134 | ///
135 | /// A RecycleHelper containing a item from the pool. 136 | public static RecycleHelper> Rent() => Shared.Rent(); 137 | } 138 | -------------------------------------------------------------------------------- /source/TrimmableCollectionObjectPoolBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Open.Disposable; 6 | 7 | /* 8 | * There are two class varations here because ICollection and ICollection do not overlap. 9 | * 'Count' is the common property used in both classes. 10 | */ 11 | 12 | public abstract class TrimmableCollectionObjectPoolBase 13 | : TrimmableObjectPoolBase 14 | where T : class 15 | where TCollection : class, ICollection 16 | { 17 | protected TrimmableCollectionObjectPoolBase( 18 | TCollection pool, 19 | Func factory, 20 | Action? recycler, 21 | Action? disposer, 22 | int capacity = DEFAULT_CAPACITY, 23 | bool countTrackingEnabled = true) 24 | : base(factory, recycler, disposer, capacity, countTrackingEnabled) 25 | => Pool = pool ?? throw new ArgumentNullException(nameof(pool)); 26 | 27 | protected TCollection Pool; 28 | 29 | /// 30 | public override int Count => (Pool?.Count ?? 0) + PocketCount; 31 | 32 | protected override void OnDispose() 33 | { 34 | base.OnDispose(); 35 | Pool = null!; 36 | } 37 | } 38 | 39 | public abstract class TrimmableGenericCollectionObjectPoolBase 40 | : TrimmableObjectPoolBase 41 | where T : class 42 | where TCollection : class, ICollection 43 | { 44 | protected TrimmableGenericCollectionObjectPoolBase( 45 | TCollection pool, 46 | Func factory, 47 | Action? recycler, 48 | Action? disposer, 49 | int capacity = DEFAULT_CAPACITY, 50 | bool countTrackingEnabled = true) 51 | : base(factory, recycler, disposer, capacity, countTrackingEnabled) 52 | => Pool = pool ?? throw new ArgumentNullException(nameof(pool)); 53 | 54 | protected TCollection Pool; 55 | 56 | /// 57 | public override int Count => (Pool?.Count ?? 0) + PocketCount; 58 | 59 | protected override void OnDispose() 60 | { 61 | //base.OnDispose(); // Do not call because the following is more optimized. 62 | 63 | var p = Pool; 64 | Pool = null!; 65 | 66 | if (OnDiscarded is not null) 67 | { 68 | foreach (var item in p) 69 | OnDiscarded(item); 70 | } 71 | 72 | p.Clear(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /source/TrimmableObjectPoolBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.CompilerServices; 4 | using System.Threading; 5 | 6 | namespace Open.Disposable; 7 | 8 | [DebuggerDisplay("Count = {Count}")] 9 | public abstract class TrimmableObjectPoolBase : ObjectPoolBase, ITrimmableObjectPool 10 | where T : class 11 | { 12 | protected TrimmableObjectPoolBase(Func factory, Action? recycler, Action? disposer, int capacity, bool countTrackingEnabled = true) 13 | : base(factory, recycler, disposer, capacity) => _countTrackingEnabled = countTrackingEnabled; 14 | 15 | #if NET9_0_OR_GREATER 16 | protected readonly Lock SyncRoot = new(); 17 | #else 18 | protected readonly object SyncRoot = new(); 19 | #endif 20 | 21 | int _count; 22 | readonly bool _countTrackingEnabled; // When true this enables tracking the number of entries entering and exiting the pool instead of calling '.Count'. 23 | 24 | protected int CountInternal => _countTrackingEnabled ? _count : Count; 25 | 26 | protected override bool CanReceive => CountInternal < MaxSize; 27 | 28 | /// 29 | /// Signal for when an item was taken (actually removed) from the pool. 30 | /// 31 | public event ObjectPoolResizeEvent? Released; 32 | protected void OnReleased(int newSize) 33 | { 34 | Debug.Assert(newSize > -2, $"newSize: {newSize}, _count: {_count}"); // Should never get out of control. It may go negative temporarily but should be 100% accounted for. 35 | Released?.Invoke(newSize); 36 | } 37 | 38 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 39 | protected override void OnReleased() => OnReleased(_countTrackingEnabled ? Interlocked.Decrement(ref _count) : Count); 40 | 41 | /// 42 | /// Signal for when an item was given (actually accepted) to the pool. 43 | /// 44 | public event ObjectPoolResizeEvent? Received; 45 | 46 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 47 | protected void OnReceived(int newSize) => Received?.Invoke(newSize); 48 | 49 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 50 | protected override void OnReceived() => OnReceived(_countTrackingEnabled ? Interlocked.Increment(ref _count) : Count); 51 | 52 | /// 53 | public virtual void TrimTo(int targetSize) 54 | { 55 | if (targetSize < 0) return; // Possible upstream math hiccup or default -1. Silently dismiss. 56 | var count = CountInternal; 57 | 58 | if (count <= targetSize) return; 59 | 60 | var i = 0; // Prevent an possiblility of indefinite loop. 61 | for ( 62 | var attempts = count - targetSize; 63 | i < attempts; 64 | i++) 65 | { 66 | var e = TryRelease(); 67 | if (e is null) break; 68 | 69 | if (_countTrackingEnabled) 70 | { 71 | Interlocked.Decrement(ref _count); 72 | //var c = Interlocked.Decrement(ref _count); 73 | //Debug.Assert(c >= 0); 74 | } 75 | 76 | OnDiscarded?.Invoke(e); 77 | } 78 | 79 | if (i != 0) OnReleased(CountInternal); 80 | } 81 | 82 | //protected ReferenceContainer Pocket2; // Default struct constructs itself. 83 | 84 | //protected override bool SaveToPocket(T item) 85 | // => Pocket.TrySave(item) || Pocket2.TrySave(item); 86 | 87 | //protected override T TakeFromPocket() 88 | // => Pocket.TryRetrieve() ?? Pocket2.TryRetrieve(); 89 | 90 | //protected override int PocketCount => 91 | // base.PocketCount + (Pocket2.Value is null ? 0 : 1); 92 | } 93 | -------------------------------------------------------------------------------- /source/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Open-NET-Libraries/Open.Disposable.ObjectPools/8f23478b5df78a9fdfdf1cc1cd624da0abac70b3/source/logo.png -------------------------------------------------------------------------------- /tests/ObjectPoolSmokeTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | 4 | namespace Open.Disposable; 5 | 6 | [TestClass] 7 | public class ObjectPoolSmokeTests 8 | { 9 | class IdContainer 10 | { 11 | public int ID; 12 | } 13 | 14 | [TestMethod] 15 | public void OptimisticArrayObjectPool_FactoryTest() 16 | { 17 | var i = 0; 18 | var pool = OptimisticArrayObjectPool.Create(() => new IdContainer { ID = ++i }); 19 | Assert.AreEqual(1, pool.Take().ID); 20 | } 21 | 22 | [TestMethod] 23 | public void ListPool_RecycleTest() 24 | { 25 | var pool = ListPool.Shared; 26 | var list = pool.Take(); 27 | list.Add(1); 28 | pool.Give(list); 29 | Assert.AreEqual(list, pool.Take()); 30 | Assert.AreEqual(0, list.Count); 31 | 32 | #pragma warning disable CS0618 // Type or member is obsolete 33 | Assert.ThrowsException(pool.Dispose); 34 | #pragma warning restore CS0618 // Type or member is obsolete 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Open.Disposable.ObjectPools.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | Open.Disposable 6 | 7 | 8 | 9 | latest 10 | 11 | 12 | 13 | latest 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | --------------------------------------------------------------------------------