Skip to content
This repository was archived by the owner on Jun 10, 2018. It is now read-only.
This repository was archived by the owner on Jun 10, 2018. It is now read-only.

Pathname usage causes significant performance degredation #506

@cheald

Description

@cheald

Ruby's Pathname module is notoriously slow, and Sprockets makes pretty heavy use of it, which imposes a pretty hefty performance penalty when doing asset resolution and compilation.

I've been tinkering with a Pathname monkeypatch in an attempt to improve Sprockets' performance. I realize that this is more of a stdlib concern than it is a Sprockets concern, but Sprockets could realize some immediate performance gains by reducing or eliminating Pathname usage.

I'm testing this via Guard with body = Rails.application.assets.find_asset(file).body - nothing all that complex.

I'm comparing this against a more direct Sass invocation, via:

context = ::Rails.application.assets.context_class.new(::Rails.application.assets.context_class, file, file)
options = @config.merge(custom: { resolver: ::Sass::Rails::Resolver.new(context) })
body = ::Sass::Engine.for_file(file, options).render

Pathname usage on the Sprockets codepath (unpatched):

image

Pathname usage on the Sprockets codepath (patched):

image

And Pathname usage on the more direct codepath:

image

The patch I'm working with is pretty straightforward - I'm just patching #join to not do the whole set of Pathname gymnatics. It still passes the Ruby pathname test suite, too! (Though it does take a shortcut by dropping Windows support; that's easily fixed by checking for File::ALT_SEPARATOR as well)

require 'pathname'
class Pathname
  def relative?
    @path[0] != File::SEPARATOR
  end

  def join(*args)
    last = args.last
    if last.to_s[0] == File::SEPARATOR
      if last.is_a? Pathname
        last
      else
        Pathname.new last
      end
    else
      Pathname.new(File.join @path, *args.map(&:to_s))
    end
  end
end

And some quick numbers:

# Standard sprockets
20:07:12 - INFO - [1/1] (2.09 sec)      wufoo/default.css.sass -> wufoo/default.css
20:07:16 - INFO - [1/1] (2.04 sec)      wufoo/default.css.sass -> wufoo/default.css
20:07:20 - INFO - [1/1] (2.06 sec)      wufoo/default.css.sass -> wufoo/default.css
20:15:13 - INFO - [5/5] (6.55 sec)                     app.css.sass -> app.css
20:15:38 - INFO - [5/5] (5.64 sec)                     app.css.sass -> app.css
20:15:57 - INFO - [5/5] (6.00 sec)                     app.css.sass -> app.css

# With Pathname patches
20:06:26 - INFO - [1/1] (1.53 sec)      wufoo/default.css.sass -> wufoo/default.css
20:06:30 - INFO - [1/1] (1.50 sec)      wufoo/default.css.sass -> wufoo/default.css
20:06:33 - INFO - [1/1] (1.53 sec)      wufoo/default.css.sass -> wufoo/default.css
20:17:25 - INFO - [5/5] (5.03 sec)                     app.css.sass -> app.css
20:17:37 - INFO - [5/5] (5.03 sec)                     app.css.sass -> app.css
20:17:58 - INFO - [5/5] (5.12 sec)                     app.css.sass -> app.css

It should be pretty obvious that leaning on Pathname so hard is causing some pretty measurable performance degradation. It may be worth moving to File methods or even just custom high-performance helpers if at all possible.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions