1 个不稳定版本

使用旧的 Rust 2015

0.0.2 2017 年 3 月 3 日

#596文件系统

MIT/Apache

5.5MB
153K SLoC

Ruby 147K SLoC // 0.0% comments C 5K SLoC // 0.0% comments Rust 380 SLoC // 0.1% comments

FasterPath

Gem Version TravisCI Build Status AppVeyor Build Status Latest Tag Commits Since Last Release Binary Release Coverage Status Inline docs Code Triagers Badge Tweet This

这个库为我的 Rails 应用页面加载时间节省了超过 30%。

本项目的 主要目标 是提高 Ruby 最常用区域的性能,因为路径关系和文件查找目前是性能的巨大瓶颈。因此,路径性能更新可能不仅限于更改 Pathname 类,还将涉及相关方法和类的更改。

用户可以选择直接为此库编写应用程序,或者选择改进或修补现有的标准库。改进是缩小范围,修补将是重型锤子 ;-)

原因

我阅读了一篇关于新的 Sprockets 3.0 系列比 2.0 系列更快的博客文章,所以我试了一下。它没有更快,反而使我的网站加载时间增加了 31.8%。所以我回到了 2.0 系列,并在 Rails 上检查了被调用最多的方法和应用程序大部分时间花费在哪里。结果大约 80% (据我所知) 的时间和调用都是文件路径处理。这是令人震惊的,但处理资产时会更糟。 这就是为什么我们需要以最有效的方式处理这些负载密集的方法!

这里是一个 Rails 堆栈分析片段,其中包含一些最常用且耗时的方法。

Booting: development
Endpoint: "/"
       user     system      total        real
100 requests 26.830000   1.780000  28.610000 ( 28.866952)
Running `stackprof tmp/2016-06-09T00:42:10-04:00-stackprof-cpu-myapp.dump`. Execute `stackprof --help` for more info
==================================
  Mode: cpu(1000)
  Samples: 7184 (0.03% miss rate)
  GC: 1013 (14.10%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
      1894  (26.4%)        1894  (26.4%)     Pathname#chop_basename
      1466  (20.4%)         305   (4.2%)     Pathname#plus
      1628  (22.7%)         162   (2.3%)     Pathname#+
       234   (3.3%)         117   (1.6%)     ActionView::PathResolver#find_template_paths
      2454  (34.2%)          62   (0.9%)     Pathname#join
        57   (0.8%)          52   (0.7%)     ActiveSupport::FileUpdateChecker#watched
       760  (10.6%)          47   (0.7%)     Pathname#relative?
       131   (1.8%)          25   (0.3%)     ActiveSupport::FileUpdateChecker#max_mtime
        88   (1.2%)          21   (0.3%)     Sprockets::Asset#dependency_fresh?
        18   (0.3%)          18   (0.3%)     ActionView::Helpers::AssetUrlHelper#compute_asset_extname
       108   (1.5%)          14   (0.2%)     ActionView::Helpers::AssetUrlHelper#asset_path

以下是更多统计数据。从 Rails 加载到我的主页,这些方法被调用了这么多次。而且主页内容很少。

Pathname#to_s called 29172 times.
Pathname#<=> called 24963 times.
Pathname#chop_basename called 24456 times
Pathname#initialize called 23103 times.
File#initialize called 23102 times.
Pathname#absolute? called 4840 times.
Pathname#+ called 4606 times.
Pathname#plus called 4606 times.
Pathname#join called 4600 times.
Pathname#extname called 4291 times.
Pathname#hash called 4207 times.
Pathname#to_path called 2706 times.
Pathname#directory? called 2396 times.
Pathname#entries called 966 times.
Dir#each called 966 times.
Pathname#basename called 424 times.
Pathname#prepend_prefix called 392 times.
Pathname#cleanpath called 392 times.
Pathname#cleanpath_aggressive called 392 times.
Pathname#split called 161 times.
Pathname#open called 153 times.
Pathname#exist? called 152 times.
Pathname#sub called 142 times.

在进一步挖掘后,我发现 Pathname 在 Sprockets 2 中使用得很频繁,但在 Sprockets 3 中,它们切换到调用 Ruby 的更快方法,即 File#initializeDir#each。看起来他们自己用 Ruby 实现了所有的路径处理。通过切换到更原始的代码方法,他们实现了一些性能提升,但随后在构建在之上的许多方法调用中损失了更多性能。

如果您想在这个库中在 Rails 中看到最佳结果,您可能需要使用 Sprockets 2.0 系列。否则,这个库需要重写 Sprockets 本身。

我以前也提到过关于Sprockets的事情,但这还需要更新两个其他gem。这些是我升级的gem和版本,我将其视为第一组(Sprockets 2)和第二组(Sprockets 3)。我的数据基于方法调用,而不是源代码。

Sprockets 2 组 Sprockets 3 组
sprockets 2.12.4 sprockets 3.6
sass 3.2.19 sass 5.0.4
bootstrap-sass 3.3.4.1 bootstrap-sass 3.3.6

状态

  • Rust编译正在运行
  • 方法最可能是稳定的
  • 欢迎测试人员和开发者加入!

安装

确保已安装Rust

Rust下载

curl -sSf https://static.rust-lang.org/rustup.sh | sh

将以下行添加到您的应用程序的Gemfile中

gem 'faster_path', '~> 0.1.0'

然后执行

$ bundle

或者您可以自行安装

$ gem install faster_path

MAC用户:目前Mac用户需要手动安装扩展。转到gem目录并运行 cargo build --release 。为此问题已提交问题,我在寻找有Mac的人来帮忙。

视觉基准测试

现在在Faster Path中的基准测试可以产生性能改进的视觉图表。当你运行 rake bench 时,图表艺术将放置在 doc/graph/。以下是 chop_basename 方法的性能改进结果。

Visual Benchmark

用法

当前实现的方法

FasterPath Rust 实现 Ruby 2.3.1 实现 性能改进
FasterPath.绝对? 路径名#绝对? 93.9%
FasterPath.chop_basename 路径名#chop_basename 50.6%
FasterPath.相对? 路径名#相对? 93.2%
FasterPath.空白?
FasterPath.目录? 路径名#目录? 25.5%
FasterPath.add_trailing_separator 路径名#add_trailing_separator 46.8%
FasterPath.has_trailing_separator? 路径名#has_trailing_separator 61.2%

您可以选择直接使用方法,或者使用范围更改来重写标准库中的行为,或者甚至调用一个方法来对每个地方进行猴子补丁。

注意: Ruby STDLIB 中的 Pathname#chop_basename 有一个与空白字符串相关的bug,这是与FasterPath实现行为差异的唯一之处。

对于范围 精炼,您需要

require "faster_path/optional/refinements"
using FasterPath::RefinePathname

而对于猴子补丁的锤子,您可以这样做

require "faster_path/optional/monkeypatches"
FasterPath.sledgehammer_everything!

不稳定的选择性部分

可能存在回归的选项方法是不稳定的。 这些将 不会 默认包含在猴子补丁中。使用 FasterPath::RefineFile 精炼时要小心。要尝试它们,请使用环境标志 WITH_REGRESSION。这些方法是为了改进而存在的。

FasterPath 实现 Ruby 实现
FasterPath.dirname File.dirname
FasterPath.basename File.basename
FasterPath.extname File.extname

我观察到(以及一些其他人)的观察结果是,Rust对C代码的 File 的实现具有相似的结果,但性能似乎取决于CPU缓存,可能取决于64位/32位系统环境。

FasterPath 开发者:其中大部分需要重写,请在测试中使用 WITH_REGRESSION=true TEST_MONKEYPATCHES=true。您可以在TravisCI的“允许失败”下看到当前的结果。

开始开发

主要的针对方法大多列在上面的为什么部分。您可能会发现Pathname的Ruby源代码很有用,请参阅Ruby源C源测试,以及查看文档

方法将尽可能用Rust编写。即使只是用Rust方法像!absolute?(非绝对)来写一个不是Ruby的,也会降低已经获得的39%的性能。只要可能,就用Rust实现。

检出仓库后,请确保您已安装Rust,然后运行bundle。运行rake test来运行测试,以及运行rake bench进行基准测试。

维基百科上学习和分享性能提示!

构建和运行测试

首先,通过运行bundle捆绑宝石的开发依赖项。

然后,构建rust代码

rake build_src

FasterPath使用Ruby Spec套件进行测试,以确保它与本地实现兼容,并且还有自己的测试套件测试其猴子补丁和改进功能。

要一次性运行所有测试,只需运行rake。要运行所有Ruby spec测试,请运行mspec

要运行FasterPath自己的套件中的单个测试或基准

# An individual test file:
ruby -I lib:test test/benches/absolute_benchmark.rb
# All tests:
rake minitest

要运行单个Ruby spec测试,请使用相对于spec/ruby_spec的路径运行mspec,例如

# A path to a file or a directory:
mspec core/file/basename_spec.rb
# Tests most relevant to FasterPath:
mspec core/file library/pathname
# All tests:
mspec

贡献

在GitHub上欢迎提交错误报告和拉取请求:https://github.com/danielpclark/faster_path

许可

MIT许可证或APACHE 2.0,任选其一。

依赖