Move Time#xmlschema in core and optimize it#11510
Merged
byroot merged 1 commit intoruby:masterfrom Sep 5, 2024
Merged
Conversation
1990333 to
34041ff
Compare
34041ff to
7b19564
Compare
nobu
reviewed
Sep 4, 2024
d0d6579 to
b3976d6
Compare
b3976d6 to
6e56056
Compare
6e56056 to
3dd6b7e
Compare
[Feature #20707] Converting Time into RFC3339 / ISO8601 representation is an significant hotspot for applications that serialize data in JSON, XML or other formats. By moving it into core we can optimize it much further than what `strftime` will allow. ``` compare-ruby: ruby 3.4.0dev (2024-08-29T13:11:40Z master 6b08a50) +YJIT [arm64-darwin23] built-ruby: ruby 3.4.0dev (2024-08-30T13:17:32Z native-xmlschema 34041ff) +YJIT [arm64-darwin23] warming up...... | |compare-ruby|built-ruby| |:-----------------------|-----------:|---------:| |time.xmlschema | 1.087M| 5.190M| | | -| 4.78x| |utc_time.xmlschema | 1.464M| 6.848M| | | -| 4.68x| |time.xmlschema(6) | 859.960k| 4.646M| | | -| 5.40x| |utc_time.xmlschema(6) | 1.080M| 5.917M| | | -| 5.48x| |time.xmlschema(9) | 893.909k| 4.668M| | | -| 5.22x| |utc_time.xmlschema(9) | 1.056M| 5.707M| | | -| 5.40x| ```
3dd6b7e to
b68e0b6
Compare
PChambino
added a commit
to PChambino/jruby
that referenced
this pull request
Jun 3, 2025
Solves: jruby#8476 Related: ruby/ruby#11510 I was looking for something to learn (J)Ruby internals and this one looked like something within my ability, so here is my stab at it. Benchmarks: ~5-10x faster than JRuby 10.0.0.1 + ~1-2x faster than CRuby 3.4.3. The main difference with CRuby is that I make use of getNanos() for the fraction digits since `subsec` is derived from getNanos() in JRuby, so we can skip the subsec logic I believe. Similar to `inspect`. I tried a couple of iterations of a similar implementation to CRuby but it would always result in worse performance. I noticed some methods in ConvertBytes that have very similar number / digit to byte(s) logic, but I ended up making the logic a bit more focused on xmlschema needs (for performance), so I didn't re-use the existing ones. Let me know if there are any suggestions / ideas, or feel free to change it yourself directly.
byroot
added a commit
to byroot/rails
that referenced
this pull request
Aug 30, 2025
Ref: ruby/ruby#11510 In Ruby 3.4 I optimized `Time#xmlschema`, so that it's now much faster than the `strftime` `TimeWithZone` was using. ``` ruby 3.5.0dev (2025-08-27T10:41:07Z master 5257e1298c) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- opt-aj-serialize-index 1.177M i/100ms Calculating ------------------------------------- opt-aj-serialize-index 13.961M (± 0.5%) i/s (71.63 ns/i) - 70.594M in 5.056554s Comparison: previous-commit: 1993282.1 i/s opt-aj-serialize-index: 13961260.3 i/s - 7.00x faster ``` Bench: ```ruby require "bundler/inline" gemfile do gem "benchmark-ips" gem "rails", path: "." end require "benchmark/ips" require "active_job/railtie" class TestApp < Rails::Application config.load_defaults Rails::VERSION::STRING.to_f config.eager_load = false config.secret_key_base = "secret_key_base" config.active_job.queue_adapter = :test config.logger = Logger.new(nil) end Rails.application.initialize! utc_time = Time.zone.now BRANCH = `git rev-parse --abbrev-ref HEAD`.strip Benchmark.ips do |x| x.report(BRANCH) do utc_time.xmlschema end x.save!("/tmp/bench-twz") x.compare!(order: :baseline) end ``` Since serializing TimeWithZone is a significant part of the Active Job benchmark, it has an effect there too: ``` ruby 3.5.0dev (2025-08-27T10:41:07Z master 5257e1298c) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- opt-aj-serialize-index 20.870k i/100ms Calculating ------------------------------------- opt-aj-serialize-index 213.253k (± 1.9%) i/s (4.69 μs/i) - 1.085M in 5.090838s Comparison: previous-commit: 188008.3 i/s opt-aj-serialize-index: 213252.7 i/s - 1.13x faster ```
byroot
added a commit
to byroot/rails
that referenced
this pull request
Aug 30, 2025
Ref: ruby/ruby#11510 In Ruby 3.4 I optimized `Time#xmlschema`, so that it's now much faster than the `strftime` `TimeWithZone` was using. ``` ruby 3.5.0dev (2025-08-27T10:41:07Z master 5257e1298c) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- opt-aj-serialize-index 1.177M i/100ms Calculating ------------------------------------- opt-aj-serialize-index 13.961M (± 0.5%) i/s (71.63 ns/i) - 70.594M in 5.056554s Comparison: previous-commit: 1993282.1 i/s opt-aj-serialize-index: 13961260.3 i/s - 7.00x faster ``` Bench: ```ruby require "bundler/inline" gemfile do gem "benchmark-ips" gem "rails", path: "." end require "benchmark/ips" require "active_job/railtie" class TestApp < Rails::Application config.load_defaults Rails::VERSION::STRING.to_f config.eager_load = false config.secret_key_base = "secret_key_base" config.active_job.queue_adapter = :test config.logger = Logger.new(nil) end Rails.application.initialize! utc_time = Time.zone.now BRANCH = `git rev-parse --abbrev-ref HEAD`.strip Benchmark.ips do |x| x.report(BRANCH) do utc_time.xmlschema end x.save!("/tmp/bench-twz") x.compare!(order: :baseline) end ``` Since serializing TimeWithZone is a significant part of the Active Job benchmark, it has an effect there too: ``` ruby 3.5.0dev (2025-08-27T10:41:07Z master 5257e1298c) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- opt-aj-serialize-index 20.870k i/100ms Calculating ------------------------------------- opt-aj-serialize-index 213.253k (± 1.9%) i/s (4.69 μs/i) - 1.085M in 5.090838s Comparison: previous-commit: 188008.3 i/s opt-aj-serialize-index: 213252.7 i/s - 1.13x faster ```
byroot
added a commit
to byroot/rails
that referenced
this pull request
Aug 30, 2025
Ref: ruby/ruby#11510 In Ruby 3.4 I optimized `Time#xmlschema`, so that it's now much faster than the `strftime` `TimeWithZone` was using. ``` ruby 3.5.0dev (2025-08-27T10:41:07Z master 5257e1298c) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- opt-aj-serialize-index 1.177M i/100ms Calculating ------------------------------------- opt-aj-serialize-index 13.961M (± 0.5%) i/s (71.63 ns/i) - 70.594M in 5.056554s Comparison: previous-commit: 1993282.1 i/s opt-aj-serialize-index: 13961260.3 i/s - 7.00x faster ``` Bench: ```ruby require "bundler/inline" gemfile do gem "benchmark-ips" gem "rails", path: "." end require "benchmark/ips" require "active_job/railtie" class TestApp < Rails::Application config.load_defaults Rails::VERSION::STRING.to_f config.eager_load = false config.secret_key_base = "secret_key_base" config.active_job.queue_adapter = :test config.logger = Logger.new(nil) end Rails.application.initialize! utc_time = Time.zone.now BRANCH = `git rev-parse --abbrev-ref HEAD`.strip Benchmark.ips do |x| x.report(BRANCH) do utc_time.xmlschema end x.save!("/tmp/bench-twz") x.compare!(order: :baseline) end ``` Since serializing TimeWithZone is a significant part of the Active Job benchmark, it has an effect there too: ``` ruby 3.5.0dev (2025-08-27T10:41:07Z master 5257e1298c) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- opt-aj-serialize-index 20.870k i/100ms Calculating ------------------------------------- opt-aj-serialize-index 213.253k (± 1.9%) i/s (4.69 μs/i) - 1.085M in 5.090838s Comparison: previous-commit: 188008.3 i/s opt-aj-serialize-index: 213252.7 i/s - 1.13x faster ```
byroot
added a commit
to byroot/rails
that referenced
this pull request
Aug 30, 2025
Ref: ruby/ruby#11510 In Ruby 3.4 I optimized `Time#xmlschema`, so that it's now much faster than the `strftime` `TimeWithZone` was using. ``` ruby 3.5.0dev (2025-08-27T10:41:07Z master 5257e1298c) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- opt-aj-serialize-index 1.177M i/100ms Calculating ------------------------------------- opt-aj-serialize-index 13.961M (± 0.5%) i/s (71.63 ns/i) - 70.594M in 5.056554s Comparison: previous-commit: 1993282.1 i/s opt-aj-serialize-index: 13961260.3 i/s - 7.00x faster ``` Bench: ```ruby require "bundler/inline" gemfile do gem "benchmark-ips" gem "rails", path: "." end require "benchmark/ips" require "active_job/railtie" class TestApp < Rails::Application config.load_defaults Rails::VERSION::STRING.to_f config.eager_load = false config.secret_key_base = "secret_key_base" config.active_job.queue_adapter = :test config.logger = Logger.new(nil) end Rails.application.initialize! utc_time = Time.zone.now BRANCH = `git rev-parse --abbrev-ref HEAD`.strip Benchmark.ips do |x| x.report(BRANCH) do utc_time.xmlschema end x.save!("/tmp/bench-twz") x.compare!(order: :baseline) end ``` Since serializing TimeWithZone is a significant part of the Active Job benchmark, it has an effect there too: ``` ruby 3.5.0dev (2025-08-27T10:41:07Z master 5257e1298c) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- opt-aj-serialize-index 20.870k i/100ms Calculating ------------------------------------- opt-aj-serialize-index 213.253k (± 1.9%) i/s (4.69 μs/i) - 1.085M in 5.090838s Comparison: previous-commit: 188008.3 i/s opt-aj-serialize-index: 213252.7 i/s - 1.13x faster ```
byroot
added a commit
to byroot/rails
that referenced
this pull request
Aug 30, 2025
Ref: ruby/ruby#11510 In Ruby 3.4 I optimized `Time#xmlschema`, so that it's now much faster than the `strftime` `TimeWithZone` was using. ``` ruby 3.5.0dev (2025-08-27T10:41:07Z master 5257e1298c) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- opt-aj-serialize-index 1.177M i/100ms Calculating ------------------------------------- opt-aj-serialize-index 13.961M (± 0.5%) i/s (71.63 ns/i) - 70.594M in 5.056554s Comparison: previous-commit: 1993282.1 i/s opt-aj-serialize-index: 13961260.3 i/s - 7.00x faster ``` Bench: ```ruby require "bundler/inline" gemfile do gem "benchmark-ips" gem "rails", path: "." end require "benchmark/ips" require "active_job/railtie" class TestApp < Rails::Application config.load_defaults Rails::VERSION::STRING.to_f config.eager_load = false config.secret_key_base = "secret_key_base" config.active_job.queue_adapter = :test config.logger = Logger.new(nil) end Rails.application.initialize! utc_time = Time.zone.now BRANCH = `git rev-parse --abbrev-ref HEAD`.strip Benchmark.ips do |x| x.report(BRANCH) do utc_time.xmlschema end x.save!("/tmp/bench-twz") x.compare!(order: :baseline) end ``` Since serializing TimeWithZone is a significant part of the Active Job benchmark, it has an effect there too: ``` ruby 3.5.0dev (2025-08-27T10:41:07Z master 5257e1298c) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- opt-aj-serialize-index 20.870k i/100ms Calculating ------------------------------------- opt-aj-serialize-index 213.253k (± 1.9%) i/s (4.69 μs/i) - 1.085M in 5.090838s Comparison: previous-commit: 188008.3 i/s opt-aj-serialize-index: 213252.7 i/s - 1.13x faster ```
byroot
added a commit
to byroot/rails
that referenced
this pull request
Aug 30, 2025
Ref: ruby/ruby#11510 In Ruby 3.4 I optimized `Time#xmlschema`, so that it's now much faster than the `strftime` `TimeWithZone` was using. ``` ruby 3.5.0dev (2025-08-27T10:41:07Z master 5257e1298c) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- opt-aj-serialize-index 1.177M i/100ms Calculating ------------------------------------- opt-aj-serialize-index 13.961M (± 0.5%) i/s (71.63 ns/i) - 70.594M in 5.056554s Comparison: previous-commit: 1993282.1 i/s opt-aj-serialize-index: 13961260.3 i/s - 7.00x faster ``` Bench: ```ruby require "bundler/inline" gemfile do gem "benchmark-ips" gem "rails", path: "." end require "benchmark/ips" require "active_job/railtie" class TestApp < Rails::Application config.load_defaults Rails::VERSION::STRING.to_f config.eager_load = false config.secret_key_base = "secret_key_base" config.active_job.queue_adapter = :test config.logger = Logger.new(nil) end Rails.application.initialize! utc_time = Time.zone.now BRANCH = `git rev-parse --abbrev-ref HEAD`.strip Benchmark.ips do |x| x.report(BRANCH) do utc_time.xmlschema end x.save!("/tmp/bench-twz") x.compare!(order: :baseline) end ``` Since serializing TimeWithZone is a significant part of the Active Job benchmark, it has an effect there too: ``` ruby 3.5.0dev (2025-08-27T10:41:07Z master 5257e1298c) +YJIT +PRISM [arm64-darwin24] Warming up -------------------------------------- opt-aj-serialize-index 20.870k i/100ms Calculating ------------------------------------- opt-aj-serialize-index 213.253k (± 1.9%) i/s (4.69 μs/i) - 1.085M in 5.090838s Comparison: previous-commit: 188008.3 i/s opt-aj-serialize-index: 213252.7 i/s - 1.13x faster ```
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
[Feature #20707]
Converting Time into RFC3339 / ISO8601 representation is an significant
hotspot for applications that serialize data in JSON, XML or other formats.
By moving it into core we can optimize it much further than what
strftimewillallow.