Created
May 15, 2026 09:38
-
-
Save tagliala/170ca1c8f1be747b33fe3371b2155bed to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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