Skip to content

Performance Regression in 3.4.1 #1537

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
dougal opened this issue Jan 26, 2016 · 11 comments · Fixed by #1544
Closed

Performance Regression in 3.4.1 #1537

dougal opened this issue Jan 26, 2016 · 11 comments · Fixed by #1544

Comments

@dougal
Copy link

dougal commented Jan 26, 2016

While running specs on a large application I maintain, I noticed a significant slowdown in running controller specs where the action would ordinarily render a template, when using 3.4.1 as compared to 3.4.0.

I have provided an example application which demonstrates this.

Benchmarking

The results I am seeing (using the timing in RSpec's own output) are:

3.4.0

Run 1: 2.51s
Run 2: 2.55s
Run 3: 2.54s

Average: 2.53s

3.4.1

Run 1: 6.99s
Run 2: 7.04s
Run 3: 7.06s

Average: 7.03s

Conjecture

I suspect this is related to the changes in PR #1535, specifically the commit 42b44a6. Having a quick look at the commit, there are at least two extra map operations in there compared to previously. I am not familiar with the code, and have not yet had the time to run a profiler with the specs in my example application to see if the extra iteration or resulting allocations are indeed the cause. I hope to do so tomorrow.

@fables-tales
Copy link
Member

Subscribe.

@fables-tales
Copy link
Member

@dougal any chance you could work up a PR to fix this?

@dougal
Copy link
Author

dougal commented Jan 27, 2016

After a short while with stackprof there didn't seem to be much extra CPU time being consumed. Something must be consuming time. IO?

3.4.1

Using DTrace I was able to see that under 3.4.1, the view code was searching for templates from the disk, many times:

$ cat dtrace-3.4.1.txt | egrep "getattrlist.*app/views/" | wc -l
     260

(The calls are for different variants of possible template names, index.html.erb, index.erb, index.builder, etc. This seems to be ActionView::PathResolver#find_template_paths manifests itself at the system level.)

It then opens the template:

$ cat dtrace-3.4.1.txt | egrep "open.*app/views/"
55684/0x13915a6:  open("/Users/dougal/Desktop/rspec_performance_regression_demo_minimal/app/views/badgers/index.html.erb\0", 0x1000000, 0x1B6)     = 9 0

And reads it:

$ cat dtrace-3.4.1.txt | egrep "read.*<%= @badger"
55684/0x13915a6:  read(0x9, "<h1><%= @badger_name %></h1>\n\0", 0x1D)    = 29 0

3.4.0

None of this happens under 3.4.0. Here are the same commands:

$ cat dtrace-3.4.0.txt | egrep "getattrlist.*app/views/" | wc -l
       0

$ cat dtrace-3.4.1.txt | egrep "open.*app/views/"
55684/0x13915a6:  open("/Users/dougal/Desktop/rspec_performance_regression_demo_minimal/app/views/badgers/index.html.erb\0", 0x1000000, 0x1B6)     = 9 0

$ cat dtrace-3.4.0.txt | egrep "read.*<%= @badger"
# <no-output>

I have posted the full DTrace output for both versions as a Gist.

Cause?

3.4.0 subclassed ActionView::Resolver, with 3.4.1 subclassing ActionView::FileSystemResolver (Which subclasses ActionView::PathResolver). All of the syscalls above happen within PathResolver.

I am now looking into subclassing ActionView::Resolver directly, while at the same time making sure #1532 is not an issue again.

@fables-tales
Copy link
Member

@pixeltrix or @sgrif can you advise here?

@sgrif
Copy link

sgrif commented Jan 28, 2016

I'm currently out of the country, but I will be happy to take a look on Friday.

@pixeltrix
Copy link
Contributor

@samphippen looks like the ActionView::OptimizedFileSystemResolver is about half of that:

# rspec-rails-3.4.0
$ rspec spec/controllers/badgers_controller_spec.rb
........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

Finished in 2.1 seconds (files took 2.9 seconds to load)
1000 examples, 0 failures

# rspec-rails-3.4.1
$ rspec spec/controllers/badgers_controller_spec.rb
........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

Finished in 6.1 seconds (files took 2.86 seconds to load)
1000 examples, 0 failures

# rspec-rails-3.4.1+ ActionView::OptimizedFileSystemResolver
$ rspec spec/controllers/badgers_controller_spec.rb
........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

Finished in 4.16 seconds (files took 2.85 seconds to load)
1000 examples, 0 failures

The implementation for ActionView::OptimizedFileSystemResolver is simple enough to just copy over and not worry about breaking the :nodoc: contract. I'll continue to investigate where the other two seconds went.

@fables-tales
Copy link
Member

@pixeltrix what if you #map! here 42b44a6#diff-275cf7192a569bb2bb4a7ee5419faee4R50 and use a single instance of 42b44a6#diff-275cf7192a569bb2bb4a7ee5419faee4R51 stored in a constant, looked up each time, instead of newing one up?

@pixeltrix
Copy link
Contributor

@samphippen I don't think that ActionView::Template instance is the same though - template.identifier, template.virtual_path and template.formats will be different.

@pixeltrix
Copy link
Contributor

@samphippen also just checked that block of code is executed the same number of times in 3.4.0 and 3.4.1

@pixeltrix
Copy link
Contributor

@samphippen it's because in 3.4.1 when we're creating the new resolvers in the view paths it's blowing away the cache, whereas it wasn't in 3.4.0 because it wrapped the existing resolvers by decorating the view paths.

I think I have a way to fix it without reaching inside the internals of :nodoc: classes.

pixeltrix added a commit to pixeltrix/rspec-rails that referenced this issue Jan 31, 2016
Closes rspec#1537.

The change in 80637fc introduced a performance regression because the
template lookup cache was being lost every time an example was run.

This commit fixes the problem by caching the EmptyTemplateResolver
instances between example runs in a hash indexed by the path string.
pixeltrix added a commit to pixeltrix/rspec-rails that referenced this issue Jan 31, 2016
Closes rspec#1537.

The change in 80637fc introduced a performance regression because the
template lookup cache was being lost every time an example was run.

This commit fixes the problem by caching the EmptyTemplateResolver
instances between example runs in a hash indexed by the path string.
@sgrif
Copy link

sgrif commented Feb 1, 2016

Just got a chance to look at this. It looks like you guys have it covered. I just wanted to mention the irony of the fact that "Optimized"FileSystemResolver is the source of the regression. Very optimized... :trollface:

Ok I'll go away now.

sebjacobs pushed a commit to futurelearn/rspec-rails that referenced this issue Mar 15, 2019
Closes rspec#1537.

The change in 80637fc introduced a performance regression because the
template lookup cache was being lost every time an example was run.

This commit fixes the problem by caching the EmptyTemplateResolver
instances between example runs in a hash indexed by the path string.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants