Skip to content

Instantly share code, notes, and snippets.

@tagliala
Created May 15, 2026 09:38
Show Gist options
  • Select an option

  • Save tagliala/170ca1c8f1be747b33fe3371b2155bed to your computer and use it in GitHub Desktop.

Select an option

Save tagliala/170ca1c8f1be747b33fe3371b2155bed to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'bundler/inline'
require 'fileutils'
begin
gemfile(true) do
source 'https://rubygems.org'
gem 'benchmark-ips', '~> 2.8.0'
gem 'benchmark-memory', '~> 0.1.2'
gem 'memory_profiler'
gem 'i18n'
end
require 'benchmark/ips'
require 'benchmark/memory'
require 'memory_profiler'
require 'i18n'
require 'i18n/backend/pluralization'
rescue Gem::LoadError => error
warn "\nMissing Dependency:\n#{error.backtrace.first} #{error.message}"
exit 1
rescue LoadError => error
warn "\nError:\n#{error.backtrace.first} #{error.message}"
warn DATA.read
exit 1
end
require_relative 'lib/rails_i18n/pluralization'
module ArabicPluralizationBenchmark
module_function
TRANSLATION_KEYS = %w[
benchmark.x_seconds
benchmark.x_minutes
benchmark.about_x_hours
].freeze
COUNTS = [0, 1, 2, 3, 7, 10, 11, 42, 99, 100, 101, 102, 103, 111].freeze
EXPECTED_CATEGORIES = {
0 => :zero,
1 => :one,
2 => :two,
3 => :few,
10 => :few,
11 => :many,
42 => :many,
99 => :many,
100 => :other,
101 => :other,
102 => :other,
103 => :few,
111 => :many
}.freeze
WARMUP_SECONDS = ENV.fetch('WARMUP_SECONDS', '1').to_i
MEASURE_SECONDS = ENV.fetch('MEASURE_SECONDS', '5').to_i
RULE_INNER_REPS = ENV.fetch('RULE_INNER_REPS', '50').delete('_').to_i
PROFILE_SWEEPS = ENV.fetch('PROFILE_SWEEPS', '25').delete('_').to_i
PROFILE_DIR = ENV.fetch('PROFILE_DIR', 'tmp').freeze
TRANSLATIONS = {
i18n: {
plural: {
keys: %i[zero one two few many other],
rule: ::RailsI18n::Pluralization::Arabic.rule
}
},
benchmark: {
x_seconds: {
zero: 'صفر ثواني',
one: 'ثانية واحدة',
two: 'ثانيتان',
few: '%{count} ثوان',
many: '%{count} ثانية',
other: '%{count} ثانية'
},
x_minutes: {
zero: 'صفر دقائق',
one: 'دقيقة واحدة',
two: 'دقيقتان',
few: '%{count} دقائق',
many: '%{count} دقيقة',
other: '%{count} دقيقة'
},
about_x_hours: {
zero: 'حوالي صفر ساعات',
one: 'حوالي ساعة واحدة',
two: 'حوالي ساعتان',
few: 'حوالي %{count} ساعات',
many: 'حوالي %{count} ساعة',
other: 'حوالي %{count} ساعة'
}
}
}.freeze
def run
boot_i18n!
verify_rule!
puts "\nRuby version: #{RUBY_VERSION}"
puts 'Arabic pluralization benchmark'
puts 'This script loads the rule from lib/rails_i18n/pluralization.rb.'
puts "Counts: #{COUNTS.join(', ')}"
puts "Translation keys: #{TRANSLATION_KEYS.join(', ')}"
puts "Warmup: #{WARMUP_SECONDS}s | Measure: #{MEASURE_SECONDS}s | Rule inner reps: #{RULE_INNER_REPS} | Profile sweeps: #{PROFILE_SWEEPS}"
puts
print_samples
run_ips_benchmark
run_memory_benchmark
run_profiler
end
def boot_i18n!
unless I18n::Backend::Simple.ancestors.include?(I18n::Backend::Pluralization)
I18n::Backend::Simple.include(I18n::Backend::Pluralization)
end
I18n.enforce_available_locales = false
I18n.available_locales = [:ar]
I18n.backend = I18n::Backend::Simple.new
I18n.backend.store_translations(:ar, TRANSLATIONS)
end
def verify_rule!
mismatches = EXPECTED_CATEGORIES.filter_map do |count, expected|
actual = rule.call(count)
next if actual == expected
"#{count.inspect}: expected #{expected}, got #{actual}"
end
return if mismatches.empty?
raise "Arabic rule sanity check failed:\n#{mismatches.join("\n")}"
end
def print_samples
puts 'Sample categories and x_seconds translations:'
COUNTS.each do |count|
category = rule.call(count)
translation = I18n.t('benchmark.x_seconds', locale: :ar, count: count)
puts format(' %5s -> %-5s %s', count, category, translation)
end
puts
end
def run_ips_benchmark
puts 'IPS'
Benchmark.ips do |benchmark|
benchmark.warmup = WARMUP_SECONDS
benchmark.time = MEASURE_SECONDS
benchmark.report(rule_label) { rule_sweep }
benchmark.report(translation_label) { translation_sweep }
benchmark.compare!
end
puts
end
def run_memory_benchmark
puts 'Memory'
Benchmark.memory do |benchmark|
benchmark.report(rule_label) { rule_sweep }
benchmark.report(translation_label) { translation_sweep }
benchmark.compare!
end
puts
end
def run_profiler
puts 'Profiler'
FileUtils.mkdir_p(PROFILE_DIR)
profile_workloads.each do |label, path, workload|
report = MemoryProfiler.report do
PROFILE_SWEEPS.times { workload.call }
end
report.pretty_print(to_file: path)
puts format(
' %-12s alloc=%8d (%10s) retained=%8d (%10s) report=%s',
label,
report.total_allocated,
format_bytes(report.total_allocated_memsize),
report.total_retained,
format_bytes(report.total_retained_memsize),
path
)
end
puts
end
def profile_workloads
[
['rule_only', File.join(PROFILE_DIR, 'arabic_rule_only.memory_profiler.txt'), method(:rule_sweep)],
['translations', File.join(PROFILE_DIR, 'arabic_translations.memory_profiler.txt'), method(:translation_sweep)]
]
end
def rule_sweep
checksum = 0
RULE_INNER_REPS.times do
COUNTS.each do |count|
checksum ^= rule.call(count).hash
end
end
checksum
end
def translation_sweep
checksum = 0
TRANSLATION_KEYS.each do |key|
COUNTS.each do |count|
checksum += I18n.t(key, locale: :ar, count: count).bytesize
end
end
checksum
end
def rule_label
"rule_only x#{COUNTS.size * RULE_INNER_REPS}"
end
def translation_label
"translations x#{COUNTS.size * TRANSLATION_KEYS.size}"
end
def rule
@rule ||= ::RailsI18n::Pluralization::Arabic.rule
end
def format_bytes(bytes)
units = %w[B KiB MiB GiB]
value = bytes.to_f
unit = units.first
units[1..].each do |next_unit|
break if value < 1024
value /= 1024
unit = next_unit
end
format('%.2f %s', value, unit)
end
end
ArabicPluralizationBenchmark.run if $PROGRAM_NAME == __FILE__
__END__
Run this script with plain ruby so bundler/inline can manage its own gems:
ruby benchmark_arabic_pluralization.rb
Useful environment overrides:
WARMUP_SECONDS=1 MEASURE_SECONDS=5 RULE_INNER_REPS=50 PROFILE_SWEEPS=25 ruby benchmark_arabic_pluralization.rb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment