Issue #21838 has been updated by peterzhu2118 (Peter Zhu). Thank you for reporting this issue. I debugged it and have a fix here: https://github.com/ruby/ruby/pull/15882 I distilled the reproduction down to this script: <details> <summary>Script</summary> ```ruby require "bundler/inline" gemfile do source "https://rubygems.org" gem "ruby-vips" gem "benchmark" gem "image_processing" gem "shrine" gem "sqlite3" gem "rails" end require "rails" require "active_record" require "benchmark" require "sqlite3" require "shrine/storage/file_system" ActiveRecord::Base.establish_connection( adapter: "sqlite3", database: ":memory:" ) ActiveRecord::Schema.define do create_table "albums", force: :cascade do |t| t.integer "artist_id", null: false t.text "cover_data" t.datetime "created_at", null: false t.integer "duration", default: 0, null: false t.string "title" t.integer "tracks_count", default: 0, null: false t.datetime "updated_at", null: false t.index ["artist_id"], name: "index_albums_on_artist_id" t.index ["duration"], name: "index_albums_on_duration" t.index ["title", "artist_id"], name: "index_albums_on_title_and_artist_id", unique: true end create_table "artists", force: :cascade do |t| t.integer "albums_count", default: 0, null: false t.datetime "created_at", null: false t.text "description" t.string "name" t.datetime "updated_at", null: false t.string "website" t.string "wikipedia" t.index ["description"], name: "index_artists_on_description" t.index ["name"], name: "index_artists_on_name", unique: true end add_foreign_key "albums", "artists" end Shrine.storages = { cache: Shrine::Storage::FileSystem.new("/tmp", prefix: "uploads/cache"), store: Shrine::Storage::FileSystem.new("/tmp", prefix: "uploads/store"), } Shrine.plugin :activerecord Shrine.plugin :cached_attachment_data Shrine.plugin :restore_cached_data Shrine.plugin :derivatives Shrine.plugin :validation Shrine.plugin :validation_helpers Shrine.plugin :determine_mime_type Shrine.plugin :remote_url, max_size: 1*1024*1024 class CoverUploader < Shrine Attacher.derivatives do |original| vips = ImageProcessing::Vips.source(original) { small: vips.resize_to_limit!(300, 300), medium: vips.resize_to_limit!(500, 500) } end end class ApplicationRecord < ActiveRecord::Base primary_abstract_class end class Album < ApplicationRecord include CoverUploader::Attachment(:cover) # ASSOCIATIONS belongs_to :artist, counter_cache: true, touch: true # VALIDATIONS validates :title, presence: true, uniqueness: {scope: :artist_id} end class Artist < ApplicationRecord # ASSOCIATIONS has_many :albums, dependent: :destroy, strict_loading: true # VALIDATIONS validates :name, presence: true, uniqueness: {case_sensitive: false} # Only allow http:// and https:// URL schemes. validates :website, format: { with: URI::DEFAULT_PARSER.make_regexp(%w[http https]), allow_blank: true } end result = Benchmark.measure do artist_records = 500.times.map do |i| { name: "Artist #{i}", description: "Description for artist #{i}", wikipedia: "https://en.wikipedia.org/wiki/Artist_#{i}", website: "https://artist#{i}.com" } end Artist.insert_all(artist_records) album_records = 1000.times.map do |i| { title: "Album #{i}", artist_id: (i % 500) + 1, } end Album.insert_all(album_records) Shrine.storages[:cache].clear! Shrine.storages[:store].clear! image_benchmark = Benchmark.measure do Album.find_each do |album| cover_path = "/home/peter/Downloads/Cover.jpg" # Local cover upload. album.cover = File.open(cover_path) album.cover_derivatives! # Generate derivatives via libvips. album.save end end puts "\n=== Image Processing Only ===" puts "User CPU: #{image_benchmark.utime}s" puts "System CPU: #{image_benchmark.stime}s" puts "Real time: #{image_benchmark.real}s" puts "==============================\n" Shrine.storages[:cache].clear! end puts "\n=== Benchmark Results ===" puts "User CPU: #{result.utime}s" puts "System CPU: #{result.stime}s" puts "Real time: #{result.real}s" puts "========================\n" puts GC.stat pp GC.stat_heap ``` </details> Before: ``` {count: 2017, time: 918, marking_time: 559, sweeping_time: 359, heap_allocated_pages: 333, heap_empty_pages: 0, heap_allocatable_slots: 5546, heap_available_slots: 287962, heap_live_slots: 287105, heap_free_slots: 857, heap_final_slots: 0, heap_marked_slots: 269412, heap_eden_pages: 333, total_allocated_pages: 333, total_freed_pages: 0, total_allocated_objects: 3459213, total_freed_objects: 3172108, malloc_increase_bytes: 2259851, malloc_increase_bytes_limit: 16777216, minor_gc_count: 1987, major_gc_count: 30, compact_count: 0, read_barrier_faults: 0, total_moved_objects: 0, remembered_wb_unprotected_objects: 0, remembered_wb_unprotected_objects_limit: 2684, old_objects: 269126, old_objects_limit: 536956, oldmalloc_increase_bytes: 2259851, oldmalloc_increase_bytes_limit: 16777216} {0 => {slot_size: 40, heap_live_slots: 167011, heap_free_slots: 1639, heap_final_slots: 0, heap_eden_pages: 103, heap_eden_slots: 168650, total_allocated_pages: 119, force_major_gc_count: 3, force_incremental_marking_finish_count: 2, total_allocated_objects: 2412984, total_freed_objects: 2245973}, 1 => {slot_size: 80, heap_live_slots: 79897, heap_free_slots: 299, heap_final_slots: 0, heap_eden_pages: 98, heap_eden_slots: 80196, total_allocated_pages: 322, force_major_gc_count: 22, force_incremental_marking_finish_count: 0, total_allocated_objects: 589564, total_freed_objects: 509667}, 2 => {slot_size: 160, heap_live_slots: 33658, heap_free_slots: 273, heap_final_slots: 0, heap_eden_pages: 83, heap_eden_slots: 33931, total_allocated_pages: 189, force_major_gc_count: 3, force_incremental_marking_finish_count: 0, total_allocated_objects: 436213, total_freed_objects: 402555}, 3 => {slot_size: 320, heap_live_slots: 3306, heap_free_slots: 158, heap_final_slots: 0, heap_eden_pages: 17, heap_eden_slots: 3464, total_allocated_pages: 52, force_major_gc_count: 0, force_incremental_marking_finish_count: 0, total_allocated_objects: 15741, total_freed_objects: 12435}, 4 => {slot_size: 640, heap_live_slots: 3271, heap_free_slots: 88, heap_final_slots: 0, heap_eden_pages: 33, heap_eden_slots: 3359, total_allocated_pages: 94, force_major_gc_count: 1, force_incremental_marking_finish_count: 0, total_allocated_objects: 4749, total_freed_objects: 1478}} ``` After: ``` {count: 2017, time: 541, marking_time: 205, sweeping_time: 336, heap_allocated_pages: 336, heap_empty_pages: 0, heap_allocatable_slots: 18931, heap_available_slots: 290435, heap_live_slots: 287797, heap_free_slots: 2638, heap_final_slots: 0, heap_marked_slots: 270104, heap_eden_pages: 336, total_allocated_pages: 336, total_freed_pages: 0, total_allocated_objects: 3460006, total_freed_objects: 3172209, malloc_increase_bytes: 2259899, malloc_increase_bytes_limit: 16777216, minor_gc_count: 2009, major_gc_count: 8, compact_count: 0, read_barrier_faults: 0, total_moved_objects: 0, remembered_wb_unprotected_objects: 0, remembered_wb_unprotected_objects_limit: 2663, old_objects: 269818, old_objects_limit: 532620, oldmalloc_increase_bytes: 2259899, oldmalloc_increase_bytes_limit: 18971452} {0 => {slot_size: 40, heap_live_slots: 167111, heap_free_slots: 1545, heap_final_slots: 0, heap_eden_pages: 103, heap_eden_slots: 168656, total_allocated_pages: 126, force_major_gc_count: 3, force_incremental_marking_finish_count: 2, total_allocated_objects: 2413543, total_freed_objects: 2246432}, 1 => {slot_size: 80, heap_live_slots: 80426, heap_free_slots: 595, heap_final_slots: 0, heap_eden_pages: 99, heap_eden_slots: 81021, total_allocated_pages: 360, force_major_gc_count: 2, force_incremental_marking_finish_count: 0, total_allocated_objects: 590103, total_freed_objects: 509677}, 2 => {slot_size: 160, heap_live_slots: 33750, heap_free_slots: 183, heap_final_slots: 0, heap_eden_pages: 83, heap_eden_slots: 33933, total_allocated_pages: 200, force_major_gc_count: 0, force_incremental_marking_finish_count: 0, total_allocated_objects: 436028, total_freed_objects: 402278}, 3 => {slot_size: 320, heap_live_slots: 3254, heap_free_slots: 5, heap_final_slots: 0, heap_eden_pages: 16, heap_eden_slots: 3259, total_allocated_pages: 33, force_major_gc_count: 0, force_incremental_marking_finish_count: 0, total_allocated_objects: 15623, total_freed_objects: 12369}, 4 => {slot_size: 640, heap_live_slots: 3294, heap_free_slots: 272, heap_final_slots: 0, heap_eden_pages: 35, heap_eden_slots: 3566, total_allocated_pages: 47, force_major_gc_count: 0, force_incremental_marking_finish_count: 0, total_allocated_objects: 4747, total_freed_objects: 1453}} ``` Note that major GC count has reduced significantly, from 30 to 8. ---------------------------------------- Bug #21838: Rails seeing degradation (20% slowdown) related to Revision 079ef92b "Implement global allocatable slots and empty pages" (from Sep 5 2024) https://bugs.ruby-lang.org/issues/21838#change-116144 * Author: bluz71 (Dennis B) * Status: Open * ruby -v: ruby 3.4.0dev (2024-09-09T14:15:21Z v3_4_0_preview2~545 079ef92b5e) * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- Hello, I noticed a performance degradation with Ruby 3.4 when seeding my Rails application, which does image derivation via the Shrine Gem and `libvips` image library. I skipped Ruby 3.4 hoping that 4.0 would magically solve my performance issue. Alas not. My degradation when doing `rails db:reset` (aka database seeding) was about 20% as compared with Ruby 3.3. The last couple of days I looked at this issue more closely and with the help of `git bisect` I believe this commit: https://bugs.ruby-lang.org/projects/ruby-master/repository/git/revisions/079... is the cause of my regression. That commit implements global allocatable slots and empty pages. The revision before is fast when seeding my Rails application. This revision however slows down my Rails seeding by about 20%, from 30 seconds to 40 seconds consistently. I created a minimal Rails application as the reproducible test case: https://github.com/bluz71/rails_app_ruby_slowdown Steps to reproduce: 1. `git clone https://github.com/bluz71/rails_app_ruby_slowdown` 2. `cd rails_app_ruby_slowdown` 3. `bundle config set force_ruby_platform true` 4. `bundle install` 5. `bundle exec rails db:create` 6. `RUBY_YJIT_ENABLE=0 time bundle exec rails db:reset` Note, `libvips` needs to be installed at the system level, for example `sudo apt install libvips`, libvips is the image processing library used by Shrine which in turn is used in this Rails application when seeding. Use Ruby 3.3 and 3.4 to compare, or use Revision de7ac11a (https://bugs.ruby-lang.org/projects/ruby-master/repository/git/revisions/de7...) and Revision 079ef92b (https://bugs.ruby-lang.org/projects/ruby-master/repository/git/revisions/079...) to compare. I hope this is reproducible, if so, I also hope this performance loss can be minimised in future. Best regards. -- https://bugs.ruby-lang.org/