Skip to content
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

Beyond Ludicrous Speed #21057

merged 22 commits into from Jul 30, 2015
Commits on Jul 30, 2015
  1. Decrease string allocations on AR#respond_to?

    schneems committed Jul 24, 2015
    When a symbol is passed in, we call `to_s` on it which allocates a string. The two hardcoded symbols that are used internally are `:to_partial_path` and `:to_model`.
    This change buys us 71,136 bytes of memory and 1,777 fewer objects per request.
  2. Decrease string allocations in apply_inflections

    schneems committed Jul 25, 2015
    In `apply_inflections` a string is down cased and some whitespace stripped in the front (which allocate strings). This would normally be fine, however `uncountables` is a fairly small array (10 elements out of the box) and this method gets called a TON. Instead we can keep an array of valid regexes for each uncountable so we don't have to allocate new strings.
    This change buys us 325,106 bytes of memory and 3,251 fewer objects per request.
  3. Decrease string allocations in url_options

    schneems committed Jul 25, 2015
    The request.script_name is dup-d which allocates an extra string. It is most commonly an empty string "". We can save a ton of string allocations by checking first if the string is empty, if so we can use a frozen empty string instead of duplicating an empty string.
    This change buys us 35,714 bytes of memory and 893 fewer objects per request.
  4. Speed up journey extract_parameterized_parts

    schneems committed Jul 25, 2015
    Micro optimization: `reverse.drop_while` is slower than `reverse_each.drop_while`. This doesn't save any object allocations.
    Second, `keys_to_keep` is typically a very small array. The operation `parameterized_parts.keys - keys_to_keep` actually allocates two arrays. It is quicker (I benchmarked) to iterate over each and check inclusion in array manually.
    This change buys us 1774 fewer objects per request
  5. Speed up journey missing_keys

    schneems committed Jul 25, 2015
    Most routes have a `route.path.requirements[key]` of `/[-_.a-zA-Z0-9]+\/[-_.a-zA-Z0-9]+/` yet every time this method is called a new regex is generated on the fly with `/\A#{DEFAULT_INPUT}\Z/`. OBJECT ALLOCATIONS BLERG!
    This change uses a special module that implements `===` so it can be used in a case statement to pull out the default input. When this happens, we use a pre-generated regex.
    This change buys us 1,643,465 bytes of memory and 7,990 fewer objects per request.
  6. Decrease route_set allocations

    schneems committed Jul 25, 2015
    In handle_positional_args `Array#-=` is used which allocates a new array. Instead we can iterate through and delete elements, modifying the array in place.
    Also `Array#take` allocates a new array. We can build the same by iterating over the other element.
    This change buys us 106,470 bytes of memory and 2,663 fewer objects per request.
  7. Reduce hash allocations in route_set

    schneems committed Jul 25, 2015
    When generating a url with `url_for` the hash of arguments passed in, is dup-d and merged a TON. I wish I could clean this up better, and might be able to do it in the future. This change removes one dup, since it's literally right after we just dup-d the hash to pass into this constructor.
    This may be a breaking, change but the tests :shipit: we can revert if it causes problems
    This change buys us 205,933 bytes of memory and 887 fewer objects per request.
  8. Decrease string allocation in content_tag_string

    schneems committed Jul 25, 2015
    When an unknonwn key is passed to the hash in `PRE_CONTENT_STRINGS` it returns nil, when you call "#{nil}" it allocates a new empty string. We can get around this allocation by using a default value ` { "".freeze }`. We can avoid the `to_sym` call by pre-populating the hash with a symbol key in addition to a string key.
    We can freeze some strings when using Array#* to reduce allocations.
    Array#join can take frozen strings.
    This change buys us 86,600 bytes of memory and 1,857 fewer objects per request.
  9. Optimize hash key

    schneems committed Jul 25, 2015
    No idea why on earth this hash key isn't already optimized by MRI, but it isn't. 💩
    This change buys us 74,077 bytes of memory and 1,852 fewer objects per request.
  10. Cut string ActionView template allocations

    schneems committed Jul 25, 2015
    The instrument method creates new strings, the most common action to instrument is "!render_template` so we can detect when that action is occurring and use a frozen string instead.
    This change buys us 113,714 bytes of memory and 1,790 fewer objects per request.
  11. Cut string allocations in content_tag_string

    schneems committed Jul 25, 2015
    content_tag's first argument is will generate a string with an html tag so `:a` will generate: `<a></a>`. When this happens, the symbol is implicitly `to_s`-d so a new string is allocated. We can get around that by using a frozen string instead which
    This change buys us 74,236 bytes of memory and 1,855 fewer objects per request.
  12. Avoid calling to_s on nil in journey/formatter

    schneems committed Jul 25, 2015
    When `defaults[key]` in `generate` in the journey formatter is called, it often returns a `nil` when we call `to_s` on a nil, it allocates an empty string. We can skip this check when the default value is nil.
    This change buys us 35,431 bytes of memory and 887 fewer objects per request.
    Thanks to @matthewd for help with the readability
  13. Freeze a string in comparator

    schneems committed Jul 25, 2015
    Saves 888 string objects per request.
  14. Only allocate new string when needed

    schneems committed Jul 25, 2015
    Instead of calling `sub` on every link_to call for controller, we can detect when the string __needs__ to be allocated and only then create a new string (without the leading slash), otherwise, use the string that is given to us.
    Saves 888 string objects per request, 35,524 bytes.
  15. Avoid hash duplication by skipping mutation

    schneems committed Jul 25, 2015
    If we don't mutate the `recall` hash, then there's no reason to duplicate it. While this change doesn't get rid of that many objects, each hash object it gets rid of was massive.
    Saves 888 string objects per request, 206,013 bytes (thats 0.2 mb which is kinda a lot).
  16. Decrease allocations in transliterate

    schneems committed Jul 26, 2015
    We can save a few objects by freezing the `replacement` string. We save a few more by down-casing the string in memory instead of allocating a new one. We save far more objects by checking for the default separator `"-"`, and using pre-generated regular expressions.
    We will save 209,231 bytes and 1,322 objects.
  17. String#freeze optimizations

    schneems committed Jul 26, 2015
  18. Don't allocate array when not necessary

    schneems committed Jul 26, 2015
    In the `tag_options` method an array is used to build up elements, then `Array#*` (which is an alias for `Array#join` is called to turn the array into a string. Instead of allocating an array to build a string, we can build the string we want from the beginning.
    Saved: 121,743 bytes 893 objects
  19. zOMG 37 objects saved

    schneems committed Jul 26, 2015
  20. Remove array allocation

    schneems committed Jul 27, 2015
    THe only reason we were allocating an array is to get the "missing_keys" variable in scope of the error message generator. Guess what? Arrays kinda take up a lot of memory, so by replacing that with a nil, we save:
    35,303 bytes and 886 objects per request
  21. Remove (another) array allocation

    schneems committed Jul 27, 2015
    We don't always need an array when generating a url with the formatter. We can be lazy about allocating the `missing_keys` array. This saves us:
    35,606 bytes and 889 objects per request
  22. Use delete_if instead of each; delete(key)

    schneems committed Jul 30, 2015
    It is slightly faster:
    Calculating -------------------------------------
            each; delete    35.166k i/100ms
               delete_if    36.416k i/100ms
            each; delete    478.026k (± 8.5%) i/s -      2.391M
               delete_if    485.123k (± 7.9%) i/s -      2.440M
You can’t perform that action at this time.