Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Scalable Apps with JRuby

headius
September 06, 2019

Scalable Apps with JRuby

headius

September 06, 2019
Tweet

More Decks by headius

Other Decks in Programming

Transcript

  1. สวัสดี! • Charles Oliver Nutter • [email protected], @headius • JVM

    language advocate at Red Hat • 24 years of JVM, 13 years of JRuby • My first time in Thailand!
  2. What is JRuby • It's just Ruby! • Ruby 2.5

    compatible, if something's broken tell us • Supports pure-Ruby gems, standard library, many extensions • We want to be a Ruby first! • It's a JVM language • Full access to the power of the JVM platform!
  3. JVM Benefits • Widely deployed and widely supported runtime •

    Excellent JIT, GC, concurrency, and platform support • Wide array of libraries • Rich tools for monitoring, profiling, debugging • WORA: JRuby feels the same on Windows as on Linux
  4. Thousands of Libraries Released artifacts: 7,000,212 Unique artifacts: 347,981 Released

    gem files: 1,042,770 Unique artifacts: 154,626 Maven Central RubyGems.org
  5. Roadmap • 9.2.8.0 (Ruby 2.5 compatible) is current version •

    9.1.x is EOL • Support 2.6 or 2.7? 9.1.17.0 ... 9.2.0.0 2.5.3+ 2.3.x 2.6? 9.2.7.0 master jruby-9_1 9.1.1.18.0 EOL ruby-2.6
  6. Skip 2.6 Support? • JRuby 9.3 -> Ruby 2.7? •

    How important is 2.6? • We skipped 2.4 to no ill effects • Less maintenance for us • 2.6 Checklist: https://github.com/jruby/jruby/issues/5576
  7. You Can Help! • New features are great opportunities to

    contribute! • Learn more about how Ruby and JRuby work • Help us keep up with Ruby development • Use Ruby or Java, we'll accept both • We are always standing by on IRC, Gitter, Twitter to help you
  8. JRuby Install • Install a JDK: https://adoptopenjdk.net/ • Java 8

    recommended, there's many distributions out there • Java 9+ work well but may print warnings • Install JRuby • Recommended: Ruby installer, Docker image, system package • Download tarball/zip or use Windows installer
  9. Gems, Paths, etc • Don't share gem path with other

    Rubies • C extension gems have JRuby versions (e.g. Nokogiri) • Ruby installers will handle this for you • System packages may not • Watch out for .ruby-version silently switching
  10. Getting Help • JRuby on GitHub: https://github.com/jruby/jruby • Chat with

    JRuby devs, users • #jruby on Freenode IRC • jruby/jruby on Gitter • jruby on Matrix • Mailing list: https://lists.ruby-lang.org
  11. JRuby Architecture Ruby (.rb) JIT Java Instructions (java bytecode) Ruby

    Instructions (IR) parse interpret interpreter interpret C1 compile native code better native code java bytecode interpreter execute C2 compile Java Virtual Machine JRuby Internals deoptimize Performance improves the longer your app runs
  12. Microbenchmarks - Usually not useful to users • Not related

    to typical applications (e.g. Rails) • JRuby has won microbenchmarks for years + Fun to show off and improve + Easier to isolate specific measurements + Quick way to explore new runtimes and tech
  13. bench_mandelbrot • Generate a Mandelbrot fractal image • Useful? Hmmm

    • Good test of numeric algorithm performance • Heavily relies on JVM to optimize
  14. bench_mandelbrot.rb def mandelbrot(size) sum = 0 byte_acc = 0 bit_num

    = 0 y = 0 while y < size ci = (2.0*y/size)-1.0 x = 0 while x < size zrzr = zr = 0.0 zizi = zi = 0.0 cr = (2.0*x/size)-1.5 escape = 0b1 z = 0 while z < 50
  15. bench_mandelbrot total execution time (lower is better) 0s 1s 2s

    3s 4s CRuby 2.5 CRuby 2.6 JIT JRuby JRuby Indy 1.33s 2.95s 3.5s 3.57s
  16. InvokeDynamic • JVM support for dynamic calls, variables, etc •

    Java 7 feature after input and testing from JRuby • Steadily improving performance, reducing overhead • Opt-in due to startup impact: -Xcompile.invokedynamic • May be default very soon!
  17. bench_mandelbrot total execution time (lower is better) 0s 1s 2s

    3s 4s CRuby 2.6 JIT JRuby JRuby Indy 1.33s 2.95s 3.5s
  18. New JVMs and JITs • IBM's OpenJ9 • Recently open-sourced

    • Many compelling features • Graal: JIT written in Java • Faster evolution • More advanced optimization
  19. bench_mandelbrot total execution time (lower is better) 0s 1s 2s

    3s 4s CRuby 2.6 JIT JRuby JRuby Indy 1.33s 2.95s 3.5s
  20. bench_mandelbrot total execution time (lower is better) 0s 1s 2s

    3s 4s CRuby 2.6 JIT JRuby Indy JRuby Indy Graal 0.139s 1.33s 3.5s
  21. Optimizing Objects and Arrays • Ruby instance vars are dynamic,

    but usually predictable • Ruby arrays are mutable, but usually small and immutable • We can make compact JVM objects • Instance vars, array elements as Java fields • Reduce memory use and allocation • Similar to CRuby storing references in object header
  22. Objects in Rails `select` Bench percent live alloc'ed class rank

    self accum bytes objs bytes objs name 23 0.82% 73.58% 1744576 18168 5894464 61396 org.jruby.gen.RubyObject17 32 0.44% 78.33% 937784 23432 2071464 51774 org.jruby.gen.RubyObject2 42 0.30% 81.96% 633312 19775 1525824 47666 org.jruby.gen.RubyObject0 43 0.30% 82.26% 632168 11280 2783968 49705 org.jruby.gen.RubyObject6 46 0.27% 83.10% 587072 18330 2133984 66671 org.jruby.gen.RubyObject1 58 0.22% 86.08% 465056 3630 1672864 13066 org.jruby.gen.RubyObject25 60 0.21% 86.51% 439304 10970 1493024 37313 org.jruby.gen.RubyObject3 61 0.20% 86.71% 434608 9044 2311744 48151 org.jruby.gen.RubyObject5 68 0.16% 87.93% 349936 7280 1305136 27180 org.jruby.gen.RubyObject4 79 0.11% 89.34% 233824 3646 838432 13093 org.jruby.gen.RubyObject8 238 0.01% 96.11% 28088 314 30816 345 org.jruby.gen.RubyObject14
  23. 10M * One-variable Object 0MB 200MB 400MB 600MB 800MB Not

    Optimized Optimized 320 480 400 Ruby Object Object[] 33% memory reduction
  24. Arrays in Rails `select` Bench percent live alloc'ed class rank

    self accum bytes objs bytes objs name 5 4.90% 33.79% 10481824 218361 38183968 795489 org.jruby.RubyArray 11 3.11% 56.32% 6661072 138762 22817680 475358 org.jruby.specialized.RubyArrayOneObject 17 1.46% 67.96% 3124112 55779 15838128 282815 org.jruby.specialized.RubyArrayTwoObject More than half of allocated arrays are 1- or 2-element!
  25. 10M * One-element Array 0MB 250MB 500MB 750MB 1000MB Not

    Optimized Optimized 400 650 570 Ruby Object IRubyObject[] 33% memory reduction
  26. JVM is Great, But... • Runtime optimizations give us excellent

    performance...eventually • Startup time, warmup time are impacted • We continue to reduce this impact
  27. total execution time (lower is better) 0s 1.25s 2.5s 3.75s

    5s gem --version gem list (~350 gems) 4.6s 3.6s 0.7s 0.4s CRuby JRuby (JDK8)
  28. Why? • CRuby: Mostly native code at startup • Parser,

    compiler, interpreter, core classes, extensions • JRuby: 100% interpreted bytecode at startup • Everything in JRuby starts "cold" • JVM eventually optimizes...but it's too late for startup time
  29. Running in Same JVM total execution time (lower is better)

    0s 1.25s 2.5s 3.75s 5s gem list (~350 gems) 1.3s 1.6s 1.6s 1.6s 2.2s 1.7s 1.7s 2.0s 2.2s 3.5s 4.6s
  30. JRuby Flag: --dev • export JRUBY_OPTS="--dev" • Disables JRuby's JIT

    • Reduces JVM JIT • Don't use when benchmarking! • 30-40% reduction • More improvements coming total execution time (lower is better) 0s 1.15s 2.3s 3.45s 4.6s gem list (~350 gems) 3.0s 4.6s JRuby JRuby --dev
  31. Ahead-of-time Compile? • Precompile JVM code to native • Improves

    base startup time • Used by TruffleRuby currently • Coming soon: precompiled JRuby and Ruby code • Instant application startup?
  32. total execution time (lower is better) 0.4s 4.3s 8.2s 12.1s

    16s -e 1 0.5s 1.7s JRuby --dev TruffleRuby native total execution time (lower is better) 0s 4s 8s 12s 16s gem list (~350 gems) 14.9s 4.6s JRuby --dev TruffleRuby native
  33. Small and Large • Sinatra and Roda • https://github.com/CaDs/ruby_benchmarks •

    Comparing JRuby, CRuby, and TruffleRuby • Rails • https://github.com/rubygems/rubygems.org • JRuby versus CRuby
  34. Sinatra and Roda • Well-supported on JRuby • Many production

    users at very large scales • Very simple example with no database • Benchmarking request routing mostly • Good indicator of small service performance
  35. Sinatra and Roda requests/second (higher is better) 0 12500 25000

    37500 50000 Sinatra Roda Roda (1 thread) Roda (WEBrick) 6,414 4,307 5,214 4,257 21,209 15,419 44,703 42,468 14,489 12,284 CRuby JRuby TruffleRuby
  36. JRuby on Rails • Benefit from the JVM, libraries, languages

    • Single JRuby process runs your whole site • "Are there JRuby users running Rails applications?" • Oh yes! And at scale! • The best way to scale large Rails apps today
  37. --- testapp_mri_pg/Gemfile +++ testapp_jruby_pg/Gemfile -# Use postgresql as the database

    for Active Record -gem 'pg', '>= 0.18', '< 2.0' +# Use jdbcpostgresql as the database for Active Record +gem 'activerecord-jdbcpostgresql-adapter' # See https://github.com/rails/execjs#readme for more supported runtimes -# gem 'mini_racer', platforms: :ruby - +gem 'therubyrhino' group :development do - # Spring speeds up development... - gem 'spring' - gem 'spring-watcher-listen', '~> 2.0.0' end Minimal Config Diffs --- testapp_mri_pg/config/database.yml +++ testapp_jruby_pg/config/database.yml @@ -65,5 +81,7 @@ production: <<: *default database: rails_prod + host: localhost + port: 5432 username: rails_prod password: rails_prod --- testapp_mri_pg/config/puma.rb 2019-04-19 04:48:51.425474315 +0000 +++ testapp_jruby_pg/config/puma.rb 2019-04-17 08:56:53.529154189 +0000 @@ -4,7 +4,7 @@ # the maximum value specified for Puma. Default is set to 5 threads for minimum # and maximum; this matches the default thread size of Active Record. # -threads_count = ENV.fetch("RAILS_MAX_THREADS") { 2 } +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 20 } threads threads_count, threads_count # Specifies the `port` that Puma will listen on to receive requests; default is 3000. @@ -21,7 +21,7 @@ # Workers do not work on JRuby or Windows (both of which do not support # processes). # -workers ENV.fetch("WEB_CONCURRENCY") { 2 } +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } # Use the `preload_app!` method when specifying a `workers` number. # This directive tells Puma to first boot the application and load code
  38. Rails 6 actioncable: 203 runs, 921 assertions, 0 failures, 10

    errors actionmailbox: 79 runs, 205 assertions, 0 failures, 2 errors actionmailer: 220 runs, 509 assertions, 0 failures, 0 errors actionpack: 3255 runs, 16027 assertions, 1 failures, 0 errors actiontext: 53 runs, 94 assertions, 5 failures, 0 errors actionview: 2068 runs, 4667 assertions, 2 failures, 3 errors activejob: 301 runs, 659 assertions, 0 failures, 0 errors activemodel: 844 runs, 2350 assertions, 0 failures, 0 errors activestorage:225 runs, 696 assertions, 0 failures, 0 errors activesupport: 4362 runs, 13909 assertions, 15 failures, 1 errors 99.902% passing!
  39. Rails is the Thing! • If you can't run Rails

    fast, you've got more work to do • By far the biggest use case for Ruby • Frustratingly difficult to optimize • JRuby has run microbenchmarks faster for years... • ...but Rails performance was always about the same • Times are changing!
  40. ActiveRecord Performance • Rails apps live and die by ActiveRecord

    • Largest CPU consumer by far • Heavy object churn, GC overhead • Create, read, and update measurements • CRuby 2.5.1 vs JRuby 9.2 on JDK11
  41. ActiveRecord Selects time for 1000 selects, lower is better 0

    0.075 0.15 0.225 0.3 CRuby 2.5 JRuby C2 JRuby Graal binary boolean date datetime decimal float integer string text time timestamp *
  42. Scaling Rails • Classic problem on MRI • No concurrent

    threads, so we need processes • Processes duplicate runtime state and waste resources • JRuby is the answer! • Multi-threaded single process runs your entire site
  43. Simple Rails Performance • Rails 5.1.6, Postgresql 10, scaffolded view

    • JRuby: 10 threads, CRuby: 10 processes • 4k requests to warm up, then measure every 10k • EC2 c4.xlarge: 4 vCPUs, 7.5GB • Bench, database, and app on same instance
  44. Requests per second, full stack scaffolded read on Postgresql 0

    325 650 975 1300 CRuby JRuby 1,253.86 910.02
  45. Requests per second 0 325 650 975 1300 Requests over

    time 10k 20k 30k 40k 50k 60k 70k 80k 90k 100k CRuby 2.5 CRuby 2.6 JIT JRuby 9.2
  46. JRuby on Rails Memory • Single instance is roughly 10x

    larger • Threading to the rescue! • Ten CRuby processes = 500MB • Ten JRuby threads = 400-500MB • Copy-on-write helps CRuby a bit • Eventually most processes grow Instances vs Memory 0MB 1250MB 2500MB 3750MB 5000MB 1 5 10 100 CRuby JRuby
  47. rubygems.org Performance • Laptop (i7 with 16Gb of memory) (localhost)

    • No SSL • Puma instead of Unicorn • Single machine: • rails, postgresql, elasticsearch, toxiproxy, redis, memcached • Benchmark driver: Apache Bench (ab)
  48. Performance versus concurrent users, requests/second Peak Requests/second 0 100 200

    300 400 Concurrent threads 1 2 4 6 8 10 12 14 16 18 20 JRuby puma single 2.6.2 puma clustered 2.6.2 puma single 3 16
  49. Memory Usage Memory (RES) 0 1000 2000 3000 4000 #

    of test runs 1 Run (35 mins) 6 Runs 11 Runs 16 Runs 21 Runs 1,536 1,536 1,536 1,434 1,228 3,620 3,540 3,460 3,340 3,060 CRuby (20 workers) JRuby (20 threads, 384m heap) +8.4% +14.4% +4.5% +6.7% +0% 2.5x smaller +0% +3.3% +3.2%
  50. Scaling Takeaways + Uses less memory (threads vs processes) +

    More memory stable over time + More CPU efficient – More warmup time
  51. Try JRuby! • New apps will be easiest, of course

    • Existing apps require a few extra steps • Tweak database, server configs • Bundle install and deal with C extensions • Run your tests
  52. Use-Case: Discourse • “A platform for community discussion” • Very

    large, well-known Rails application • >500 gems • 250,000 lines of Ruby • JRuby is not currently supported • But it is almost working!
  53. jruby-lint gem See how ready your Ruby code is to

    run on JRuby $ gem install jruby-lint $ cd my-app $ jrlint
  54. [] ~/projects/discourse $ jrlint JRuby-Lint version 0.9.0 ./Gemfile:: [gems, info]

    For more on gem compatibility see http://wiki.jruby.org/C-Extension-Alternatives ./Gemfile:80: [gems, warning] Found gem 'oj' which is reported to have some issues: Try `gson`, `json` or `json_pure` instead.| gem 'oj' ./Gemfile:81: [gems, warning] Found gem 'pg' which is reported to have some issues: Use activerecord-jdbcpostgresql-adapter instead or pg_jruby (drop-in replacement).| gem 'pg' ./Gemfile:187: [gems, warning] Found gem 'mysql2' which is reported to have some issues: Use activerecord-jdbcmysql-adapter.| gem 'mysql2' ./Gemfile:188: [gems, warning] Found gem 'redcarpet' which is reported to have some issues: Same as with **RDiscount** use alternatives such as kramdown, Maruku or markdown_j| gem 'redcarpet' ./Gemfile:189: [gems, warning] Found gem 'sqlite3' which is reported to have some issues: Use activerecord-jdbcsqlite3-adapter.| gem 'sqlite3', '~> 1.3.13' ./app/mailers/user_notifications.rb:152: [nonatomic, warning] Non-local operator assignment (@popular_topics) is not guaranteed to be atomic. @popular_topics = topics_for_digest[0, SiteSetting.digest_topics] ./app/mailers/user_notifications.rb:630: [nonatomic, warning] Non-local operator assignment (@site_name) is not guaranteed to be atomic. @site_name = SiteSetting.email_prefix.presence || SiteSetting.title # used by I18n ./app/models/report.rb:21: [nonatomic, warning] Non-local operator assignment (@start_date) is not guaranteed to be atomic. @start_date ||= Report.default_days.days.ago.utc.beginning_of_day ./app/models/report.rb:22: [nonatomic, warning] Non-local operator assignment (@end_date) is not guaranteed to be atomic. @end_date ||= Time.now.utc.end_of_day ./app/models/admin_dashboard_next_data.rb:17: [nonatomic, warning] Non-local operator assignment (@json) is not guaranteed to be atomic. @json ||= get_json ./app/models/topic_posters_summary.rb:31: [nonatomic, warning] Non-local operator assignment (@descriptions_by_id) is not guaranteed to be atomic. @descriptions_by_id ||= begin ./app/models/topic_posters_summary.rb:33: [nonatomic, warning] Non-local operator assignment (descriptions[id]) is not guaranteed to be atomic. descriptions[id] ||= [] ./app/models/topic_posters_summary.rb:79: [nonatomic, warning] Non-local operator assignment (@avatar_lookup) is not guaranteed to be atomic. @avatar_lookup ||= options[:avatar_lookup] || AvatarLookup.new(user_ids) ./app/models/topic_posters_summary.rb:83: [nonatomic, warning] Non-local operator assignment (@primary_group_lookup) is not guaranteed to be atomic. @primary_group_lookup ||= options[:primary_group_lookup] || PrimaryGroupLookup.new(user_ids) ./app/models/directory_item.rb:8: [nonatomic, warning] Non-local operator assignment (@headings) is not guaranteed to be atomic. @headings ||= [:likes_received, ./app/models/directory_item.rb:18: [nonatomic, warning] Non-local operator assignment (@types) is not guaranteed to be atomic. @types ||= Enum.new(all: 1, ./app/models/group_history.rb:11: [nonatomic, warning] Non-local operator assignment (@actions) is not guaranteed to be atomic. @actions ||= Enum.new( ./app/models/locale_site_setting.rb:10: [nonatomic, warning] Non-local operator assignment (@values) is not guaranteed to be atomic. @values ||= supported_locales.map do |locale| ./app/models/locale_site_setting.rb:25: [nonatomic, warning] Non-local operator assignment (@language_names) is not guaranteed to be atomic. @language_names ||= begin ./app/models/locale_site_setting.rb:41: [nonatomic, warning] Non-local operator assignment (@supported_locales) is not guaranteed to be atomic. @supported_locales ||= begin ./app/models/category.rb:98: [nonatomic, warning] Non-local operator assignment (TOPIC_CREATION_PERMISSIONS) is not guaranteed to be atomic. TOPIC_CREATION_PERMISSIONS ||= [:full] ./app/models/category.rb:99: [nonatomic, warning] Non-local operator assignment (POST_CREATION_PERMISSIONS) is not guaranteed to be atomic. POST_CREATION_PERMISSIONS ||= [:create_post, :full] ./app/models/category.rb:109: [nonatomic, warning] Non-local operator assignment (@topic_id_cache) is not guaranteed to be atomic. @topic_id_cache = DistributedCache.new('category_topic_ids') ./app/models/category.rb:228: [nonatomic, warning] Non-local operator assignment (@@cache) is not guaranteed to be atomic. @@cache ||= LruRedux::ThreadSafeCache.new(1000) ./app/models/category.rb:520: [nonatomic, warning] Non-local operator assignment (@has_children) is not guaranteed to be atomic. @has_children ||= (id && Category.where(parent_category_id: id).exists?) ? :true : :false
  55. Unsupported Extensions ./Gemfile:: [gems, info] For more on gem compatibility

    see http://wiki.jruby.org/C-Extension-Alternatives ./Gemfile:80: [gems, warning] Found gem 'oj' which is reported to have some issues: Try `gson`, `json` or `json_pure` instead.| gem 'oj' ./Gemfile:81: [gems, warning] Found gem 'pg' which is reported to have some issues: Use activerecord-jdbcpostgresql-adapter instead or pg_jruby (drop-in replacement).| gem 'pg' ./Gemfile:187: [gems, warning] Found gem 'mysql2' which is reported to have some issues: Use activerecord-jdbcmysql-adapter.| gem 'mysql2' ./Gemfile:188: [gems, warning] Found gem 'redcarpet' which is reported to have some issues: Same as with **RDiscount** use alternatives such as kramdown, Maruku or markdown_j| gem 'redcarpet' ./Gemfile:189: [gems, warning] Found gem 'sqlite3' which is reported to have some issues: Use activerecord-jdbcsqlite3-adapter.| gem 'sqlite3', '~> 1.3.13'
  56. Threading Concerns ./app/models/report.rb:21: [nonatomic, warning] Non-local operator assignment (@start_date) is

    not guaranteed to be atomic. @start_date ||= Report.default_days.days.ago.utc.beginning_of_day ./app/models/report.rb:22: [nonatomic, warning] Non-local operator assignment (@end_date) is not guaranteed to be atomic. @end_date ||= Time.now.utc.end_of_day ./app/models/admin_dashboard_next_data.rb:17: [nonatomic, warning] Non-local operator assignment (@json) is not guaranteed to be atomic. @json ||= get_json ./app/models/topic_posters_summary.rb:31: [nonatomic, warning] Non-local operator assignment (@descriptions_by_id) is not guaranteed to be atomic. @descriptions_by_id ||= begin ./app/models/topic_posters_summary.rb:33: [nonatomic, warning] Non-local operator assignment (descriptions[id]) is not guaranteed to be atomic. descriptions[id] ||= [] ./app/models/topic_posters_summary.rb:79: [nonatomic, warning] Non-local operator assignment (@avatar_lookup) is not guaranteed to be atomic. @avatar_lookup ||= options[:avatar_lookup] || AvatarLookup.new(user_ids) ./app/models/topic_posters_summary.rb:83: [nonatomic, warning] Non-local operator assignment (@primary_group_lookup) is not guaranteed to be atomic. @primary_group_lookup ||= options[:primary_group_lookup] || PrimaryGroupLookup.new(user_ids) @language_names) is not guaranteed to be atomic. @language_names ||= begin
  57. Unsupported Features ./script/measure.rb:48: [objectspace, warning] Use of ObjectSpace is expensive

    and disabled by default. Use -X+O to enable. ObjectSpace.each_object do |o| ./script/check_forking.rb:14: [fork, error] Kernel#fork is not implemented on JRuby. child = fork do ./script/check_forking.rb:17: [fork, error] Kernel#fork is not implemented on JRuby. grand_child = fork do
  58. Why Not Support C Extensions? • Often used for performance

    reasons • The API is enormous and invasive • Direct pointer access to Ruby objects • JRuby 1.6 shipped experimental support, but... • Huge amount of work for partial compatibility • Performance was slower than pure Ruby!
  59. Options 1.Remove it if not needed 2.Use a pure-Ruby version

    if performance is good enough 3.Call into a native library using FFI (Foreign Function Interface) 4.Use an equivalent JRuby library 5.Write a JRuby extension
  60. oj: Optimized json • Fast json parsing and dumping with

    many options • No support for JRuby currently • Common transitive dependency with its own API • Needed for many popular apps/libraries https://github.com/ohler55/oj
  61. oj for JRuby! • 9200 lines of Java (vs 20k

    lines of C) • Almost ready: 448 runs, 765 assertions, 43 failures, 12 errors • 35 F/E from minor features, date/time diffs • Performs even better than the C extension! • ...and we've done almost no optimization!
  62. Load Performance 0M 0.3M 0.6M 0.9M 1.2M small medium large

    0.13 0.29 1.1 0.05 0.11 0.36 0.06 0.11 0.72 MRI (oj) JRuby (json) JRuby (oj) Millions of loads per second (higher is better) https://techblog.thescore.com/2014/05/23/benchmarking-json-generation-in-ruby/
  63. Dump Performance 0 0.75 1.5 2.25 3 small medium large

    0.44 0.86 2.3 0.22 0.44 1.1 0.33 0.73 2.1 MRI (oj) JRuby (json) JRuby (oj) Million of dumps per second (higher is better) https://techblog.thescore.com/2014/05/23/benchmarking-json-generation-in-ruby/
  64. That's it! • Once your application bundles, it should run!

    • Remaining issues: come talk to us • Many options for deploying, packaging as Java app, etc • Remember those JVM tools!
  65. Thank You! • Charles Oliver Nutter • [email protected] • Follow

    me on Twitter: @headius • http://jruby.org