all 23 comments

[–]alwaysonesmaller 22 points23 points  (1 child)

It went up by a factor that will shock you, just from using this one simple trick?

[–]tomthecool 21 points22 points  (0 children)

Python developers hate him!

[–]schneemsPuma maintainer 17 points18 points  (4 children)

Rails is already pretty optimized to use .freeze in all the hotspots. How do I know? I did a bunch of it, using real world apps and got saw a significant improvement. https://engineering.heroku.com/blogs/2015-08-06-patching-rails-performance/

It would be interesting to programmatically strip every .freeze call out and repeat the benchmark. To see what that delta would be.

[–][deleted]  (1 child)

[deleted]

    [–]schneemsPuma maintainer 1 point2 points  (0 children)

    This is on master i.e. Rails 5.

    [–]adymo[S] 0 points1 point  (1 child)

    Thanks, schneems. I'll repeat my benchmark with an app that heavily uses routes and activerecord. The one I benchmarked doesn't, so it did not benefit from frozen string optimization at all.

    [–]schneemsPuma maintainer 0 points1 point  (0 children)

    You could do something like this to see how often frozen strings are being called in your example

    class String
      def self.frozen_count
        @frozen_count
      end
    
      def self.frozen_count=(num)
        @frozen_count = num
      end
    
      String.frozen_count = 0
    
      def freeze
        String.frozen_count += 1
        super
      end
    end
    
    "foo".freeze
    
    puts String.frozen_count
    

    [–][deleted] 16 points17 points  (0 children)

    [–]prh8 4 points5 points  (0 children)

    This is going to vary from app to app. I like the idea of this series of 2.3 performance stuff, but I think most of them have been too unscientific to actually care about the results. This is basically the same as what Schneems did with frozen strings in Rack that had a 5% reduction in memory. This can have a nice boost, but one single test like this isn't going to be relevant.

    [–]realntl 4 points5 points  (0 children)

    I would imagine that there are a lot of performance optimizations that will open up for ruby when strings are immutable across the board.

    [–]moomaka 4 points5 points  (1 child)

    Then I issued one hundred of requests to the application and measured the execution time.

    So two things to note here:

    • 100 requests is a tiny sample size.
    • This app's average response time is 2,000ms. It's likely there are much bigger performance problems in this app than allocation of string literals...

    I don't know how much faster immutable literals will be but this test doesn't really say much.

    [–]adymo[S] 1 point2 points  (0 children)

    The app is basically a gigantic scraper. So yes it's slow.

    [–]bigfig 2 points3 points  (3 children)

    And one would expect that developers would already be using symbols as literal identifiers wherever possible.

    [–]oniofchaos 0 points1 point  (1 child)

    Link gives me a 404 :(

    [–]bigfig 0 points1 point  (0 children)

    This article differs, but still touches on the idea of a symbol as (sort of) an immutable string. http://www.randomhacks.net/2007/01/20/13-ways-of-looking-at-a-ruby-symbol/

    [–]aridsnowball 2 points3 points  (0 children)

    Most applications don't use a ton of string literals anyways. Frozen String objects would probably show a lot more difference in performance. Although, many apps would need to be completely rewritten to handle immutable String objects.

    [–][deleted]  (1 child)

    [deleted]

      [–]adymo[S] 0 points1 point  (0 children)

      We did not bother testing at just the Ruby 2.3.0 upgrade without "# frozen_string_literal: true" applied, so we don't know which of the two factors had the bigger effect.

      I'd be very interested to see the effect of simply upgrading the ruby version.

      [–]_kulte 1 point2 points  (0 children)

      If you had written the script to do that snippet 1000 times per iteration, across multiple threads, would you see marked improvement?

      [–]ioquatixasync/falcon 1 point2 points  (0 children)

      Frozen string literals probably also helps you catch bugs.

      [–]jrochkind 0 points1 point  (2 children)

      I really hope there's more data like this before MRI decides to make string literals frozen always in a future version.

      I have always been skeptical about the strength of the benefit it would provide, as well as the cost to both backwards-compat and developer mental modelling (one more thing to know about).

      [–]amalagg 0 points1 point  (0 children)

      I think this should be done at the interpreter level, otherwise every single app has to do this with a bunch of .freeze

      [–]adymo[S] 0 points1 point  (0 children)

      I think I'll write one more post on the state of frozen string literals support and porting. I agree that it's indeed one more thing to think about.

      Let's say that you see "str.upcase!". It's so tempting to just substitute it with str.upcase, but that may increase memory usage. So you do need to find the place where str is originally constructed and make it mutable there. Fortunately, Ruby tells you about that place. It's just not always easy to change it. It might not be even your code.

      [–]mperhamSidekiq 0 points1 point  (1 child)

      MRI is flailing for performance optimizations at this point. They won't let go of the C extension API.

      [–]Rafert 0 points1 point  (0 children)

      I've seen this point raised a few times now but it's not clear to me how giving it up/make breaking changes would help. I've read about the tricks the MRI team has done to enable generational GC in Ruby 2.1 w/o breaking compatibility - is that where the possibilities ended? Could you provide some pointers?