simple_form: What is the expected slowdown of using SimpleForm?

I was investigating some slow view rendering in our application.

We had a partial that was rendered many times, which looked like this. I had perhaps stupidly opted out of most Simple Form goodness, but I guess for consistency’s sake, thought this would be ok.

<div class='sortableItem variant' data-item-id='<%= f.object.id %>'>
  <div class="variant-creator-row">
    <div class="variant-details">
      <%= f.input :text, label: false, wrapper: false, as: :string %>
    </div>
    <div class="variant-option-value">
      <%= f.input :option_1_value, label: false, wrapper: false %>
    </div>
    <div class="variant-option-value">
      <%= f.input :option_2_value, label: false, wrapper: false %>
    </div>
    <div class="variant-option-value">
      <%= f.input :option_3_value, label: false, wrapper: false %>
    </div>

    <div class="variant-hidden">
      <%= f.input :hidden, label: false,  wrapper: false %>
    </div>

    <div class="variant-delete survey-answer-delete">
      <a href="#" class="text text-danger">delete</a>
      <div class="hidden">
        <%= f.input_field :_destroy, as: :boolean, label: false, wrapper: false %>
      </div>
    </div>
  </div>
</div>

<%= f.hidden_field :field_kind, value: 'multiple_choice' %>

Worth noting, this f value is coming from a “simple_fields_for” call.

This partial was getting rendered many times, and looking at my logs, each one was taking > 200 milliseconds! eek! https://gist.github.com/maxwell/12cd7e949cae4ea60766

Changing this to just be normal Rails helpers radically reduced render time, to about 5 milliseconds per partial, 40x faster!

https://gist.github.com/maxwell/f7b6c1bc6625f6b5c3e7

I realize that it is reasonable to know that there would be some overhead, and perhaps one should not use SimpleForms for simple forms which are rendered many times, but is this the kind of overhead we should expect? Am I doing something incredibly stupid to cause this behavior?

I am currently using simple_form (3.1.0.rc1), and Rails 4.1.10

I love using SimpleForm, thanks so much for all the hard work you guys put into it!

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Comments: 40 (11 by maintainers)

Commits related to this issue

Most upvoted comments

3.2.0 released with the performance fix. Thanks everyone!

@josevalim I will corroborate the conclusions of others on this thread that i18n MissingTranslations is the culprit.

I think the binding_of_caller “slowdown” is a magnification of an underlying issues.

Running a reduced version of my view through ruby-prof, the number of calls to I18n::Backend::Base#translate goes from 98 to 6281 when I add in a simple_fields_for call which renders a partial. The partial in question only has ~200 inputs which should result in O(6000x) more calls to translate. There is some sort of non-linear behavior going on in simple_fields_for. I think it’s related to interactions with I18n::MissingTranslation in SimpleForms. If you add the following monkey patch to config/initializers somewhere:

module ActionView
  module Helpers
    module TranslationHelper
      def translate(key, options={})
        I18n.translate(key)
      end
      alias :t :translate
    end
  end
end

The raises of I18n::MissingTranslation disappear from the profile and performance returns (I’m dropping from 4s/page load down to 700ms).

I think the reason binding_of_caller is causing a drastic slowdown is because ActionView::Helpers::TranslationHelper#translate raises for a unknown translation key. For each raise (of which we now have thousands), better_errors calls BetterErrors::ExceptionExtension#set_backtrace which invokes BindingOfCaller::BindingExtensions#callers. This, at minimum, done some array allocation/deallocation in addition to a stack walk.

Just to be completely thorough, I wanted to verify that MRI2’s stack walk code wasn’t somehow causing a huge slowdown (it shouldn’t since it’s some very straightforward pointer math). To do this, I downloaded binding_of_caller and stubbed out BindingOfCaller::BindingExtensions#callers to just return an empty array. Things did not get appreciably faster in this case.

Hope this helps!