async-enumerable 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.standard.yml +5 -0
  4. data/CHANGELOG.md +5 -0
  5. data/CODE_OF_CONDUCT.md +132 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +416 -0
  8. data/Rakefile +127 -0
  9. data/benchmark/async_all.yaml +38 -0
  10. data/benchmark/async_any.yaml +39 -0
  11. data/benchmark/async_each.yaml +51 -0
  12. data/benchmark/async_find.yaml +37 -0
  13. data/benchmark/async_map.yaml +50 -0
  14. data/benchmark/async_select.yaml +31 -0
  15. data/benchmark/early_termination/any_early.yaml +17 -0
  16. data/benchmark/early_termination/any_late.yaml +17 -0
  17. data/benchmark/early_termination/find_middle.yaml +17 -0
  18. data/benchmark/size_comparison/map_10.yaml +17 -0
  19. data/benchmark/size_comparison/map_100.yaml +17 -0
  20. data/benchmark/size_comparison/map_1000.yaml +20 -0
  21. data/benchmark/size_comparison/map_10000.yaml +23 -0
  22. data/docs/reference/README.md +43 -0
  23. data/docs/reference/concurrency_bounder.md +234 -0
  24. data/docs/reference/enumerable.md +258 -0
  25. data/docs/reference/enumerator.md +221 -0
  26. data/docs/reference/methods/converters.md +97 -0
  27. data/docs/reference/methods/predicates.md +254 -0
  28. data/docs/reference/methods/transformers.md +104 -0
  29. data/lib/async/enumerable/comparable.rb +26 -0
  30. data/lib/async/enumerable/concurrency_bounder.rb +37 -0
  31. data/lib/async/enumerable/configurable.rb +140 -0
  32. data/lib/async/enumerable/methods/aggregators.rb +40 -0
  33. data/lib/async/enumerable/methods/converters.rb +21 -0
  34. data/lib/async/enumerable/methods/each.rb +39 -0
  35. data/lib/async/enumerable/methods/iterators.rb +27 -0
  36. data/lib/async/enumerable/methods/predicates/all.rb +47 -0
  37. data/lib/async/enumerable/methods/predicates/any.rb +47 -0
  38. data/lib/async/enumerable/methods/predicates/find.rb +55 -0
  39. data/lib/async/enumerable/methods/predicates/find_index.rb +50 -0
  40. data/lib/async/enumerable/methods/predicates/include.rb +23 -0
  41. data/lib/async/enumerable/methods/predicates/none.rb +27 -0
  42. data/lib/async/enumerable/methods/predicates/one.rb +48 -0
  43. data/lib/async/enumerable/methods/predicates.rb +29 -0
  44. data/lib/async/enumerable/methods/slicers.rb +34 -0
  45. data/lib/async/enumerable/methods/transformers/compact.rb +18 -0
  46. data/lib/async/enumerable/methods/transformers/filter_map.rb +19 -0
  47. data/lib/async/enumerable/methods/transformers/flat_map.rb +20 -0
  48. data/lib/async/enumerable/methods/transformers/map.rb +22 -0
  49. data/lib/async/enumerable/methods/transformers/reject.rb +19 -0
  50. data/lib/async/enumerable/methods/transformers/select.rb +21 -0
  51. data/lib/async/enumerable/methods/transformers/sort.rb +18 -0
  52. data/lib/async/enumerable/methods/transformers/sort_by.rb +19 -0
  53. data/lib/async/enumerable/methods/transformers/uniq.rb +18 -0
  54. data/lib/async/enumerable/methods/transformers.rb +35 -0
  55. data/lib/async/enumerable/methods.rb +26 -0
  56. data/lib/async/enumerable/version.rb +10 -0
  57. data/lib/async/enumerable.rb +72 -0
  58. data/lib/async/enumerator.rb +33 -0
  59. data/lib/enumerable/async.rb +38 -0
  60. data/scripts/debug_config.rb +26 -0
  61. data/scripts/debug_config2.rb +34 -0
  62. data/scripts/sketch.rb +30 -0
  63. data/scripts/test_aggregators.rb +66 -0
  64. data/scripts/test_ancestors.rb +12 -0
  65. data/scripts/test_async_chaining.rb +30 -0
  66. data/scripts/test_direct_method_calls.rb +53 -0
  67. data/scripts/test_example.rb +37 -0
  68. data/scripts/test_issue_7.rb +69 -0
  69. data/scripts/test_method_source.rb +15 -0
  70. metadata +145 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8852221259aabc878bee75ae41cac26dc82ca740fbb937ac7bcc125a27d3d47b
4
+ data.tar.gz: b7a6a9eda6c006233e953787d218fa958ea393e0266e352433ca4213851f692c
5
+ SHA512:
6
+ metadata.gz: 9b010a33644435577e81c667a6cafc60e6740ae9060f4d0136d23eb1cbef9741f4ddfcd571e5ce36c2179f658ce3aa7a21ca2e51ac470d2c91b1d2215520e9fa
7
+ data.tar.gz: 9c5aa73d047831793123bd9f5e030d27271774e420c79c3d0e3a142c6c4bc43bac902d166442fe1354600664c8e5d0e81acde0bbc39ad301efda371e50c0092c
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.standard.yml ADDED
@@ -0,0 +1,5 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/standardrb/standard
3
+ ruby_version: 3.1
4
+ ignore:
5
+ - 'scripts/**/*rb'
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-08-22
4
+
5
+ - Initial release
@@ -0,0 +1,132 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the overall
26
+ community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or advances of
31
+ any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email address,
35
+ without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official email address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement at
63
+ [INSERT CONTACT METHOD].
64
+ All complaints will be reviewed and investigated promptly and fairly.
65
+
66
+ All community leaders are obligated to respect the privacy and security of the
67
+ reporter of any incident.
68
+
69
+ ## Enforcement Guidelines
70
+
71
+ Community leaders will follow these Community Impact Guidelines in determining
72
+ the consequences for any action they deem in violation of this Code of Conduct:
73
+
74
+ ### 1. Correction
75
+
76
+ **Community Impact**: Use of inappropriate language or other behavior deemed
77
+ unprofessional or unwelcome in the community.
78
+
79
+ **Consequence**: A private, written warning from community leaders, providing
80
+ clarity around the nature of the violation and an explanation of why the
81
+ behavior was inappropriate. A public apology may be requested.
82
+
83
+ ### 2. Warning
84
+
85
+ **Community Impact**: A violation through a single incident or series of
86
+ actions.
87
+
88
+ **Consequence**: A warning with consequences for continued behavior. No
89
+ interaction with the people involved, including unsolicited interaction with
90
+ those enforcing the Code of Conduct, for a specified period of time. This
91
+ includes avoiding interactions in community spaces as well as external channels
92
+ like social media. Violating these terms may lead to a temporary or permanent
93
+ ban.
94
+
95
+ ### 3. Temporary Ban
96
+
97
+ **Community Impact**: A serious violation of community standards, including
98
+ sustained inappropriate behavior.
99
+
100
+ **Consequence**: A temporary ban from any sort of interaction or public
101
+ communication with the community for a specified period of time. No public or
102
+ private interaction with the people involved, including unsolicited interaction
103
+ with those enforcing the Code of Conduct, is allowed during this period.
104
+ Violating these terms may lead to a permanent ban.
105
+
106
+ ### 4. Permanent Ban
107
+
108
+ **Community Impact**: Demonstrating a pattern of violation of community
109
+ standards, including sustained inappropriate behavior, harassment of an
110
+ individual, or aggression toward or disparagement of classes of individuals.
111
+
112
+ **Consequence**: A permanent ban from any sort of public interaction within the
113
+ community.
114
+
115
+ ## Attribution
116
+
117
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118
+ version 2.1, available at
119
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120
+
121
+ Community Impact Guidelines were inspired by
122
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123
+
124
+ For answers to common questions about this code of conduct, see the FAQ at
125
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126
+ [https://www.contributor-covenant.org/translations][translations].
127
+
128
+ [homepage]: https://www.contributor-covenant.org
129
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130
+ [Mozilla CoC]: https://github.com/mozilla/diversity
131
+ [FAQ]: https://www.contributor-covenant.org/faq
132
+ [translations]: https://www.contributor-covenant.org/translations
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Eric Jacobs
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.
data/README.md ADDED
@@ -0,0 +1,416 @@
1
+ # Async::Enumerable
2
+
3
+ Async::Enumerable extends Ruby's Enumerable module with asynchronous
4
+ capabilities, allowing you to perform operations in parallel using the
5
+ [socketry/async](https://github.com/socketry/async) library.
6
+
7
+ ## Installation
8
+
9
+ In your bundler-managed project, run
10
+ ```bash
11
+ bundle add async-enumerable
12
+ ```
13
+
14
+ Or to install globally:
15
+ ```bash
16
+ gem install async-enumerable
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ Async::Enumerable adds an `.async` method to any Enumerable object, which returns
22
+ an Async::Enumerator that performs operations in parallel:
23
+
24
+ ```ruby
25
+ require 'async/enumerable'
26
+
27
+ # Convert any enumerable to async
28
+ results = [1, 2, 3, 4, 5].async.map { |n| n * 2 }
29
+ # => [2, 4, 6, 8, 10]
30
+
31
+ # Works with any enumerable
32
+ (1..100).async.select { |n| expensive_check(n) }
33
+
34
+ # Chain async operations
35
+ data.async
36
+ .select { |item| item.valid? }
37
+ .map { |item| process(item) }
38
+ .take(10)
39
+ ```
40
+
41
+ ### Including in Your Classes
42
+
43
+ Async::Enumerable can be included in your own classes to add async capabilities:
44
+
45
+ ```ruby
46
+ class TodoList
47
+ include Async::Enumerable
48
+ def_async_enumerable :@todos # Specify which ivar/method returns the enumerable
49
+
50
+ def initialize
51
+ @todos = []
52
+ end
53
+
54
+ def add(todo)
55
+ @todos << todo
56
+ self
57
+ end
58
+
59
+ attr_reader :todos
60
+ end
61
+
62
+ list = TodoList.new
63
+ list.add("Buy milk").add("Write code").add("Review PR")
64
+
65
+ # Process todos asynchronously
66
+ completed = list.async.map { |todo| process_todo(todo) }.sync
67
+ ```
68
+
69
+ You can also specify a default fiber limit for your class:
70
+
71
+ ```ruby
72
+ class ApiClient
73
+ include Async::Enumerable
74
+ def_async_enumerable :@endpoints, max_fibers: 10 # Limit concurrent requests
75
+
76
+ attr_reader :endpoints
77
+ end
78
+ ```
79
+
80
+ ### Parallel Execution
81
+
82
+ The main benefit of Async::Enumerable is parallel execution of block operations:
83
+
84
+ ```ruby
85
+ require 'async/enumerable'
86
+ require 'net/http'
87
+
88
+ urls = [
89
+ 'https://api.github.com/users/ruby',
90
+ 'https://api.github.com/users/rails',
91
+ 'https://api.github.com/users/matz'
92
+ ]
93
+
94
+ # Synchronous - requests made sequentially
95
+ responses = urls.map do |url|
96
+ Net::HTTP.get(URI(url))
97
+ end
98
+
99
+ # Asynchronous - requests made in parallel
100
+ responses = urls.async.map do |url|
101
+ Net::HTTP.get(URI(url))
102
+ end
103
+ ```
104
+
105
+ ### Supported Methods
106
+
107
+ Async::Enumerable supports **all** Enumerable methods through different strategies:
108
+
109
+ #### Methods with Async Implementations
110
+
111
+ Most Enumerable methods work automatically through the async implementation of `each`:
112
+ - `map`, `select`, `reject`, `collect`, `filter`, `filter_map`
113
+ - `reduce`, `inject`, `sum`, `min`, `max`, `minmax`
114
+ - `count`, `tally`, `group_by`, `partition`
115
+ - `sort`, `sort_by`, `uniq`, `compact`
116
+ - `to_a`, `to_h`, `entries`
117
+ - `each_with_index`, `each_with_object`, `with_index`
118
+ - `zip`, `cycle`, `chunk`, `slice_*`
119
+
120
+ These methods automatically benefit from parallel execution when blocks contain
121
+ I/O or expensive operations.
122
+
123
+ #### Methods with Optimized Early Termination
124
+
125
+ The Predicates module provides optimized implementations that stop
126
+ processing as soon as the result is determined:
127
+
128
+ - `all?` - Returns true if all elements match (stops on first false)
129
+ - `any?` - Returns true if any element matches (stops on first true)
130
+ - `none?` - Returns true if no elements match (stops on first true)
131
+ - `one?` - Returns true if exactly one element matches
132
+ - `include?` / `member?` - Check if collection includes a value
133
+ - `find` / `detect` - Find first matching element *
134
+ - `find_index` - Find index of matching element *
135
+
136
+ **\* Important:** `find` and `find_index` return the **fastest completing** result, not necessarily the **first** element in order. See [Parallel Execution Behavior](#parallel-execution-behavior) below.
137
+
138
+ #### Methods Delegated to Synchronous Implementation
139
+
140
+ Some methods are inherently sequential and are delegated back to the wrapped enumerable:
141
+ - `first` - Takes elements from the beginning
142
+ - `take` - Takes first n elements
143
+ - `take_while` - Must evaluate elements in order
144
+ - `lazy` - Returns a standard lazy enumerator (lazy evaluation uses break internally, incompatible with async)
145
+
146
+ ### Method Chaining
147
+
148
+ Async::Enumerator maintains the async context through method chains. Transformation methods like `map`, `select`, and `reject` return new `Async::Enumerator` instances, allowing you to chain multiple operations while staying in "async land":
149
+
150
+ ```ruby
151
+ # Chain stays async until .sync
152
+ result = [1, 2, 3, 4, 5].async
153
+ .map { |x| expensive_operation(x) } # Returns Async::Enumerator
154
+ .select { |x| x > threshold } # Returns Async::Enumerator
155
+ .map { |x| transform(x) } # Returns Async::Enumerator
156
+ .sync # Returns Array
157
+
158
+ # The .sync method explicitly converts back to an array
159
+ data = urls.async
160
+ .map { |url| fetch_data(url) }
161
+ .select { |data| data.valid? }
162
+ .sync # Get final results as array
163
+ ```
164
+
165
+ Async::Enumerator also implements comparison operators, so it can be compared directly with arrays:
166
+
167
+ ```ruby
168
+ # Equality comparison works without .sync
169
+ async_result = [1, 2, 3].async.map { |x| x * 2 }
170
+ async_result == [2, 4, 6] # => true
171
+
172
+ # This makes testing clean and simple
173
+ expect(data.async.select { |x| x.valid? }).to eq(expected_valid_items)
174
+ ```
175
+
176
+ ### Module Structure
177
+
178
+ Async::Enumerable is organized into logical modules for better maintainability and selective inclusion:
179
+
180
+ - **`Async::Enumerable::Methods::Transformers`** - Methods that transform collections (map, select, reject, etc.)
181
+ - **`Async::Enumerable::Methods::Predicates`** - Methods that test conditions with early termination (all?, any?, none?, one?, include?, find, find_index)
182
+ - **`Async::Enumerable::Methods::Converters`** - Methods that convert to other types (to_a, sync)
183
+ - **`Async::Enumerable::Methods::Aggregators`** - Aggregation methods inherited from Enumerable (reduce, sum, count, etc.)
184
+ - **`Async::Enumerable::Methods::Iterators`** - Iteration helpers inherited from Enumerable (each_with_index, each_cons, etc.)
185
+ - **`Async::Enumerable::Methods::Slicers`** - Slicing/filtering methods inherited from Enumerable (drop, grep, partition, etc.)
186
+ - **`Async::Enumerable::ConcurrencyBounder`** - Cross-cutting concern for limiting concurrent fibers
187
+ - **`Async::Enumerable::Configurable`** - Configuration management system with hierarchical config inheritance and collection resolution
188
+ - **`Async::Enumerable::Comparable`** - Comparison operators for async enumerators
189
+
190
+ You can selectively include specific modules if needed:
191
+
192
+ ```ruby
193
+ class CustomAsync
194
+ include Enumerable
195
+ include Async::Enumerable::Methods::Transformers::Map
196
+ include Async::Enumerable::Methods::Converters::Sync
197
+ include Async::Enumerable::ConcurrencyBounder
198
+
199
+ # Only has async map and sync methods
200
+ end
201
+ ```
202
+
203
+ ### Parallel Execution Behavior
204
+
205
+ Due to the parallel nature of async operations, some methods behave differently than their synchronous counterparts:
206
+
207
+ #### Find and Find_index Return Fastest Result
208
+
209
+ When using `find`, `detect`, or `find_index` with async enumeration, the result returned is the **first to complete evaluation**, not necessarily the first element in the collection order:
210
+
211
+ ```ruby
212
+ # Synchronous - always returns 3 (first element > 2)
213
+ [1, 2, 3, 4, 5].find { |n| n > 2 } # => 3
214
+
215
+ # Async - returns whichever completes first
216
+ [1, 2, 3, 4, 5].async.find { |n|
217
+ sleep(6 - n) # Element 5 completes first
218
+ n > 2
219
+ } # => Could be 3, 4, or 5 (likely 5 due to shorter sleep)
220
+ ```
221
+
222
+ This is a performance optimization - as soon as any matching element is found, the search terminates immediately without waiting for earlier elements to complete.
223
+
224
+ #### When Order Matters
225
+
226
+ If you need the **first element by position** rather than the **fastest to evaluate**, you have several options:
227
+
228
+ ```ruby
229
+ # Option 1: Use synchronous enumeration
230
+ collection.find { |item| expensive_check(item) }
231
+
232
+ # Option 2: Process in order then find
233
+ collection.async.map { |item| [item, expensive_check(item)] }
234
+ .find { |item, result| result }
235
+ &.first
236
+
237
+ # Option 3: Use with_index to track position
238
+ matches = collection.async.filter_map.with_index do |item, index|
239
+ expensive_check(item) ? [index, item] : nil
240
+ end
241
+ first_match = matches.min_by { |index, _| index }&.last
242
+ ```
243
+
244
+ This behavior applies to:
245
+ - `find` / `detect` - Returns fastest matching element
246
+ - `find_index` - Returns index of fastest matching element
247
+
248
+ Other predicates like `all?`, `any?`, `none?`, `one?`, and `include?` return boolean values, so the order doesn't affect the result.
249
+
250
+ ### When to Use Async
251
+
252
+ Async::Enumerable is beneficial when:
253
+ - Operations in the block are I/O bound (network requests, file operations)
254
+ - You have a large collection with expensive operations per element
255
+ - The order of results doesn't matter, or you're collecting all results
256
+
257
+ Async::Enumerable may not help (and could be slower) when:
258
+ - Operations are very fast/simple
259
+ - Order of execution matters (for find/find_index)
260
+ - Operations depend on previous results
261
+ - The overhead of concurrency exceeds the operation cost
262
+
263
+ ### Examples
264
+
265
+ #### Parallel API Requests
266
+ ```ruby
267
+ user_ids = [1, 2, 3, 4, 5]
268
+
269
+ # Fetch user data in parallel
270
+ users = user_ids.async.map do |id|
271
+ fetch_user_from_api(id)
272
+ end
273
+ ```
274
+
275
+ #### Parallel File Processing
276
+ ```ruby
277
+ file_paths = Dir.glob("*.txt")
278
+
279
+ # Process files in parallel
280
+ results = file_paths.async.map do |path|
281
+ process_file(File.read(path))
282
+ end
283
+ ```
284
+
285
+ #### Early Termination with any?
286
+ ```ruby
287
+ # Stops as soon as one valid item is found
288
+ has_valid = items.async.any? do |item|
289
+ expensive_validation(item)
290
+ end
291
+ ```
292
+
293
+ #### Finding in Parallel
294
+ ```ruby
295
+ # Searches in parallel, stops when found
296
+ result = large_dataset.async.find do |record|
297
+ complex_matching_logic(record)
298
+ end
299
+ ```
300
+
301
+ ## Benchmarks
302
+
303
+ The gem includes benchmarks that demonstrate the performance characteristics of async operations compared to synchronous ones.
304
+
305
+ ### Performance Results
306
+
307
+ When operations involve IO (simulated with sleep delays), async methods show significant performance improvements that scale with collection size:
308
+
309
+ #### Collection Size Comparison
310
+
311
+ | Collection Size | Sync (i/s) | Async (i/s) | Speedup |
312
+ |----------------|------------|-------------|---------|
313
+ | 10 items | 159.8 | 924.7 | **5.8x faster** |
314
+ | 100 items | 15.8 | 325.2 | **20.6x faster** |
315
+ | 1000 items | 7.8 | 44.7 | **5.8x faster** |
316
+
317
+ *Note: For 1000+ items, using `max_fibers` can provide additional optimization*
318
+
319
+ #### Early Termination Performance
320
+
321
+ Even methods that can terminate early benefit from async execution:
322
+
323
+ | Method | Scenario | Sync (i/s) | Async (i/s) | Speedup |
324
+ |--------|----------|------------|-------------|---------|
325
+ | `any?` | Early match | 265.5 | 1190.9 | **4.5x faster** |
326
+ | `any?` | Late match | 16.5 | 351.8 | **21.3x faster** |
327
+ | `find` | Middle element | 31.8 | 412.5 | **13.0x faster** |
328
+
329
+ #### Max Fibers Configuration
330
+
331
+ For very large collections, limiting concurrent fibers can improve performance:
332
+
333
+ ```ruby
334
+ # Default (1024 fibers max)
335
+ (1..10000).async.map { |n| process(n) }
336
+
337
+ # Limited to 100 concurrent fibers
338
+ (1..10000).async(max_fibers: 100).map { |n| process(n) }
339
+
340
+ # Configure global default
341
+ Async::Enumerable.configure { |c| c.max_fibers = 100 }
342
+
343
+ # Configure at class level
344
+ class MyClass
345
+ include Async::Enumerable
346
+ def_async_enumerable :@data, max_fibers: 50
347
+ end
348
+ ```
349
+
350
+ ### Running Benchmarks
351
+
352
+ ```bash
353
+ # Run detailed benchmarks with organized comparisons
354
+ bundle exec rake benchmark
355
+
356
+ # Run quick performance overview
357
+ bundle exec rake benchmark_quick
358
+
359
+ # Run specific benchmark files
360
+ bundle exec benchmark-driver benchmark/size_comparison/map_100.yaml
361
+ bundle exec benchmark-driver benchmark/early_termination/any_early.yaml
362
+ ```
363
+
364
+ ### Benchmark Structure
365
+
366
+ The benchmarks are organized into two categories:
367
+
368
+ #### Size Comparison Benchmarks (`benchmark/size_comparison/`)
369
+ Compare sync vs async performance across different collection sizes (10, 100, 1000, 10000 items):
370
+ - **map_*.yaml** - Tests parallel transformation performance at different scales
371
+
372
+ #### Early Termination Benchmarks (`benchmark/early_termination/`)
373
+ Test methods that can stop processing early:
374
+ - **any_early.yaml** - Tests `any?` with early match
375
+ - **any_late.yaml** - Tests `any?` with late match
376
+ - **find_middle.yaml** - Tests `find` with middle element match
377
+
378
+ The benchmarks simulate IO operations using scaled sleep delays to demonstrate real-world performance benefits.
379
+
380
+ ### Writing Custom Benchmarks
381
+
382
+ You can create your own benchmarks using benchmark-driver's YAML format:
383
+
384
+ ```yaml
385
+ prelude: |
386
+ require 'async/enumerable'
387
+
388
+ def expensive_operation(n)
389
+ sleep(rand / 100.0) # Simulate 0-10ms IO
390
+ n * 2
391
+ end
392
+
393
+ data = (1..100).to_a
394
+
395
+ benchmark:
396
+ sync: data.map { |n| expensive_operation(n) }
397
+ async: data.async.map { |n| expensive_operation(n) }
398
+ ```
399
+
400
+ ## Development
401
+
402
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
403
+
404
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
405
+
406
+ ## Contributing
407
+
408
+ Bug reports and pull requests are welcome on GitHub at https://github.com/jetpks/async-enumerable. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/jetpks/async-enumerable/blob/main/CODE_OF_CONDUCT.md).
409
+
410
+ ## License
411
+
412
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
413
+
414
+ ## Code of Conduct
415
+
416
+ Everyone interacting in the Async::Enumerable project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/jetpks/async-enumerable/blob/main/CODE_OF_CONDUCT.md).