camertron / turbo-sprockets-rails4

Speed up asset precompliation by compiling assets in parallel.
MIT License
19 stars 8 forks source link

A lot slower than regular sprockets? #7

Open dbackeus opened 5 years ago

dbackeus commented 5 years ago

We have a 12 year old rails app (though up to date with Rails 5.2.x) with quite a few assets to compile. On my dev machine a normal rake asset:precompile run takes ~80 seconds to complete (activity monitor not really showing any processes using a lot of CPU).

When installing an enabling turbo sprockets with 8 workers I completed in ~200 seconds (activity monitor showing 8 ruby processes at around 90% CPU each). After trying to reduce the workers to 2 the compilation took over 400 seconds (activity monitor showing 2 ruby processes at 100% CPU).

I'm using production like settings in my development.rb for the sake of environment parity:

config.assets.debug = false
config.assets.js_compressor = :uglifier
config.assets.compile = true

For JS compilation we are using the mini_racer gem which gives us the fastest js compilation times by far.

Before each run I run rake assets:clobber assets:clean tmp:clear to ensure I'm building everything from scratch.

Any idea what turbo sprockets might be doing differently here which would massively degrade the build performance?

camertron commented 5 years ago

Heh yeah this is why the README mentions that your mileage may vary.

The gem works by attempting to schedule asset compilation across all the workers, but there are a number of factors that could negatively affect build time.

  1. Make sure the number of workers you're asking turbo-sprockes to use is less than or equal to the number of cores available on your build machine.
  2. Turbo-sprockets works by naïvely assuming all assets can be compiled in isolation, but often Javascript and CSS assets depend on other assets. Turbo-sprockets doesn't know anything about your asset's dependency graph, so there is a distinct possibility that a shared asset depended upon by two or more assets could get compiled two or more times. This is mostly due to a limitation in Sprockets which prevents retrieving asset dependency information without compiling the asset first :(
  3. When I originally wrote the gem I don't think mini_racer was available (or at least I hadn't heard of it). A lot of the speed-ups we saw adding turbo-sprockets to our codebase were due to the fact that therubyracer is so dang slow. Replacing it with mini_racer resulted in compile times only a few seconds slower than when using turbo-sprockets. Using nodejs also resulted in some modest speed increases for us, but nothing has had the impact on performance that mini-racer has. Pretty amazing if you ask me.

My guess is your codebase has a) a lot of Javascript, and b) a bunch of those Javascript assets are dependent on one another.

In the course of my research, I discovered that by far the most expensive part of Javascript compilation is minification. If you're looking for faster asset build times, I would seriously consider setting config.assets.js_compressor to nil in your config, or swapping it out for a different one (maybe the YUI compressor or closure compiler?)

In any case, it sounds like turbo-sprockets in its current form isn't right for your codebase. If you're interested in trying to improve the code to better suit your use-case, I'm always willing to accept a pull request :)

dbackeus commented 5 years ago

Thanks for the feedback!

The dependency issue makes a lot of sense and I'm pretty sure that's the problem we're running into.

With regards to minification I wonder if it's possible to configure UglifyJS with the "compress = false" option (which only removes whitespace) via sprockets. According to https://slack.engineering/keep-webpack-fast-a-field-guide-for-better-build-performance-f56a5995e8f1 this allows keeping 95% of the size reduction while making the builds 3x faster.

camertron commented 5 years ago

Good idea :)