{"id":13467713,"url":"https://github.com/danielpclark/faster_path","last_synced_at":"2025-05-15T15:06:00.341Z","repository":{"id":56845594,"uuid":"60771060","full_name":"danielpclark/faster_path","owner":"danielpclark","description":"Faster Pathname handling for Ruby written in Rust","archived":false,"fork":false,"pushed_at":"2019-10-22T23:42:34.000Z","size":1239,"stargazers_count":778,"open_issues_count":10,"forks_count":29,"subscribers_count":22,"default_branch":"master","last_synced_at":"2025-05-10T17:47:40.603Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","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/danielpclark.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"MIT-LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-06-09T11:51:40.000Z","updated_at":"2025-01-25T02:54:22.000Z","dependencies_parsed_at":"2022-09-26T20:11:43.964Z","dependency_job_id":null,"html_url":"https://github.com/danielpclark/faster_path","commit_stats":null,"previous_names":[],"tags_count":42,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielpclark%2Ffaster_path","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielpclark%2Ffaster_path/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielpclark%2Ffaster_path/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielpclark%2Ffaster_path/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danielpclark","download_url":"https://codeload.github.com/danielpclark/faster_path/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254364270,"owners_count":22058878,"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":[],"created_at":"2024-07-31T15:00:59.673Z","updated_at":"2025-05-15T15:06:00.282Z","avatar_url":"https://github.com/danielpclark.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# FasterPath\n[![Gem Version](https://badge.fury.io/rb/faster_path.svg)](https://badge.fury.io/rb/faster_path)\n[![TravisCI Build Status](https://travis-ci.org/danielpclark/faster_path.svg?branch=master)](https://travis-ci.org/danielpclark/faster_path)\n[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/10ul0gk3cwhlt2lj/branch/master?svg=true)](https://ci.appveyor.com/project/danielpclark/faster-path/branch/master)\n[![Latest Tag](https://img.shields.io/github/tag/danielpclark/faster_path.svg)](https://github.com/danielpclark/faster_path/tags)\n[![Commits Since Last Release](https://img.shields.io/github/commits-since/danielpclark/faster_path/v0.3.10.svg)](https://github.com/danielpclark/faster_path/pulse)\n[![Binary Release](https://img.shields.io/github/release/danielpclark/faster_path.svg)](https://github.com/danielpclark/faster_path/releases)\n[![Coverage Status](https://coveralls.io/repos/github/danielpclark/faster_path/badge.svg?branch=master)](https://coveralls.io/github/danielpclark/faster_path?branch=master)\n[![Inline docs](http://inch-ci.org/github/danielpclark/faster_path.svg?branch=master)](http://inch-ci.org/github/danielpclark/faster_path)\n[![Code Triagers Badge](https://www.codetriage.com/danielpclark/faster_path/badges/users.svg)](https://www.codetriage.com/danielpclark/faster_path)\n\n#### This gem shaves off more than 30% of my Rails application page load time.\n\nThe primary **GOAL** of this project is to improve performance in the most heavily used areas of Ruby as\npath relation and file lookup is currently a huge bottleneck in performance.  As this is the case the\npath performance updates will likely not be limited to just changing the Pathname class but also will\nbe offering changes in related methods and classes.\n\nUsers will have the option to write their apps directly for this library, or they can choose to either\nrefine or monkeypatch the existing standard library.  Refinements are narrowed to scope and monkeypatching will\nbe a sledge hammer ;-)\n\n## Why\n\nI read a blog post about the new Sprockets 3.0 series being faster than the 2.0 series so I tried it out.  It was not faster but rather it made my website take 31.8% longer to load.  So I reverted back to the 2.0 series and I did a check on Rails on what methods were being called the most and where the application spends\nmost of its time.  It turns out roughly 80% _(as far as I can tell)_ of the time spent and calls made\nare file Path handling.  This is shocking, but it only gets worse when handling assets.  **That is\nwhy we need to deal with these load heavy methods in the most efficient manner!**\n\nHere's a snippet of a Rails stack profile with some of the most used and time expensive methods.\n\n```\nBooting: development\nEndpoint: \"/\"\n       user     system      total        real\n100 requests 26.830000   1.780000  28.610000 ( 28.866952)\nRunning `stackprof tmp/2016-06-09T00:42:10-04:00-stackprof-cpu-myapp.dump`. Execute `stackprof --help` for more info\n==================================\n  Mode: cpu(1000)\n  Samples: 7184 (0.03% miss rate)\n  GC: 1013 (14.10%)\n==================================\n     TOTAL    (pct)     SAMPLES    (pct)     FRAME\n      1894  (26.4%)        1894  (26.4%)     Pathname#chop_basename\n      1466  (20.4%)         305   (4.2%)     Pathname#plus\n      1628  (22.7%)         162   (2.3%)     Pathname#+\n       234   (3.3%)         117   (1.6%)     ActionView::PathResolver#find_template_paths\n      2454  (34.2%)          62   (0.9%)     Pathname#join\n        57   (0.8%)          52   (0.7%)     ActiveSupport::FileUpdateChecker#watched\n       760  (10.6%)          47   (0.7%)     Pathname#relative?\n       131   (1.8%)          25   (0.3%)     ActiveSupport::FileUpdateChecker#max_mtime\n        88   (1.2%)          21   (0.3%)     Sprockets::Asset#dependency_fresh?\n        18   (0.3%)          18   (0.3%)     ActionView::Helpers::AssetUrlHelper#compute_asset_extname\n       108   (1.5%)          14   (0.2%)     ActionView::Helpers::AssetUrlHelper#asset_path\n```\n\nHere are some additional stats.  From Rails loading to my home page, these methods are called _(not directly, Rails \u0026 gems call them)_ this many times.  And the home page has minimal content.\n```ruby\nPathname#to_s called 29172 times.\nPathname#\u003c=\u003e called 24963 times.\nPathname#chop_basename called 24456 times\nPathname#initialize called 23103 times.\nFile#initialize called 23102 times.\nPathname#absolute? called 4840 times.\nPathname#+ called 4606 times.\nPathname#plus called 4606 times.\nPathname#join called 4600 times.\nPathname#extname called 4291 times.\nPathname#hash called 4207 times.\nPathname#to_path called 2706 times.\nPathname#directory? called 2396 times.\nPathname#entries called 966 times.\nDir#each called 966 times.\nPathname#basename called 424 times.\nPathname#prepend_prefix called 392 times.\nPathname#cleanpath called 392 times.\nPathname#cleanpath_aggressive called 392 times.\nPathname#split called 161 times.\nPathname#open called 153 times.\nPathname#exist? called 152 times.\nPathname#sub called 142 times.\n```\n\nAfter digging further I've found that Pathname is heavily used in Sprockets 2 but in Sprockets 3 they switched to calling Ruby's faster methods from `File#initialize` and `Dir#each`.  It appears they've written all of the path handling on top of these themselves in Ruby.  They achieved some performance gain by switching to rawer code methods, but then they lost more than that in performance by the **many** method calls built on top of that.\n\nIf you want to see the best results in Rails with this gem you will likely need to be using the Sprockets 2.0 series.  Otherwise this library would need to rewrite Sprockets itself.\n\nI've said this about Sprockets but this required two other gems to be updated as well.  These are the gems and versions I upgraded and consider group 1 (Sprockets 2) and group 2 (Sprockets 3).  My data is based on method calls rather than source code.\n\n|Sprockets 2 Group|Sprockets 3 Group|\n|:---:|:---:|\n|sprockets 2.12.4|sprockets 3.6|\n|sass 3.2.19|sass 5.0.4|\n|bootstrap-sass 3.3.4.1|bootstrap-sass 3.3.6|\n\n## Performance Specifics\n\nThe headline for the amount for improvement on this library is specific to only the improvement made with the method `chop_basename`.  Just so you know; in my initial release I had a bug in which that method immediately returned nothing. Now the good thing about this is that it gave me some very valuable information.  First I found that all my Rails site tests still passed.  Second I found that all my assets no longer loaded in the website. And third, and most importantly, I found my Rails web pages loaded just more than 66% faster without the cost of time that `chop_basename` took.\n\n**That's right; the path handling for assets in your website \\*consumes more than 2/3rds of your websites page load time.**\n\nSo now we have some real numbers to work with  We can be generoues and use 66% as our margin of area to improve over _(for `chop_basename` specifically, not counting the benefit from improving the performance in other file path related methods)_.  That means we want to remove as much of that percentage from the overall systems page load time.  The original headline boasts over 33% performance improvement — that was when `chop_basename` was improved by just over 50%.  Now `chop_basename` is improved by 83.4%.  That alone should make your site run 55.044% faster now _(given your performance profile stats are similar to mine)_.\n\n## What Rails Versions Will This Apply To?\n\nAs mentioned earlier Sprockets, which handles assets, changed away from using `Pathname` at all when moving from major version 2 to 3.  So if you're using Sprockets 3 or later you won't reap the biggest performance rewards from using this gem for now _(it's my goal to have this project become a core feature that Rails depends on and yes… that's a big ask)_.  That is unless you write you're own implementation to re-integrate the use of `Pathname` and `FasterPath` into your asset handling library.  For now just know that the Sprockets 2 series primarily works with Rails 4.1 and earlier.  It may work in later Rails versions but I have not investigated this.\n\n## Status\n\n* Rust compilation is working\n* Methods are stable\n* Thoroughly tested\n* Testers and developers are most welcome\n* Windows \u0026 encoding support is underway!\n\n## Installation\n\nEnsure Rust is installed:\n\n[Rust Downloads](https://www.rust-lang.org/downloads.html)\n\n```\ncurl -sSf https://static.rust-lang.org/rustup.sh | sh\n```\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'faster_path', '~\u003e 0.3.10'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install faster_path\n\n## Visual Benchmarks\n\nBenchmarks in Faster Path now produce visual graph charts of performance improvements.\nWhen you run `export GRAPH=1; bundle \u0026\u0026 rake bench` the graph art will be placed in `doc/graph/`.  Here's the performance\nimprovement result  for the `chop_basename` method.\n\n![Visual Benchmark](https://raw.githubusercontent.com/danielpclark/faster_path/master/assets/chop_basename_benchmark.jpg \"Visual Benchmark\")\n\n## Usage\n\nAdd the proper require to your project.\n\n```ruby\nrequire \"faster_path\"\n```\n\nCurrent methods implemented:\n\n|FasterPath Rust Implementation|Ruby 2.5.0 Implementation|Time Shaved Off|\n|---|---|:---:|\n| `FasterPath.absolute?` | `Pathname#absolute?` | 95.3% |\n| `FasterPath.add_trailing_separator` | `Pathname#add_trailing_separator` | 48.4% |\n| `FasterPath.basename` | `File.basename` | 12.0% |\n| `FasterPath.children` | `Pathname#children` | 34.4% |\n| `FasterPath.chop_basename` | `Pathname#chop_basename` | 83.4% |\n| `FasterPath.cleanpath_aggressive` | `Pathname#cleanpath_aggressive` | 94.1% |\n| `FasterPath.cleanpath_conservative` | `Pathname#cleanpath_conservative` | 93.5% |\n| `FasterPath.del_trailing_separator` | `Pathname#del_trailing_separator` | 85.4% |\n| `FasterPath.directory?` | `Pathname#directory?` | 6.4% |\n| `FasterPath.dirname` | `File.dirname` | 55.4% |\n| `FasterPath.entries` | `Pathname#entries` | 41.0% |\n| `FasterPath.extname` | `File.extname` | 63.1% |\n| `FasterPath.has_trailing_separator?` | `Pathname#has_trailing_separator` | 88.9% |\n| `FasterPath.plus` | `Pathname#join` | 79.1% |\n| `FasterPath.plus` | `Pathname#plus` | 94.7% |\n| `FasterPath.relative?` | `Pathname#relative?` | 92.6% |\n| `FasterPath.relative_path_from` | `Pathname#relative_path_from` | 93.3% |\n\nYou may choose to use the methods directly, or scope change to rewrite behavior on the\nstandard library with the included refinements, or even call a method to monkeypatch\neverything everywhere.\n\nFor the scoped **refinements** you will need to\n\n```\nrequire \"faster_path/optional/refinements\"\nusing FasterPath::RefinePathname\n```\n\nAnd for the sledgehammer of monkey patching you can do\n\n```\nrequire \"faster_path/optional/monkeypatches\"\nFasterPath.sledgehammer_everything!\n```\n\n## Optional Rust implementations\n\n**These are stable, not performant, and not included in `Pathname` by default.**\n\nThese will **not** be included by default in monkey-patches.  To try them with monkeypatching use the environment flag of `WITH_REGRESSION`.  These methods are here to be improved upon.\n\n|FasterPath Implementation|Ruby Implementation|\n|---|---|\n| `FasterPath.entries_compat` | `Pathname.entries` |\n| `FasterPath.children_compat` | `Pathname.children` |\n\nIt's been my observation (and some others) that the Rust implementation of the C code for `File` has similar results but\nperformance seems to vary based on CPU cache on possibly 64bit/32bit system environments.  These are not included by default when the monkey patch method `FasterPath.sledgehammer_everything!` is executed.\n\n## Getting Started with Development\n\nThe primary methods to target are mostly listed in the **Why** section above.  You may find the Ruby\nsource code useful for Pathname's [Ruby source](https://github.com/ruby/ruby/blob/32674b167bddc0d737c38f84722986b0f228b44b/ext/pathname/lib/pathname.rb),\n[C source](https://github.com/ruby/ruby/blob/32674b167bddc0d737c38f84722986b0f228b44b/ext/pathname/pathname.c),\n[tests](https://github.com/ruby/ruby/blob/32674b167bddc0d737c38f84722986b0f228b44b/test/pathname/test_pathname.rb),\nand checkout the [documentation](http://ruby-doc.org/stdlib-2.3.1/libdoc/pathname/rdoc/Pathname.html).\n\nMethods will be written as exclusively in Rust as possible.  Even just writing a **not** in Ruby with a\nRust method like `!absolute?` _(not absolute)_ drops 39% of the performance already gained in Rust.\nWhenever feasible implement it in Rust.\n\nAfter checking out the repo, make sure you have Rust installed, then run `bundle`.\nRun `rake test` to run the tests, and `rake bench` for benchmarks.\n\n### Building and running tests\n\nFirst, bundle the gem's development dependencies by running `bundle`.  Rust compilation is included in the current rake commands.\n\nFasterPath is tested with [The Ruby Spec Suite](https://github.com/ruby/spec) to ensure it is compatible with the\nnative implementation, and also has its own test suite testing its monkey-patching and refinements functionality.\n\nTo run all the tests at once, simply run `rake`.\nTo run all the ruby spec tests, run `mspec`.\n\nTo run an individual test or benchmark from FasterPath's own suite:\n\n```sh\n# An individual test file:\nruby -I lib:test test/benches/absolute_benchmark.rb\n# All tests:\nrake minitest\n```\n\nTo run an individual ruby spec test, run `mspec` with a path relative to `spec/ruby_spec`, e.g.:\n\n```sh\n# A path to a file or a directory:\nmspec core/file/basename_spec.rb\n# Tests most relevant to FasterPath:\nmspec core/file library/pathname\n# All tests:\nmspec\n```\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/danielpclark/faster_path.\n\n\n## License\n\n[MIT License](http://opensource.org/licenses/MIT) or APACHE 2.0 at your pleasure.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielpclark%2Ffaster_path","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanielpclark%2Ffaster_path","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielpclark%2Ffaster_path/lists"}