type_balancer 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.
- checksums.yaml +7 -0
- data/.rubocop.yml +96 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/Dockerfile +38 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +101 -0
- data/LICENSE.txt +21 -0
- data/README.md +86 -0
- data/Rakefile +101 -0
- data/benchmark/README.md +90 -0
- data/benchmark/end_to_end_benchmark.rb +65 -0
- data/benchmark/quick_benchmark.rb +70 -0
- data/benchmark_output.log +1581 -0
- data/benchmark_results/ruby3.2.8.txt +55 -0
- data/benchmark_results/ruby3.2.8_yjit.txt +55 -0
- data/benchmark_results/ruby3.3.7.txt +55 -0
- data/benchmark_results/ruby3.3.7_yjit.txt +55 -0
- data/benchmark_results/ruby3.4.2.txt +55 -0
- data/benchmark_results/ruby3.4.2_yjit.txt +55 -0
- data/docs/benchmarks/README.md +180 -0
- data/examples/quality.rb +178 -0
- data/lib/type_balancer/alternating_filler.rb +46 -0
- data/lib/type_balancer/balancer.rb +126 -0
- data/lib/type_balancer/calculator.rb +218 -0
- data/lib/type_balancer/distribution_calculator.rb +21 -0
- data/lib/type_balancer/distributor.rb +61 -0
- data/lib/type_balancer/ordered_collection_manager.rb +95 -0
- data/lib/type_balancer/sequential_filler.rb +32 -0
- data/lib/type_balancer/version.rb +5 -0
- data/lib/type_balancer.rb +44 -0
- data/sig/type_balancer.rbs +85 -0
- data/type_balancer.gemspec +33 -0
- metadata +77 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9d017bcd987973e61c2cca2f30f3a968ebccc0604b7043e44fd7ca4aa22a57a2
|
4
|
+
data.tar.gz: 214815b85a1095b6c4b9b853c6154b3c919d4671db2c69a6730ca21dcad5c559
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c8e8dd1cd9854f80d68a54475e13634bb2ca2e78350f9fe4994252a502bd7acd75d764f7a7c0bd779808ebf39ba496289efa50f8bc7a281391d9e11d3a8ab500
|
7
|
+
data.tar.gz: fbcec72c85c4480f6b035f07bc8f49311798b90dd14c6eaf8cf4a7054c8ec2e7ded4ef9d1dd08e9029a5ce2f698696054f9f96191fae5f88461ad5d303aa10c8
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require:
|
2
|
+
- rubocop-rspec
|
3
|
+
|
4
|
+
AllCops:
|
5
|
+
TargetRubyVersion: 3.2
|
6
|
+
NewCops: enable
|
7
|
+
SuggestExtensions: false
|
8
|
+
Exclude:
|
9
|
+
- 'bin/*'
|
10
|
+
- 'ext/**/*'
|
11
|
+
- 'benchmark/**/*'
|
12
|
+
- 'vendor/**/*'
|
13
|
+
- 'examples/quality.rb'
|
14
|
+
|
15
|
+
Style/StringLiterals:
|
16
|
+
EnforcedStyle: single_quotes
|
17
|
+
|
18
|
+
Style/Documentation:
|
19
|
+
Enabled: false
|
20
|
+
|
21
|
+
Style/DocumentDynamicEvalDefinition:
|
22
|
+
Enabled: false
|
23
|
+
|
24
|
+
Metrics/BlockLength:
|
25
|
+
Enabled: true
|
26
|
+
Exclude:
|
27
|
+
- 'spec/**/*'
|
28
|
+
- 'Rakefile'
|
29
|
+
|
30
|
+
Metrics/MethodLength:
|
31
|
+
Max: 20
|
32
|
+
Exclude:
|
33
|
+
- 'lib/type_balancer/distributor.rb'
|
34
|
+
- 'lib/type_balancer/balancer.rb'
|
35
|
+
|
36
|
+
Metrics/AbcSize:
|
37
|
+
Max: 20
|
38
|
+
Exclude:
|
39
|
+
- 'lib/type_balancer/distributor.rb'
|
40
|
+
- 'lib/type_balancer/balancer.rb'
|
41
|
+
|
42
|
+
Metrics/CyclomaticComplexity:
|
43
|
+
Max: 10
|
44
|
+
Exclude:
|
45
|
+
- 'lib/type_balancer/distributor.rb'
|
46
|
+
- 'lib/type_balancer/balancer.rb'
|
47
|
+
|
48
|
+
Metrics/PerceivedComplexity:
|
49
|
+
Max: 10
|
50
|
+
Exclude:
|
51
|
+
- 'lib/type_balancer/distributor.rb'
|
52
|
+
- 'lib/type_balancer/balancer.rb'
|
53
|
+
|
54
|
+
RSpec/ExampleLength:
|
55
|
+
Max: 15
|
56
|
+
|
57
|
+
RSpec/MultipleExpectations:
|
58
|
+
Max: 5
|
59
|
+
|
60
|
+
RSpec/NestedGroups:
|
61
|
+
Max: 4
|
62
|
+
|
63
|
+
RSpec/VerifiedDoubles:
|
64
|
+
Enabled: false
|
65
|
+
|
66
|
+
RSpec/MultipleMemoizedHelpers:
|
67
|
+
Max: 10
|
68
|
+
|
69
|
+
RSpec/RepeatedExample:
|
70
|
+
Enabled: false # We have intentionally repeated examples
|
71
|
+
|
72
|
+
RSpec/MultipleDescribes:
|
73
|
+
Enabled: false # We have intentionally structured our specs this way
|
74
|
+
|
75
|
+
RSpec/RepeatedExampleGroupDescription:
|
76
|
+
Enabled: false # We have intentionally structured our specs this way
|
77
|
+
|
78
|
+
# Disable all Capybara/RSpec cops
|
79
|
+
Capybara/RSpec:
|
80
|
+
Enabled: false
|
81
|
+
|
82
|
+
# Disable all RSpec/Capybara cops
|
83
|
+
RSpec/Capybara:
|
84
|
+
Enabled: false
|
85
|
+
|
86
|
+
# Disable all RSpec/FactoryBot cops
|
87
|
+
RSpec/FactoryBot:
|
88
|
+
Enabled: false
|
89
|
+
|
90
|
+
# Disable all RSpec/Rails cops
|
91
|
+
RSpec/Rails:
|
92
|
+
Enabled: false
|
93
|
+
|
94
|
+
# Disable PredicateMatcher cop that's causing errors
|
95
|
+
RSpec/PredicateMatcher:
|
96
|
+
Enabled: false
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.4.1
|
data/CHANGELOG.md
ADDED
data/Dockerfile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# Allow platform to be specified at build time
|
2
|
+
ARG PLATFORM=linux/arm64
|
3
|
+
ARG RUBY_VERSION
|
4
|
+
FROM --platform=${PLATFORM} ruby:${RUBY_VERSION}-slim
|
5
|
+
|
6
|
+
# Install build dependencies
|
7
|
+
RUN apt-get update && apt-get install -y \
|
8
|
+
git \
|
9
|
+
build-essential \
|
10
|
+
pkg-config \
|
11
|
+
libffi-dev \
|
12
|
+
&& rm -rf /var/lib/apt/lists/*
|
13
|
+
|
14
|
+
# Install Rust if YJIT is enabled
|
15
|
+
ARG ENABLE_YJIT=false
|
16
|
+
RUN if [ "${ENABLE_YJIT}" = "true" ]; then \
|
17
|
+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y; \
|
18
|
+
fi
|
19
|
+
|
20
|
+
WORKDIR /app
|
21
|
+
|
22
|
+
# Copy all necessary files for building the gem
|
23
|
+
COPY Gemfile Gemfile.lock type_balancer.gemspec Rakefile ./
|
24
|
+
COPY lib/ lib/
|
25
|
+
COPY sig/ sig/
|
26
|
+
COPY benchmark/ benchmark/
|
27
|
+
|
28
|
+
# Initialize Git repository and stage files (needed for gemspec)
|
29
|
+
RUN git init && \
|
30
|
+
git add .
|
31
|
+
|
32
|
+
# Install dependencies
|
33
|
+
RUN bundle install
|
34
|
+
|
35
|
+
# Set environment variable for Ruby to find native extensions
|
36
|
+
ENV RUBYLIB=/app/lib
|
37
|
+
|
38
|
+
CMD ["bundle", "exec", "rake", "benchmark:complete"]
|
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in type_balancer.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
group :development do
|
9
|
+
gem 'benchmark'
|
10
|
+
gem 'benchmark-ips', '~> 2.12'
|
11
|
+
gem 'ffi'
|
12
|
+
gem 'rake', '~> 13.0'
|
13
|
+
gem 'rake-compiler', '~> 1.2'
|
14
|
+
gem 'rspec', '~> 3.0'
|
15
|
+
gem 'rubocop', '~> 1.21'
|
16
|
+
gem 'rubocop-rake', '~> 0.6.0'
|
17
|
+
gem 'rubocop-rspec', '~> 2.26'
|
18
|
+
gem 'simplecov', '~> 0.22.0'
|
19
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
type_balancer (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
ast (2.4.3)
|
10
|
+
benchmark (0.4.0)
|
11
|
+
benchmark-ips (2.14.0)
|
12
|
+
diff-lcs (1.6.1)
|
13
|
+
docile (1.4.1)
|
14
|
+
ffi (1.17.1)
|
15
|
+
ffi (1.17.1-arm64-darwin)
|
16
|
+
json (2.10.2)
|
17
|
+
language_server-protocol (3.17.0.4)
|
18
|
+
lint_roller (1.1.0)
|
19
|
+
parallel (1.26.3)
|
20
|
+
parser (3.3.7.3)
|
21
|
+
ast (~> 2.4.1)
|
22
|
+
racc
|
23
|
+
prism (1.4.0)
|
24
|
+
racc (1.8.1)
|
25
|
+
rainbow (3.1.1)
|
26
|
+
rake (13.2.1)
|
27
|
+
rake-compiler (1.2.9)
|
28
|
+
rake
|
29
|
+
regexp_parser (2.10.0)
|
30
|
+
rspec (3.13.0)
|
31
|
+
rspec-core (~> 3.13.0)
|
32
|
+
rspec-expectations (~> 3.13.0)
|
33
|
+
rspec-mocks (~> 3.13.0)
|
34
|
+
rspec-core (3.13.3)
|
35
|
+
rspec-support (~> 3.13.0)
|
36
|
+
rspec-expectations (3.13.3)
|
37
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
38
|
+
rspec-support (~> 3.13.0)
|
39
|
+
rspec-mocks (3.13.2)
|
40
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
41
|
+
rspec-support (~> 3.13.0)
|
42
|
+
rspec-support (3.13.2)
|
43
|
+
rubocop (1.75.1)
|
44
|
+
json (~> 2.3)
|
45
|
+
language_server-protocol (~> 3.17.0.2)
|
46
|
+
lint_roller (~> 1.1.0)
|
47
|
+
parallel (~> 1.10)
|
48
|
+
parser (>= 3.3.0.2)
|
49
|
+
rainbow (>= 2.2.2, < 4.0)
|
50
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
51
|
+
rubocop-ast (>= 1.43.0, < 2.0)
|
52
|
+
ruby-progressbar (~> 1.7)
|
53
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
54
|
+
rubocop-ast (1.43.0)
|
55
|
+
parser (>= 3.3.7.2)
|
56
|
+
prism (~> 1.4)
|
57
|
+
rubocop-capybara (2.22.1)
|
58
|
+
lint_roller (~> 1.1)
|
59
|
+
rubocop (~> 1.72, >= 1.72.1)
|
60
|
+
rubocop-factory_bot (2.27.1)
|
61
|
+
lint_roller (~> 1.1)
|
62
|
+
rubocop (~> 1.72, >= 1.72.1)
|
63
|
+
rubocop-rake (0.6.0)
|
64
|
+
rubocop (~> 1.0)
|
65
|
+
rubocop-rspec (2.31.0)
|
66
|
+
rubocop (~> 1.40)
|
67
|
+
rubocop-capybara (~> 2.17)
|
68
|
+
rubocop-factory_bot (~> 2.22)
|
69
|
+
rubocop-rspec_rails (~> 2.28)
|
70
|
+
rubocop-rspec_rails (2.29.1)
|
71
|
+
rubocop (~> 1.61)
|
72
|
+
ruby-progressbar (1.13.0)
|
73
|
+
simplecov (0.22.0)
|
74
|
+
docile (~> 1.1)
|
75
|
+
simplecov-html (~> 0.11)
|
76
|
+
simplecov_json_formatter (~> 0.1)
|
77
|
+
simplecov-html (0.13.1)
|
78
|
+
simplecov_json_formatter (0.1.4)
|
79
|
+
unicode-display_width (3.1.4)
|
80
|
+
unicode-emoji (~> 4.0, >= 4.0.4)
|
81
|
+
unicode-emoji (4.0.4)
|
82
|
+
|
83
|
+
PLATFORMS
|
84
|
+
arm64-darwin-23
|
85
|
+
ruby
|
86
|
+
|
87
|
+
DEPENDENCIES
|
88
|
+
benchmark
|
89
|
+
benchmark-ips (~> 2.12)
|
90
|
+
ffi
|
91
|
+
rake (~> 13.0)
|
92
|
+
rake-compiler (~> 1.2)
|
93
|
+
rspec (~> 3.0)
|
94
|
+
rubocop (~> 1.21)
|
95
|
+
rubocop-rake (~> 0.6.0)
|
96
|
+
rubocop-rspec (~> 2.26)
|
97
|
+
simplecov (~> 0.22.0)
|
98
|
+
type_balancer!
|
99
|
+
|
100
|
+
BUNDLED WITH
|
101
|
+
2.6.2
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 Carl Smith
|
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,86 @@
|
|
1
|
+
<img src="https://www.ruby-lang.org/images/header-ruby-logo.png" width="50" align="right" alt="Ruby Logo"/>
|
2
|
+
|
3
|
+
# TypeBalancer
|
4
|
+
|
5
|
+
[](https://badge.fury.io/rb/type_balancer)
|
6
|
+
[](https://github.com/llwebconsulting/type_balancer/actions/workflows/ci.yml)
|
7
|
+
[](https://github.com/llwebconsulting/type_balancer/blob/main/coverage/index.html)
|
8
|
+
[](LICENSE.txt)
|
9
|
+
|
10
|
+
A Ruby gem for balancing and distributing items of different types across a sequence while maintaining optimal spacing.
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem 'type_balancer'
|
18
|
+
```
|
19
|
+
|
20
|
+
And then execute:
|
21
|
+
|
22
|
+
```bash
|
23
|
+
$ bundle install
|
24
|
+
```
|
25
|
+
|
26
|
+
Or install it yourself as:
|
27
|
+
|
28
|
+
```bash
|
29
|
+
$ gem install type_balancer
|
30
|
+
```
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
items = [
|
36
|
+
{ type: 'video', title: 'Video 1' },
|
37
|
+
{ type: 'image', title: 'Image 1' },
|
38
|
+
{ type: 'article', title: 'Article 1' },
|
39
|
+
# ... more items
|
40
|
+
]
|
41
|
+
|
42
|
+
# Balance items by type
|
43
|
+
balanced_items = TypeBalancer.balance(items, type_field: :type)
|
44
|
+
```
|
45
|
+
|
46
|
+
## Performance Characteristics
|
47
|
+
|
48
|
+
TypeBalancer is designed to handle collections of varying sizes efficiently. Here are the current performance metrics:
|
49
|
+
|
50
|
+
- Tiny collections (10 items): Microsecond-level processing (9-14μs)
|
51
|
+
- Small collections (100 items): Sub-millisecond processing (~450-500μs)
|
52
|
+
- Medium collections (1,000 items): Fast processing (~20-21ms)
|
53
|
+
- Large collections (10,000 items): Efficient processing (~193-209ms)
|
54
|
+
|
55
|
+
Performance has been thoroughly tested across Ruby versions (3.2.8, 3.3.7, and 3.4.2). YJIT provides significant improvements for small datasets (up to 51% faster for tiny collections) with varying impact on larger datasets. For detailed benchmarks across Ruby versions and YJIT configurations, see our [benchmark documentation](docs/benchmarks/README.md).
|
56
|
+
|
57
|
+
### Recommendations
|
58
|
+
|
59
|
+
- Suitable for real-time processing of collections up to 10,000 items
|
60
|
+
- Excellent performance for content management systems and feed generation
|
61
|
+
- Thread-safe and memory-efficient
|
62
|
+
- If you need to process very large collections (>10,000 items), consider batch processing or open an issue for guidance
|
63
|
+
|
64
|
+
## Features
|
65
|
+
|
66
|
+
- Maintains optimal spacing between items of the same type
|
67
|
+
- Supports custom type fields
|
68
|
+
- Preserves original item data
|
69
|
+
- Thread-safe
|
70
|
+
- Zero dependencies
|
71
|
+
|
72
|
+
## Development
|
73
|
+
|
74
|
+
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.
|
75
|
+
|
76
|
+
## Contributing
|
77
|
+
|
78
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/llwebconsulting/type_balancer. 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/llwebconsulting/type_balancer/blob/main/CODE_OF_CONDUCT.md).
|
79
|
+
|
80
|
+
## License
|
81
|
+
|
82
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
83
|
+
|
84
|
+
## Code of Conduct
|
85
|
+
|
86
|
+
Everyone interacting in the TypeBalancer project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/llwebconsulting/type_balancer/blob/main/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rake/extensiontask'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
8
|
+
|
9
|
+
require 'rubocop/rake_task'
|
10
|
+
|
11
|
+
RuboCop::RakeTask.new
|
12
|
+
|
13
|
+
Rake::ExtensionTask.new('type_balancer') do |ext|
|
14
|
+
ext.lib_dir = 'lib/type_balancer'
|
15
|
+
ext.ext_dir = 'ext/type_balancer'
|
16
|
+
ext.source_pattern = '*.{c,h}'
|
17
|
+
ext.config_options = ['--with-cflags=-Wall -Wextra -O3']
|
18
|
+
end
|
19
|
+
|
20
|
+
# Add GoogleTest task using CMake
|
21
|
+
namespace :gtest do
|
22
|
+
desc 'Build and run all GoogleTest tests'
|
23
|
+
task :all do
|
24
|
+
Dir.chdir('c_tests') do
|
25
|
+
sh './build.sh'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
desc 'Build and run a specific GoogleTest test (e.g., rake gtest:run[TestSuite.TestName])'
|
30
|
+
task :run, [:test_name] do |_, args|
|
31
|
+
test_filter = args[:test_name] ? "--gtest_filter=#{args[:test_name]}" : ''
|
32
|
+
Dir.chdir('c_tests') do
|
33
|
+
sh "rm -rf build && mkdir build && cd build && cmake .. && make && ./type_balancer_tests #{test_filter} | cat"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
desc 'List all available GoogleTest tests'
|
38
|
+
task :list do
|
39
|
+
Dir.chdir('c_tests') do
|
40
|
+
sh 'rm -rf build && mkdir build && cd build && cmake .. && make && ./type_balancer_tests --gtest_list_tests | cat'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
namespace :lint do
|
46
|
+
desc 'Run C linting with clang-tidy'
|
47
|
+
task :c do
|
48
|
+
mkdir_p 'c_tests/build'
|
49
|
+
Dir.chdir('c_tests/build') do
|
50
|
+
sh 'cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ..'
|
51
|
+
end
|
52
|
+
sh 'clang-tidy -p c_tests/build ext/type_balancer/*.{c,h}'
|
53
|
+
end
|
54
|
+
|
55
|
+
desc 'Run all linting checks'
|
56
|
+
task all: %i[rubocop c]
|
57
|
+
end
|
58
|
+
|
59
|
+
desc 'Run all tests with proper mocking'
|
60
|
+
task test_with_mocks: [:spec] do
|
61
|
+
puts 'Running tests with proper mocking...'
|
62
|
+
Rake::Task['gtest:all'].invoke
|
63
|
+
end
|
64
|
+
|
65
|
+
task default: [:test_with_mocks, 'lint:all']
|
66
|
+
|
67
|
+
# Benchmark tasks
|
68
|
+
namespace :benchmark do
|
69
|
+
desc 'Run distributor benchmark comparing C extension vs Pure Ruby (without YJIT)'
|
70
|
+
task :distributor do
|
71
|
+
ruby 'benchmark/distributor_benchmark.rb'
|
72
|
+
end
|
73
|
+
|
74
|
+
desc 'Run combined benchmark comparing full C vs Ruby implementations (without YJIT)'
|
75
|
+
task :combined do
|
76
|
+
ruby 'benchmark/combined_benchmark.rb'
|
77
|
+
end
|
78
|
+
|
79
|
+
desc 'Run all benchmarks (without YJIT)'
|
80
|
+
task all: %i[distributor combined]
|
81
|
+
|
82
|
+
namespace :yjit do
|
83
|
+
desc 'Run distributor benchmark comparing C extension vs Pure Ruby with YJIT enabled'
|
84
|
+
task :distributor do
|
85
|
+
ENV['RUBY_YJIT_ENABLE'] = '1'
|
86
|
+
ruby 'benchmark/distributor_benchmark.rb'
|
87
|
+
end
|
88
|
+
|
89
|
+
desc 'Run combined benchmark comparing full C vs Ruby implementations with YJIT enabled'
|
90
|
+
task :combined do
|
91
|
+
ENV['RUBY_YJIT_ENABLE'] = '1'
|
92
|
+
ruby 'benchmark/combined_benchmark.rb'
|
93
|
+
end
|
94
|
+
|
95
|
+
desc 'Run all benchmarks with YJIT enabled'
|
96
|
+
task all: %i[distributor combined]
|
97
|
+
end
|
98
|
+
|
99
|
+
desc 'Run all benchmarks with and without YJIT'
|
100
|
+
task complete: %i[all yjit:all]
|
101
|
+
end
|
data/benchmark/README.md
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# TypeBalancer Benchmarks
|
2
|
+
|
3
|
+
This directory contains benchmarks for measuring TypeBalancer's performance across different Ruby versions and configurations.
|
4
|
+
|
5
|
+
## Latest Benchmark Results
|
6
|
+
|
7
|
+
Tests run on ARM64 platform (M-series Mac via Docker) with Ruby versions 3.2.8, 3.3.7, and 3.4.2, both with and without YJIT.
|
8
|
+
|
9
|
+
### Processing Time by Dataset Size
|
10
|
+
|
11
|
+
#### Tiny Dataset (10 items)
|
12
|
+
- Processing time: ~15 microseconds
|
13
|
+
- Throughput: ~65-70k operations/second
|
14
|
+
- Even distribution achieved: 40/30/30 split
|
15
|
+
|
16
|
+
#### Small Dataset (100 items)
|
17
|
+
- Processing time: ~550 microseconds
|
18
|
+
- Throughput: ~1.8k operations/second
|
19
|
+
- Even distribution achieved: 34/33/33 split
|
20
|
+
|
21
|
+
#### Medium Dataset (1,000 items)
|
22
|
+
- Processing time: ~50 milliseconds
|
23
|
+
- Throughput: ~20 operations/second
|
24
|
+
- Even distribution achieved: 33.4/33.3/33.3 split
|
25
|
+
|
26
|
+
#### Large Dataset (10,000 items)
|
27
|
+
- Processing time: ~4.3-4.7 seconds
|
28
|
+
- Throughput: ~0.21-0.23 operations/second
|
29
|
+
- Even distribution achieved: 33.34/33.33/33.33 split
|
30
|
+
|
31
|
+
### Performance Analysis
|
32
|
+
|
33
|
+
#### Ruby Version Comparison (10,000 items)
|
34
|
+
| Ruby Version | YJIT | Time (seconds) |
|
35
|
+
|-------------|------|----------------|
|
36
|
+
| 3.2.8 | No | 4.37 |
|
37
|
+
| 3.2.8 | Yes | 4.29 |
|
38
|
+
| 3.3.7 | No | 4.45 |
|
39
|
+
| 3.3.7 | Yes | 4.31 |
|
40
|
+
| 3.4.2 | No | 4.71 |
|
41
|
+
| 3.4.2 | Yes | 4.71 |
|
42
|
+
|
43
|
+
#### Key Findings
|
44
|
+
1. YJIT Impact:
|
45
|
+
- Provides modest improvements (2-3%) on larger datasets
|
46
|
+
- Most effective with Ruby 3.2.8
|
47
|
+
- Diminishing returns in newer Ruby versions
|
48
|
+
|
49
|
+
2. Scaling Characteristics:
|
50
|
+
- Performance scales non-linearly with dataset size
|
51
|
+
- Sweet spot appears to be around 1,000 items
|
52
|
+
- Processing time increases significantly beyond 1,000 items
|
53
|
+
|
54
|
+
3. Distribution Quality:
|
55
|
+
- Maintains excellent distribution ratios across all dataset sizes
|
56
|
+
- Larger datasets achieve near-perfect distribution (33.33%)
|
57
|
+
|
58
|
+
## Current Limitations and Future Work
|
59
|
+
|
60
|
+
1. Performance Concerns:
|
61
|
+
- Large datasets (>10,000 items) process slower than desired
|
62
|
+
- Non-linear scaling suggests algorithmic optimization opportunities
|
63
|
+
- Current implementation prioritizes distribution quality over speed
|
64
|
+
|
65
|
+
2. Optimization Priorities:
|
66
|
+
- Improve processing time for large datasets
|
67
|
+
- Investigate algorithmic improvements in distribution logic
|
68
|
+
- Explore parallel processing options for large collections
|
69
|
+
|
70
|
+
3. Recommendations:
|
71
|
+
- For production use, process collections of ≤1,000 items at a time
|
72
|
+
- Break larger datasets into smaller batches
|
73
|
+
- Monitor memory usage with very large collections
|
74
|
+
|
75
|
+
## Running Benchmarks
|
76
|
+
|
77
|
+
To run the benchmarks:
|
78
|
+
|
79
|
+
```bash
|
80
|
+
# Run all Ruby versions with and without YJIT
|
81
|
+
./bin/run_benchmarks.sh --platform linux/arm64
|
82
|
+
|
83
|
+
# Run specific version with YJIT
|
84
|
+
./bin/run_benchmarks.sh -v 3.3.8 --yjit --platform linux/arm64
|
85
|
+
|
86
|
+
# Run specific version without YJIT
|
87
|
+
./bin/run_benchmarks.sh -v 3.3.8 --no-yjit --platform linux/arm64
|
88
|
+
```
|
89
|
+
|
90
|
+
Results will be saved in the `benchmark_results` directory.
|
@@ -0,0 +1,65 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "benchmark/ips"
|
6
|
+
require_relative "../lib/type_balancer"
|
7
|
+
|
8
|
+
# Print Ruby and platform info
|
9
|
+
puts "Ruby version: #{RUBY_VERSION}"
|
10
|
+
puts "RUBY_PLATFORM: #{RUBY_PLATFORM}"
|
11
|
+
puts "YJIT enabled: #{RubyVM.const_defined?(:YJIT) && RubyVM::YJIT.enabled?}"
|
12
|
+
|
13
|
+
# Test data generator
|
14
|
+
def generate_test_data(size)
|
15
|
+
types = %w[video image article]
|
16
|
+
Array.new(size) do |i|
|
17
|
+
{
|
18
|
+
id: i,
|
19
|
+
type: types[i % types.size],
|
20
|
+
title: "Item #{i}"
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# More granular test cases
|
26
|
+
TEST_CASES = [
|
27
|
+
{ name: "Tiny Dataset", size: 10 },
|
28
|
+
{ name: "Small Dataset", size: 100 },
|
29
|
+
{ name: "Medium Dataset", size: 1_000 },
|
30
|
+
{ name: "Large Dataset", size: 10_000 }
|
31
|
+
]
|
32
|
+
|
33
|
+
def run_benchmark
|
34
|
+
puts "\nRunning benchmarks..."
|
35
|
+
|
36
|
+
TEST_CASES.each do |test_case|
|
37
|
+
puts "\nBenchmarking #{test_case[:name]} (#{test_case[:size]} items)"
|
38
|
+
collection = generate_test_data(test_case[:size])
|
39
|
+
|
40
|
+
# Single warmup run to ensure everything is loaded
|
41
|
+
TypeBalancer.balance(collection, type_field: :type)
|
42
|
+
|
43
|
+
# Run benchmark
|
44
|
+
Benchmark.ips do |bm|
|
45
|
+
# Adjust warmup/time based on dataset size
|
46
|
+
warmup_time = test_case[:size] <= 100 ? 1 : 2
|
47
|
+
bench_time = test_case[:size] <= 100 ? 2 : 3
|
48
|
+
|
49
|
+
bm.config(time: bench_time, warmup: warmup_time)
|
50
|
+
bm.report("Ruby Implementation") do
|
51
|
+
TypeBalancer.balance(collection, type_field: :type)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Print distribution stats for verification
|
56
|
+
result = TypeBalancer.balance(collection, type_field: :type)
|
57
|
+
puts "\nDistribution Stats:"
|
58
|
+
%w[video image article].each do |type|
|
59
|
+
count = result.count { |i| i[:type] == type }
|
60
|
+
puts "#{type.capitalize}: #{count} (#{(count.to_f / test_case[:size] * 100).round(2)}%)"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
run_benchmark
|