{"id":13879220,"url":"https://github.com/piotrmurach/benchmark-trend","last_synced_at":"2025-06-12T13:09:26.895Z","repository":{"id":62355191,"uuid":"147726651","full_name":"piotrmurach/benchmark-trend","owner":"piotrmurach","description":"Measure performance trends of Ruby code","archived":false,"fork":false,"pushed_at":"2024-03-15T23:17:39.000Z","size":78,"stargazers_count":60,"open_issues_count":0,"forks_count":4,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-05-21T08:46:12.323Z","etag":null,"topics":["benchmarking","complexity","performance-analysis","performance-testing","ruby","rubygem"],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/piotrmurach.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"piotrmurach"}},"created_at":"2018-09-06T19:58:42.000Z","updated_at":"2024-06-06T00:36:53.000Z","dependencies_parsed_at":"2024-01-13T10:40:51.699Z","dependency_job_id":"fb54e1bd-88fe-4d93-a48f-100e843d5744","html_url":"https://github.com/piotrmurach/benchmark-trend","commit_stats":{"total_commits":105,"total_committers":3,"mean_commits":35.0,"dds":0.01904761904761909,"last_synced_commit":"72fc39dec5f8cef6e4f3ce560582aadcb4a3b69e"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/piotrmurach/benchmark-trend","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Fbenchmark-trend","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Fbenchmark-trend/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Fbenchmark-trend/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Fbenchmark-trend/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/piotrmurach","download_url":"https://codeload.github.com/piotrmurach/benchmark-trend/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Fbenchmark-trend/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259470951,"owners_count":22862999,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["benchmarking","complexity","performance-analysis","performance-testing","ruby","rubygem"],"created_at":"2024-08-06T08:02:14.000Z","updated_at":"2025-06-12T13:09:26.875Z","avatar_url":"https://github.com/piotrmurach.png","language":"Ruby","funding_links":["https://github.com/sponsors/piotrmurach"],"categories":["Ruby"],"sub_categories":[],"readme":"# Benchmark::Trend\n\n[![Gem Version](https://badge.fury.io/rb/benchmark-trend.svg)][gem]\n[![Actions CI](https://github.com/piotrmurach/benchmark-trend/workflows/CI/badge.svg?branch=master)][gh_actions_ci]\n[![Build status](https://ci.appveyor.com/api/projects/status/798apneaa8ixg5dk?svg=true)][appveyor]\n[![Maintainability](https://api.codeclimate.com/v1/badges/782faa4a8a4662c86792/maintainability)][codeclimate]\n[![Coverage Status](https://coveralls.io/repos/github/piotrmurach/benchmark-trend/badge.svg?branch=master)][coverage]\n[![Inline docs](http://inch-ci.org/github/piotrmurach/benchmark-trend.svg?branch=master)][inchpages]\n\n[gem]: http://badge.fury.io/rb/benchmark-trend\n[gh_actions_ci]: https://github.com/piotrmurach/benchmark-trend/actions?query=workflow%3ACI\n[appveyor]: https://ci.appveyor.com/project/piotrmurach/benchmark-trend\n[codeclimate]: https://codeclimate.com/github/piotrmurach/benchmark-trend/maintainability\n[coverage]: https://coveralls.io/github/piotrmurach/benchmark-trend?branch=master\n[inchpages]: http://inch-ci.org/github/piotrmurach/benchmark-trend\n\n\u003e Measure performance trends of Ruby code based on the input size distribution.\n\n**Benchmark::Trend** will help you estimate the computational complexity of Ruby code by running it on inputs increasing in size, measuring their execution times, and then fitting these observations into a model that best predicts how a given Ruby code will scale as a function of growing workload.\n\n## Why?\n\nTests provide safety net that ensures your code works correctly. What you don't know is how fast your code is! How does it scale with different input sizes? Your code may have computational complexity that doesn't scale with large workloads. It would be good to know before your application goes into production, wouldn't it?\n\n**Benchmark::Trend** will allow you to uncover performance bugs or confirm that a Ruby code performance scales as expected.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'benchmark-trend'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install benchmark-trend\n\n## Contents\n\n* [1. Usage](#1-usage)\n* [2. API](#2-api)\n  * [2.1 range](#21-range)\n  * [2.2 infer_trend](#22-infer_trend)\n    * [2.2.1 repeat](#221-repeat)\n  * [2.3 fit](#23-fit)\n  * [2.4 fit_at](#24-fit_at)\n* [3. Examples](#3-examples)\n  * [3.1 Ruby array max](#31-ruby-array-max)\n\n## 1. Usage\n\nLet's assume we would like to find out behaviour of a Fibonacci algorithm:\n\n```ruby\ndef fibonacci(n)\n  n \u003c 2 ? n : fibonacci(n - 1) + fibonacci(n - 2)\nend\n```\n\nTo measure the actual complexity of above function, we will use `infer_trend` method and pass it as a first argument an array of integer sizes and a block to execute the method:\n\n```ruby\nnumbers = Benchmark::Trend.range(1, 28, ratio: 2)\n\ntrend, trends = Benchmark::Trend.infer_trend(numbers) do |n, i|\n  fibonacci(n)\nend\n```\n\nThe return type will provide a best trend name:\n\n```ruby\nprint trend\n# =\u003e exponential\n```\n\nand a Hash of all the trend data:\n\n```ruby\nprint trends\n# =\u003e\n# {:exponential=\u003e\n#   {:trend=\u003e\"1.38 * 0.00^x\",\n#    :slope=\u003e1.382889711685203,\n#    :intercept=\u003e3.822775903539121e-06,\n#    :residual=\u003e0.9052392775178072},\n#  :power=\u003e\n#   {:trend=\u003e\"0.00 * x^2.11\",\n#    :slope=\u003e2.4911044372815657e-06,\n#    :intercept=\u003e2.1138475434240918,\n#    :residual=\u003e0.5623418036957115},\n#  :linear=\u003e\n#   {:trend=\u003e\"0.00 + -0.01*x\",\n#    :slope=\u003e0.0028434594496586007,\n#    :intercept=\u003e-0.01370769842204958,\n#    :residual=\u003e0.7290365425188893},\n#  :logarithmic=\u003e\n#   {:trend=\u003e\"0.02 + -0.02*ln(x)\",\n#    :slope=\u003e0.01738674709454521,\n#    :intercept=\u003e-0.015489004560847924,\n#    :residual=\u003e0.3982368125757882}}\n```\n\nYou can see information for the best trend by passing name into trends hash:\n\n```ruby\nprint trends[trend]\n# =\u003e\n# {:trend=\u003e\"1.38 * 0.00^x\",\n#  :slope=\u003e1.382889711685203,\n#  :intercept=\u003e3.822775903539121e-06,\n#  :residual=\u003e0.9052392775178072},\n```\n\n## 2. API\n\n### 2.1 range\n\nTo generate a range of values for testing code fitness use the `range` method. It will generate a geometric sequence of numbers, where intermediate values are powers of range multiplier, by default 8:\n\n```ruby\nBenchmark::Trend.range(8, 8 \u003c\u003c 10)\n# =\u003e [8, 64, 512, 4096, 8192]\n```\n\nYou can change the default sequence power by using `:ratio` keyword:\n\n```ruby\nBenchmark::Trend.range(8, 8 \u003c\u003c 10, ratio: 2)\n# =\u003e [8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192]\n```\n\n### 2.2 infer_trend\n\nTo calculate an asymptotic behaviour of Ruby code by inferring its computational complexity use `infer_trend`. This method takes as an argument an array of inputs which can be generated using [range](#21-range). The code to measure needs to be provided inside a block. Two parameters are always yielded to a block, first, the actual data input and second the current index matching the input.\n\nFor example, let's assume you would like to find out asymptotic behaviour of a Fibonacci algorithm:\n\n```ruby\ndef fibonacci(n)\n  n \u003c 2 ? n : fibonacci(n - 1) + fibonacci(n - 2)\nend\n```\n\nYou could start by generating a range of inputs in powers of 2:\n\n```ruby\nnumbers = Benchmark::Trend.range(1, 32, ratio: 2)\n# =\u003e [1, 2, 4, 8, 16, 32]\n```\n\nThen measure the performance of the Fibonacci algorithm for each of the data points and fit the observations into a model to predict behaviour as a function of input size:\n\n```ruby\ntrend, trends = Benchmark::Trend.infer_trend(numbers) do |n, i|\n  fibonacci(n)\nend\n```\n\nThe return includes the best fit name:\n\n```ruby\nprint trend\n# =\u003e exponential\n```\n\nAnd a Hash of all measurements:\n\n```ruby\nprint trends\n# =\u003e\n# {:exponential=\u003e\n#   {:trend=\u003e\"1.38 * 0.00^x\",\n#    :slope=\u003e1.382889711685203,\n#    :intercept=\u003e3.822775903539121e-06,\n#    :residual=\u003e0.9052392775178072},\n#  :power=\u003e\n#   {:trend=\u003e\"0.00 * x^2.11\",\n#    :slope=\u003e2.4911044372815657e-06,\n#    :intercept=\u003e2.1138475434240918,\n#    :residual=\u003e0.5623418036957115},\n#  :linear=\u003e\n#   {:trend=\u003e\"0.00 + -0.01*x\",\n#    :slope=\u003e0.0028434594496586007,\n#    :intercept=\u003e-0.01370769842204958,\n#    :residual=\u003e0.7290365425188893},\n#  :logarithmic=\u003e\n#   {:trend=\u003e\"0.02 + -0.02*ln(x)\",\n#    :slope=\u003e0.01738674709454521,\n#    :intercept=\u003e-0.015489004560847924,\n#    :residual=\u003e0.3982368125757882}}\n```\n\nIn order to retrieve trend data for the best fit do:\n\n```ruby\nprint trends[trend]\n# =\u003e\n# {:trend=\u003e\"1.38 * 0.00^x\",\n#  :slope=\u003e1.382889711685203,\n#  :intercept=\u003e3.822775903539121e-06,\n#  :residual=\u003e0.9052392775178072}\n```\n\n### 2.2.1 repeat\n\nTo increase stability of you tests consider repeating all time execution measurements using `:repeat` keyword.\n\nStart by generating a range of inputs for your algorithm:\n\n```ruby\nnumbers = Benchmark::Trend.range(1, 32, ratio: 2)\n# =\u003e [1, 2, 4, 8, 16, 32]\n```\n\nand then run your algorithm for each input repeating measurements `100` times:\n\n```ruby\nBenchmark::Trend.infer_trend(numbers, repeat: 100) { |n, i| ... }\n```\n\n### 2.3 fit\n\nUse `fit` method if you wish to fit arbitrary data into a model with a slope and intercept parameters that minimize the error.\n\nFor example, given a set of data points that exhibit linear behaviour:\n\n```ruby\nxs = [1, 2, 3, 4, 5]\nys = xs.map { |x| 3.0 * x + 1.0 }\n```\n\nFit the data into a model:\n\n```ruby\nslope, intercept, error = Benchmark::Trend.fit(xs, ys)\n```\n\nAnd printing the values we get confirmation of the linear behaviour of the data points:\n\n```ruby\nprint slope\n# =\u003e 3.0\nprint intercept\n# =\u003e 1.0\nprint error\n# =\u003e 1.0\n```\n\n### 2.4 fit_at\n\nIf you are interested how a model scales for a given input use `fit_at`. This method expects that there is a fit model generated using [infer_trend](#22-infer_trend).\n\nFor example, measuring Fibonacci recursive algorithm:\n\n```ruby\nnumbers = Benchmark::Trend.range(1, 28, ratio: 2)\ntrend, trends = Benchmark::Trend.infer_trend(numbers) do |n, i|\n  fibonacci(n)\nend\n```\n\nWe get the following results:\n\n```ruby\ntrends[trend]\n# =\u003e\n# {:trend=\u003e\"1.38 * 0.00^x\",\n#  :slope=\u003e1.382889711685203,\n#  :intercept=\u003e3.822775903539121e-06,\n#  :residual=\u003e0.9052392775178072}\n```\n\nAnd checking model at input of `50`:\n\n```ruby\nBenchamrk::Trend.fit_at(trend, n: 50, slope: trends[trend][:slope], intercept: trends[trend][:intercept])\n# =\u003e 41.8558455915123\n```\n\nWe can see that Fibonacci with just a number 50 will take around 42 seconds to get the result!\n\nHow about Fibonacci with 100 as an input?\n\n```ruby\nBenchamrk::Trend.fit_at(trend, n: 100, slope: trends[trend][:slope], intercept: trends[trend][:intercept])\n# =\u003e 458282633.9777338\n```\n\nThis means Fibonacci recursive algorithm will take about 1.45 year to complete!\n\n## 3. Examples\n\n### 3.1 Ruby array max\n\nSuppose you wish to find an asymptotic behaviour of Ruby built Array `max` method.\n\nYou could start with generating a [range](#21-range) of inputs:\n\n```ruby\narray_sizes = Benchmark::Trend.range(1, 100_000)\n# =\u003e [1, 8, 64, 512, 4096, 32768, 100000]\n```\n\nNext, based on the generated ranges create arrays containing randomly generated integers:\n\n```ruby\nnumber_arrays = array_sizes.map { |n| Array.new(n) { rand(n) } }\n```\n\nThen feed this information to infer a trend:\n\n```ruby\ntrend, trends = Benchmark::Trend.infer_trend(array_sizes) do |n, i|\n  number_arrays[i].max\nend\n```\n\nUnsurprisingly, we discover that Ruby's `max` call scales linearily with the input size:\n\n```ruby\nprint trend\n# =\u003e linear\n```\n\nWe can also see from the residual value that this is a near perfect fit:\n\n```ruby\nprint trends[trend]\n# =\u003e\n# {:trend=\u003e\"0.00 + 0.00*x\",\n#  :slope=\u003e5.873536409841244e-09,\n#  :intercept=\u003e3.028647045635842e-05,\n#  :residual=\u003e0.9986764704492359}\n```\n\n## Development\n\nAfter 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.\n\nTo 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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/benchmark-trend. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the Benchmark::Trend project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/piotrmurach/benchmark-trend/blob/master/CODE_OF_CONDUCT.md).\n\n## Copyright\n\nCopyright (c) 2018 Piotr Murach. See LICENSE for further details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiotrmurach%2Fbenchmark-trend","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpiotrmurach%2Fbenchmark-trend","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiotrmurach%2Fbenchmark-trend/lists"}