Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rails 7.1をn倍速くした話

Rails 7.1をn倍速くした話

鹿児島Ruby会議02 の講演「Rails 7.1をn倍速くした話」のスライド https://k-ruby.com/kagoshima-rubykaigi02/ #k_ruby

Akira Matsuda

April 27, 2023
Tweet

More Decks by Akira Matsuda

Other Decks in Programming

Transcript

  1. Rails
    7
    .
    1
    n
    @a_matsuda

    View full-size slide

  2. self
    🌋
    Tw: @a_matsuda


    🌋
    GH: amatsuda


    🌋
    Rails


    🌋
    Asakusa.rb


    🌋
    RubyKaigi

    View full-size slide

  3. MAGMA City!?

    View full-size slide

  4. Ruby on Rails

    View full-size slide

  5. Framework Benchmarks

    View full-size slide

  6. jeremyevans/r
    1
    0
    k

    View full-size slide

  7. jeremyevans/r
    1
    0
    k
    $ rake bench graphs R10K_APPS="rails roda"
    /Users/a_matsuda/.rbenv/versions/3.3.0-dev/bin/ruby builder.rb '' rails 1 10
    /Users/a_matsuda/.rbenv/versions/3.3.0-dev/bin/ruby builder.rb '' rails 2 10
    /Users/a_matsuda/.rbenv/versions/3.3.0-dev/bin/ruby builder.rb '' rails 3 10
    /Users/a_matsuda/.rbenv/versions/3.3.0-dev/bin/ruby builder.rb '' rails 4 10
    /Users/a_matsuda/.rbenv/versions/3.3.0-dev/bin/ruby builder.rb '' roda 1 10
    /Users/a_matsuda/.rbenv/versions/3.3.0-dev/bin/ruby builder.rb '' roda 2 10
    /Users/a_matsuda/.rbenv/versions/3.3.0-dev/bin/ruby builder.rb '' roda 3 10
    /Users/a_matsuda/.rbenv/versions/3.3.0-dev/bin/ruby builder.rb '' roda 4 10
    running apps/rails_1_10.rb, pass 1, 11181 requests/second
    running apps/rails_1_10.rb, pass 2, 11274 requests/second
    running apps/rails_1_10.rb, pass 3, 10911 requests/second
    running apps/rails_2_10.rb, pass 1, 9691 requests/second
    running apps/rails_2_10.rb, pass 2, 10756 requests/second
    running apps/rails_2_10.rb, pass 3, 10630 requests/second
    running apps/rails_3_10.rb, pass 1, 10071 requests/second
    running apps/rails_3_10.rb, pass 2, 10211 requests/second
    running apps/rails_3_10.rb, pass 3, 10261 requests/second
    running apps/rails_4_10.rb, pass 1, 9377 requests/second
    running apps/rails_4_10.rb, pass 2, 9342 requests/second
    running apps/rails_4_10.rb, pass 3, 9440 requests/second
    running apps/roda_1_10.rb, pass 1, 431760 requests/second
    running apps/roda_1_10.rb, pass 2, 428678 requests/second
    running apps/roda_1_10.rb, pass 3, 433585 requests/second
    running apps/roda_2_10.rb, pass 1, 240096 requests/second
    running apps/roda_2_10.rb, pass 2, 236359 requests/second
    running apps/roda_2_10.rb, pass 3, 239348 requests/second
    running apps/roda_3_10.rb, pass 1, 178426 requests/second
    running apps/roda_3_10.rb, pass 2, 184530 requests/second
    running apps/roda_3_10.rb, pass 3, 185453 requests/second
    running apps/roda_4_10.rb, pass 1, 121037 requests/second
    running apps/roda_4_10.rb, pass 2, 144010 requests/second
    running apps/roda_4_10.rb, pass 3, 141795 requests/second

    View full-size slide

  8. No
    🌋


    🌋



    🌋

    View full-size slide

  9. (1)
    🌋
    Nate Asakusa.rb

    View full-size slide

  10. Nate Asakusa.rb
    🌋
    2022 5


    🌋
    Puma Nate Asakusa meetup


    🌋


    🌋


    🌋
    Rails


    🌋
    upstream

    View full-size slide

  11. 🌋
    2022 11


    🌋
    🦴


    🌋


    🌋
    push


    🌋
    Ruby

    View full-size slide

  12. 🌋
    nobu


    🌋
    OSS

    🙏

    View full-size slide

  13. 🌋


    🌋


    🌋

    View full-size slide

  14. 🌋
    Object Allocations

    View full-size slide

  15. Object Allocations
    🌋


    🌋
    GC

    View full-size slide

  16. Object Allocations
    🌋
    Object.new


    🌋
    '' [] {}

    View full-size slide

  17. [199ec16d-cd73-4d9f-9941-a6754b86fcc3] Completed 200 OK in 2ms (Allocations: 249)

    View full-size slide

  18. 🌋
    Zero Allocation


    🌋
    ( )

    View full-size slide

  19. How Do We Measure?
    🌋
    Rails : 7.0.4.2 (current newest
    stable)


    🌋
    production env


    🌋
    ObjectSpace GC.stat

    View full-size slide

  20. module Rails
    def self.mem
    # ͳΜ͔ ObjectSpace.count_objects ͕ࢥͬͨͱ͓ΓͷڍಈͰ͸ͳ͔ͬͨͷͰྗٕͰ
    before = GC.stat(:total_allocated_objects)
    result = yield
    after = GC.stat(:total_allocated_objects)
    puts "total_allocated: #{after - before - 1}"
    result
    ennd

    View full-size slide

  21. 🌋
    $ rails r 'Rails.mem {
    1
    0
    0
    .times {
    a = [] } }'


    🌋
    total_allocated:
    1
    0
    0

    View full-size slide

  22. Rails
    🌋
    web
    Rack

    View full-size slide

  23. Rails::Engine
    Rails::Engine.prepend(
    Module.new {
    def call(*)
    Rails.mem do
    super
    end
    end
    }
    )

    View full-size slide

  24. GC.disable
    Rails.application.config.to_prepare do
    GC.start
    GC.disable
    end

    View full-size slide

  25. scaffold
    show
    $ rails g scaffold post title
    $ rails db:migrate
    $ rails r 'Post.create! title: "Post1"'

    View full-size slide

  26. 🌋
    total_allocated:
    1
    7
    5
    7

    View full-size slide

  27. 🌋
    SamSaffron/memory_profiler


    🌋
    ko
    1
    /allocation_tracer

    View full-size slide

  28. SamSaffron/memory_profiler

    View full-size slide


  29. module Rails
    def self.memory_profiler
    result = nil
    MemoryProfiler.report { result = yield }.pretty_print(allocated_strings: 100, normalize_paths: true)
    result
    ennd

    View full-size slide


  30. 🌋
    allocated objects by location


    🌋
    allocated objects by class


    🌋
    Allocated String Report


    🌋

    View full-size slide


  31. 🌋
    view render
    render


    🌋
    Action View template

    View full-size slide

  32. show
    def show
    render html: 'Hello'
    end

    View full-size slide

  33. 🌋
    Total allocated:
    6
    5
    1
    3
    5
    bytes
    (
    7
    8
    2
    objects)

    View full-size slide

  34. set_post
    🌋
    @post


    🌋
    Active Record

    View full-size slide

  35. ✂ show
    - before_action :set_post, only: %i[ show edit update destroy ]
    + before_action :set_post, only: %i[ edit update destroy ]

    View full-size slide

  36. 🌋
    Total allocated:
    5
    9
    0
    4
    7
    bytes
    (
    7
    0
    1
    objects)

    View full-size slide

  37. view
    render
    head :ok

    View full-size slide

  38. 🌋
    5
    3
    3
    0
    2
    bytes (
    6
    3
    4
    objects)

    View full-size slide

  39. Rails

    🌋
    Rack


    🌋
    Rack Rails


    🌋
    action

    View full-size slide

  40. Rack
    🌋
    Rails::Engine#call

    View full-size slide

  41. Rack Rails
    module Rails
    class Prof
    def initialize(app) = @app = app
    def call(env) = Rails.memory_profiler { @app.call(env) }
    end
    application.config.middleware.use Prof
    end

    View full-size slide

  42. action
    class ApplicationController < ActionController::Base
    def send_action(*)
    p request.path
    Rails.memory_profiler do
    super
    ennnd

    View full-size slide

  43. 🌋
    2
    0
    7
    2
    bytes (
    3
    4
    objects)

    View full-size slide

  44. 🌋
    loop do


    🌋


    🌋
    &


    🌋


    🌋
    end

    View full-size slide

  45. Array

    # actionview/lib/action_view/lookup_context.rb
    - if values == [:js]
    + if (values.length == 1) && (values[0] == :js)

    View full-size slide

  46. Range
    - path[(i + 2)..-1]
    + path[(i + 2), path.length]

    View full-size slide

  47. Request
    # actionpack/lib/action_dispatch/middleware/show_exceptions.rb
    def call(env)
    - request = ActionDispatch::Request.new env
    @app.call(env)
    rescue Exception => exception
    + request = ActionDispatch::Request.new env
    if request.show_exceptions?
    render_exception(request, exception)
    else

    View full-size slide

  48. String#%

    Array
    # railties/lib/rails/rack/logger.rb
    def started_request_message(request) # :doc:
    - 'Started %s "%s" for %s at %s' % [
    + sprintf('Started %s "%s" for %s at %s',
    request.raw_request_method,
    request.filtered_path,
    request.remote_ip,
    - Time.now.to_default_s ]
    + Time.now.to_default_s)

    View full-size slide

  49. Rack middleware env

    Array
    # actionpack/lib/action_dispatch/journey/router.rb
    req.path_parameters = tmp_params
    - status, headers, body = route.app.serve(req)
    + _, headers, _ = response = route.app.serve(req)
    if "pass" == headers["X-Cascade"]
    req.script_name = script_name
    @@ -56,7 +56,7 @@ def serve(req)
    next
    end
    - return [status, headers, body]
    + return response
    end

    View full-size slide

  50. 🌋
    Rails 1


    🌋

    View full-size slide

  51. 🌋
    CPU


    🌋
    5

    View full-size slide

  52. Rails
    🌋


    🌋
    Active Support
    Action View

    View full-size slide

  53. 7.0 edge Rails
    (to be
    7
    .
    1
    ) push
    31ddd41e58
    a81be79d4b
    7a63a7a668
    13783f36ad
    4c23742a13
    8b2f57dc6f
    bd11e520a2
    19a6979cfc
    e0936d99a3
    ae569eaef8
    3d00c8b97f
    3ade331e75
    ca0d6521b1
    0671acfeea
    c9875d31cc
    d247f491a4
    3ba079526b
    5653d7d56d
    aa73d1ad52
    076003d8e6
    ffdbf17191
    bfb0a6c211
    a1c4aa87b3
    d1461cdb61
    6287c109d3
    341b30be2e
    f517eefe14
    afa47b93a1
    c49b8270e1
    f6dbed5de4
    2b33690bba
    b378293c0d
    946bc94e82
    4ba87aae1f
    2ead5132ea
    bcfdfefc08
    d15acd7ba6
    ee94a5c34b
    a18d90e7cd
    dce7c1cd7c
    663e8e1ccd
    9e39c8a721
    7d0a788167
    4f61d46348
    c5af4f4505
    8317fffb85
    e28e2f784e
    9f141a423d
    4732c91d14
    a790203408
    15ab7223c7
    055f71cece
    66386d3b4c
    41c2c26dc6
    974de71036
    45dd422901
    8b617e224b
    01001028df
    4fbc4bbe43
    6576eec6a8
    3baffd31be
    b9beb3eee1
    56333f3c69
    c989a2908e
    26f51f36fa
    905c720c2c
    bc3251f1be
    78599ba1e8
    59728911e4
    b368c68c6a
    4e7620b110
    9a8d2de95b
    351e726be5
    cac0e04313
    b9fe288d6d
    a289f4c127
    c73eafa634
    60ffaac2e9
    d8c05043c3
    bdd090abf6
    c0f16c16a3
    9c66072b97
    41b3e61735

    View full-size slide


  54. PR

    🌋
    real world app

    View full-size slide

  55. 🌋
    0.1% 50
    5%

    View full-size slide

  56. main push
    🌋


    🌋
    Rails

    View full-size slide

  57. main push

    4 CI

    🌋
    ( )


    🌋

    View full-size slide

  58. rack logger ipaddr


    View full-size slide


  59. 🌋
    Final Fight

    View full-size slide

  60. edge Rails

    🌋
    1
    6
    7
    2
    bytes (
    2
    5
    objects)

    View full-size slide



  61. 🌋
    memory_profiler

    🤔

    View full-size slide

  62. Rails

    class C
    def initialize = @x = nil
    delegate :hash, to: :@x
    def hash2() = @x.hash
    end
    c = C.new
    p hash_via_delegate: c.hash
    Rails.memory_profiler { c.hash }
    #=> 1
    p hash_via_method_call: c.hash2
    Rails.memory_profiler { c.hash2 }
    #=> 0

    View full-size slide

  63. 🌋
    delegate
    *args, **kwargs, &block


    🌋
    *args Array **kwargs Hash


    🌋
    ... Array Hash


    🌋
    delegate

    View full-size slide


  64. 🌋
    https://bugs.ruby-lang.org/
    issues/
    1
    9
    1
    6
    5

    View full-size slide


  65. delegate

    🌋
    1
    5
    5
    2
    bytes (
    2
    2
    objects)

    View full-size slide

  66. allocated objects by location
    5 actionpack/lib/action_dispatch/http/response.rb:429
    4 actionpack/lib/action_dispatch/http/response.rb:428
    3 rack-3.0.4.2/lib/rack/headers.rb:151
    3 actionpack/lib/action_dispatch/http/response.rb:443
    2 rack-3.0.4.2/lib/rack/headers.rb:31
    1 actionpack/lib/action_controller/metal.rb:224
    1 actionpack/lib/action_dispatch/http/mime_type.rb:149
    1 actionpack/lib/action_dispatch/http/response.rb:438
    1 actionpack/lib/action_dispatch/http/response.rb:468
    1 kagoshima02/app/controllers/application_controller.rb:6

    View full-size slide

  67. 🌋
    CONTENT_TYPE Match


    🌋
    ContentTypeHeader


    🌋
    Rack request header key String


    🌋
    charset downcase String


    🌋
    CONTENT_TYPE value String


    🌋
    Rack body Array


    🌋
    MimeType Symbol lookup to_s String
    ( Symbol GC )


    🌋
    body Buffer


    🌋
    send_action splat Array

    View full-size slide

  68. 🌋
    CONTENT_TYPE Match


    🌋
    ContentTypeHeader


    🌋
    charset downcase String


    🌋
    CONTENT_TYPE value String


    🌋
    Rack body Array


    🌋
    body Buffer


    🌋
    send_action splat Array

    View full-size slide

  69. allocated objects by location
    -----------------------------------
    5 actionpack/lib/action_dispatch/http/response.rb:429
    4 actionpack/lib/action_dispatch/http/response.rb:428
    3 actionpack/lib/action_dispatch/http/response.rb:443
    1 actionpack/lib/action_controller/metal.rb:224
    1 actionpack/lib/action_dispatch/http/response.rb:438
    1 actionpack/lib/action_dispatch/http/response.rb:468
    1 kagoshima02/app/controllers/application_controller.rb:6

    View full-size slide

  70. head :ok Final Result
    🌋
    1
    3
    1
    2
    bytes (
    1
    6
    objects)

    View full-size slide

  71. Rack middleware
    🌋
    2
    9
    9
    3
    8
    bytes (
    3
    7
    7
    objects)

    View full-size slide

  72. 🌋
    Rack::Static


    🌋
    Rack::Static

    View full-size slide

  73. config.public_file_server.enab
    led = false
    🌋
    2
    4
    1
    4
    6
    bytes (
    2
    9
    6
    objects)

    View full-size slide

  74. 🌋
    ParameterFilter


    🌋
    URL


    🌋
    IP


    🌋
    Monitor


    🌋


    🌋

    View full-size slide

  75. ParameterFilter

    View full-size slide

  76. 🌋
    2
    2
    0
    5
    4
    bytes (
    2
    6
    8
    objects)

    View full-size slide

  77. benchmark-ips

    View full-size slide

  78. Rails benchmark-
    ips 5
    Rails::Engine.prepend(
    Module.new {
    def call(*)
    result = super
    Benchmark.ips do |x|
    x.report { super }
    end
    result
    end
    }
    )

    View full-size slide

  79. head :ok IPS
    🌋
    7
    .
    0
    :
    1
    .
    9
    4
    8
    k (±
    4
    .
    1
    %) i/s -
    9
    .
    9
    0
    0
    k in
    5
    .
    0
    9
    1
    7
    1
    0
    s


    🌋
    7
    .
    1
    :
    6
    .
    5
    0
    6
    k (±
    9
    .
    0
    %) i/s -
    3
    2
    .
    5
    7
    1
    k in
    5
    .
    0
    4
    7
    1
    5
    5
    s

    View full-size slide

  80. render plain: 'Hello' IPS
    🌋
    7
    .
    0
    :
    1
    .
    8
    4
    1
    k (±
    3
    .
    2
    %) i/s -
    9
    .
    2
    0
    4
    k in
    5
    .
    0
    0
    3
    8
    4
    9
    s


    🌋
    7
    .
    1
    :
    5
    .
    7
    6
    1
    k (±
    8
    .
    2
    %) i/s -
    2
    8
    .
    9
    4
    4
    k in
    5
    .
    0
    5
    9
    8
    1
    7
    s

    View full-size slide

  81. "Rails
    7
    .
    1
    n "

    View full-size slide

  82. Action Controller

    View full-size slide

  83. Action View &

    Active Record

    View full-size slide

  84. To Be Continued...

    View full-size slide