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

Runtime Model of Ruby, JavaScript, Erlang, and ...

Runtime Model of Ruby, JavaScript, Erlang, and other popular languages

Each language has a specific runtime properties which make it a good fit for one task and a bad one for another.
In this talk we will walk though the most important runtime properties and discuss a lot of popular languages with their strengths and weaknesses. As a bonus section, we will take a deeper look on a current state and a possible future of Ruby MRI runtime.

Railsware

April 18, 2017
Tweet

More Decks by Railsware

Other Decks in Programming

Transcript

  1. Key Behaviors: CPU-bound and IO-bound CPU-bound: How fast can we

    calculate something? IO-bound: How many "simultaneous" interactions with the outer world can we handle?
  2. Main Questions to Runtime Model How efficient are CPU-bound tasks?

    does runtime support parallelism? How efficient are IO-bound tasks? what is concurrency model? How efficient is Memory management? does runtime use GC and what kind of GC?
  3. CPU-bound tasks CPU-bound - time to complete a task is

    determined by the speed of the CPU Examples: compiling assets building Ruby during installation resizing images creating ActiveRecord objects after obtaining response from database
  4. CPU-bound tasks: Bare Performance The closer to bare metal -

    the faster The champions: statically typed languages compiled to native code C/C++ Rust Swift Go
  5. CPU-bound tasks: Bare Performance Not that bad: dynamic languages with

    JIT Raw estimation: best is about 50% of statically typed languages performance Clojure JavaScript V8 JRuby Truffle
  6. Parallel: C/C++ Rust Go JVM (Java, Scala, Clojure, JRuby) .NET

    (C#, F#) Haskell Erlang / Elixir Non-Parallel (GIL): Ruby MRI Python Non-Parallel (Event Loop): JavaScript (Node.JS) Ruby (EventMachine) Python (Twisted) CPU-bound tasks: Parallelism
  7. Non-GC: C/C++ Rust Swift GC: Go Java / Scala C#

    / F# JavaScript Ruby Erlang / Elixir Python CPU-bound tasks: non-GC vs GC Raw estimation: ~10% performance penalty when using GC
  8. Garbage Collector Main contributions to Runtime Model: 1. Expect about

    ~10% of performance penalty 2. Leads to GC "pauses" in execution 3. There is a maximum heap size, which can be handled efficiently
  9. Reference-Counting: Python Perl 5 Tracing: JVM .NET Go Ruby JavaScript/Node.JS

    Haskell Erlang / Elixir ... Garbage Collector Types
  10. Garbage Collector: Tracing Generational GC dominates Generational GC: JVM .NET

    Ruby JavaScript/Node.JS Erlang / Elixir Haskell Concurrent, Tri-color, mark-sweep: Go
  11. GC at a different angle 1. GC in a shared-heap

    runtime 2. GC in a multi-heap runtime
  12. GC in a shared-heap runtime Mostly all popular runtimes use

    shared heap Examples: JVM .NET Go Ruby JavaScript/Node.JS Python Haskell ...
  13. GC in a shared-heap runtime Main issue: GC should be

    done across one chunk of memory and complexity of GC grows linearly (or worse) with a heap size Outcomes: Performance degradation on big heap size Increased delays in runtime execution due to GC
  14. GC in a multi-heap runtime Example: Erlang VM Main trick:

    each process has its own isolated heap, which shares nothing with others
  15. GC in a multi-heap runtime Main outcomes of Erlang VM

    memory layout: 1. GC can be done on a much smaller area and scales well 2. GC can be totally avoided by finishing process before GC kicks in (web- request is finished) 3. Erlang VM can use 128Gb and support 2 million active connections
  16. IO-bound IO-bound: how many "simultaneous" interactions with the outer world

    can we handle? Examples: IRB/Pry input/output Reading file content Handling web request Handling websocket connection Performing database query Calling remote service Reading data from Redis Sending Email
  17. IO-bound: Blocking vs Non- Blocking IO Synchronous or Blocking: waits

    for the other side to be ready for IO-interaction Asynchronous or Non-Blocking: handles other IO-interactions until the other side is ready to interact
  18. IO-bound: Synchronous or Blocking Pros: Easy to develop - sequential

    code Cons: Working thread is dedicated to only one IO-interaction
  19. IO-bound: Asynchronous or Non-Blocking Pros: High performance - ability to

    handle large number of connections Cons: Harder to write code Callback / Promise hell
  20. IO-bound: Main Concurrency Models 1. Blocking IO + OS Threads

    2. Event Loop or Reactor pattern 3. Green Threads
  21. IO-bound: Blocking IO + Threads In such combination runtime blocks

    on any IO operation, and OS handles switch to another thread. Pros: Easy to write logic per thread - everything is sequential Quite performant - max limit ~ 5000 concurrent threads Memory efficient - everything is shared Cons: Shared state is a big issue for mutable languages Requires usage of different thread synchronization primitives High requirements to quality of third-party libraries
  22. IO-bound: Blocking IO + Threads Managed Runtimes: JVM .NET Ruby

    MRI (despite having GIL) Python (despite having GIL)
  23. IO-bound: Blocking IO + Processes Unicorn - Ruby web-server Postgres

    for handling client connections Apache (one of the modes) Pros: Isolated memory - no risks of simultaneous writes to the same memory Sequential, "blocking" code Cons: Higher memory consumption compared to threads Lower performance compared to asynchronous mode
  24. IO-bound: Asynchronous: Event Loop or Reactor pattern Pros: Memory-efficient -

    shared memory Memory-safe - no race conditions, because only one "callback" is performed till it finishes High-performant - potentially can handle millions of connections Cons: Single-threaded - only one CPU core is used Callback / Promise hell, but can be avoided with coroutines or async/await
  25. IO-bound: Synchronous Asynchronity: Green Threads Instead of using OS threads,

    runtime has its own scheduler and manages threads without OS. API looks like synchronous But under the hood everything runs asynchronously Green Threads Benefits compared to OS Threads: Smaller memory usage per thread Cheaper context-switch
  26. IO-bound: Green Threads: Failure Stories Java 1.1 Ruby 1.8 Main

    issues: Using mutexes as a main concurrency primitive Not efficient implementation Both switched to OS Threads
  27. IO-bound: Green Threads: Success Stories Erlang VM Golang VM GHC

    Haskell Main difference - simpler set of primitives for handling concurrency without mutexes and synchronization: Actors and message-passing in Erlang Gorotines and channels in Go MVar and STM in Haskell
  28. IO-bound: Green Threads of Go Pros: Sequential "blocking" code Memory-efficient

    - one virtual machine Non-copying memory exchange between goroutines through channels Great IO-performance Cons: Go still has a possibility to mutate a global state
  29. IO-bound: Green Threads of Erlang Pros: Sequential "blocking" code Memory-efficient

    - one virtual machine Great IO-performance: can handle about ~2_000_000 connections Cons: Copies data when exchanging messages between processes
  30. Ruby MRI Runtime: CPU- bound CPU-bound: dynamic no JIT non-parallel

    CPU-bound performance is poor MRI-team is going to add JIT, still not clear when it will happen
  31. Ruby MRI Runtime: GC GC: one big heap - delays

    in GC generational mark&sweep - good Current GC is quite good MRI-team does not have any further plans to improve GC speed
  32. Ruby MRI Runtime: IO-bound IO-bound: blocking single-threaded multi-process (Unicorn) IO-bound

    stuff is not efficient compared to Node.JS or Erlang - slower 5-10x
  33. Why Ruby doesn't use Threads and Java does? Java was

    built and promoted from the start as a concurrency-focused language Every library was meant to work in multi-threaded environment Ruby was never built with multi-threading in mind Main risk is to run into library which unsafely changes global state
  34. Ruby MRI Runtime: New Hope - Guilds Guild is a

    set of Threads and Fibers which can't directly access memory of another Guild
  35. Ruby MRI Runtime: New Hope - Guilds Pros: Memory-efficient -

    non-mutable stuff is shared (code, freezed objects) Memory-safe - different Guilds can't simultaneously mutate same object Good enough performance for web-requests Parallel - Guilds don't have GIL
  36. Ruby MRI Runtime: New Hope - Guilds Cons: Still can't

    handle big amount of connections - Guilds are more expensive than Green Threads or Event Loop Compared to Event Loop or Green Treads: Memory usage is higher Context switch is slower
  37. Ruby MRI Runtime: New Hope - Guilds MRI team is

    keen to implement them, but there are a lot of small issues with memory sharing, which should be addressed Estimated performance gain: ~3-5x
  38. Other factors contributing to Runtime Model Data Structures: mutable or

    immutable, O(?) GC implementation details Heap and Stack usage Memory Model CPU Architecture Underlying OS system calls(pthreads, select, epoll/kqueue, etc) ...