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 Slide

  2. self
    🌋
    Tw: @a_matsuda


    🌋
    GH: amatsuda


    🌋
    Rails


    🌋
    Asakusa.rb


    🌋
    RubyKaigi

    View Slide

  3. MAGMA City!?

    View Slide

  4. Ruby on Rails

    View Slide

  5. View Slide

  6. Framework Benchmarks

    View Slide

  7. jeremyevans/r
    1
    0
    k

    View Slide

  8. 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 Slide

  9. (Roda )

    View Slide

  10. 🌋
    Roda 40

    View Slide

  11. Rails

    View Slide

  12. View Slide

  13. DB
    🌋
    Rails

    View Slide

  14. View Slide

  15. Rails

    View Slide

  16. Really?

    View Slide

  17. Rails

    View Slide

  18. View Slide

  19. View Slide

  20. Rails

    View Slide

  21. View Slide

  22. Rails

    View Slide

  23. 🌋

    View Slide

  24. No
    🌋


    🌋



    🌋

    View Slide

  25. 🌋
    🤔

    View Slide

  26. View Slide

  27. View Slide

  28. (1)
    🌋
    Nate Asakusa.rb

    View Slide

  29. Nate Asakusa.rb
    🌋
    2022 5


    🌋
    Puma Nate Asakusa meetup


    🌋


    🌋


    🌋
    Rails


    🌋
    upstream

    View Slide

  30. (2)
    🌋

    View Slide

  31. 🌋
    2022 11


    🌋
    🦴


    🌋


    🌋
    push


    🌋
    Ruby

    View Slide

  32. (3)
    🌋

    View Slide

  33. 🌋
    nobu


    🌋
    OSS

    🙏

    View Slide

  34. View Slide

  35. 🌋


    🌋


    🌋

    View Slide

  36. 🌋

    View Slide

  37. View Slide

  38. 🌋
    Object Allocations

    View Slide

  39. Object Allocations
    🌋


    🌋
    GC

    View Slide

  40. Object Allocations
    🌋
    Object.new


    🌋
    '' [] {}

    View Slide

  41. 🌋
    Rails

    View Slide

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

    View Slide

  43. 🌋
    Zero Allocation


    🌋
    ( )

    View Slide

  44. View Slide

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


    🌋
    production env


    🌋
    ObjectSpace GC.stat

    View Slide

  46. 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 Slide

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


    🌋
    total_allocated:
    1
    0
    0

    View Slide

  48. View Slide

  49. Rails
    🌋
    web
    Rack

    View Slide

  50. View Slide

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

    View Slide

  52. 🌋
    GC

    View Slide

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

    View Slide

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

    View Slide

  55. 🌋
    total_allocated:
    1
    7
    5
    7

    View Slide

  56. View Slide


  57. 🌋

    View Slide

  58. 🌋
    SamSaffron/memory_profiler


    🌋
    ko
    1
    /allocation_tracer

    View Slide

  59. SamSaffron/memory_profiler

    View Slide


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

    View Slide


  61. 🌋
    allocated objects by location


    🌋
    allocated objects by class


    🌋
    Allocated String Report


    🌋

    View Slide

  62. show
    🌋

    View Slide

  63. View Slide


  64. 🌋

    View Slide

  65. 🌋



    🌋

    View Slide


  66. 🌋
    view render
    render


    🌋
    Action View template

    View Slide

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

    View Slide

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

    View Slide

  69. set_post
    🌋
    @post


    🌋
    Active Record

    View Slide

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

    View Slide

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

    View Slide

  72. view
    render
    head :ok

    View Slide

  73. View Slide

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

    View Slide

  75. View Slide

  76. Rails

    🌋
    Rack


    🌋
    Rack Rails


    🌋
    action

    View Slide

  77. Rack
    🌋
    Rails::Engine#call

    View Slide

  78. 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 Slide

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

    View Slide

  80. 🌋

    View Slide

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

    View Slide

  82. View Slide

  83. View Slide

  84. 🌋
    loop do


    🌋


    🌋
    &


    🌋


    🌋
    end

    View Slide

  85. View Slide

  86. Array

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

    View Slide

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

    View Slide

  88. 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 Slide

  89. 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 Slide

  90. 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 Slide

  91. View Slide


  92. 🌋

    View Slide

  93. 🌋
    Rails 1


    🌋

    View Slide

  94. 🌋


    🌋

    View Slide

  95. 🌋
    CPU


    🌋
    5

    View Slide

  96. Rails
    🌋


    🌋
    Active Support
    Action View

    View Slide

  97. 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 Slide

  98. 1 80

    🌋

    View Slide


  99. PR

    🌋
    real world app

    View Slide

  100. 🌋
    0.1% 50
    5%

    View Slide

  101. main push
    🌋


    🌋
    Rails

    View Slide

  102. main push

    4 CI

    🌋
    ( )


    🌋

    View Slide

  103. rack logger ipaddr


    View Slide


  104. 🌋
    Final Fight

    View Slide

  105. edge Rails

    🌋
    1
    6
    7
    2
    bytes (
    2
    5
    objects)

    View Slide

  106. Rails

    View Slide



  107. 🌋
    memory_profiler

    🤔

    View Slide


  108. 🌋

    View Slide

  109. 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 Slide

  110. delegate

    View Slide

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


    🌋
    *args Array **kwargs Hash


    🌋
    ... Array Hash


    🌋
    delegate

    View Slide


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

    View Slide


  113. delegate

    🌋
    1
    5
    5
    2
    bytes (
    2
    2
    objects)

    View Slide

  114. 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 Slide

  115. 🌋
    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 Slide

  116. push ⾒

    View Slide

  117. 🌋
    CONTENT_TYPE Match


    🌋
    ContentTypeHeader


    🌋
    charset downcase String


    🌋
    CONTENT_TYPE value String


    🌋
    Rack body Array


    🌋
    body Buffer


    🌋
    send_action splat Array

    View Slide

  118. 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 Slide

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

    View Slide

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

    View Slide

  121. 🌋
    Rack::Static


    🌋
    Rack::Static

    View Slide

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

    View Slide

  123. 🌋
    ParameterFilter


    🌋
    URL


    🌋
    IP


    🌋
    Monitor


    🌋


    🌋

    View Slide

  124. ParameterFilter

    View Slide

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

    View Slide

  126. View Slide

  127. View Slide

  128. View Slide

  129. View Slide

  130. benchmark-ips

    View Slide

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

    View Slide

  132. 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 Slide

  133. 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 Slide

  134. View Slide

  135. "Rails
    7
    .
    1
    n "

    View Slide


  136. n =
    3
    4

    View Slide

  137. Action Controller

    View Slide

  138. Action View &

    Active Record

    View Slide

  139. To Be Continued...

    View Slide

  140. end

    View Slide