RubyKaigi 2018

Faster Apps, No Memory Thrash: Get Your Memory Config Right

The Ruby memory system can be tricky. Configuring it isn't easy. I'll show you a new simple tool to optimize your Ruby binary's memory settings.
You'll learn about the CRuby memory resources and how you check them. Let's optimize your memory usage to keep memory small and keep garbage collection fast.

RubyKaigi 2018 https://rubykaigi.org/2018/presentations/codefolio

RubyKaigi 2018

00:00:01.399 Hello everyone! It's great to see you all here.
00:00:07.319 I'm Noah Gibbs, and today we're going to talk about improving memory configurations for Ruby applications.
00:00:14.250 Specifically, we'll focus on how to optimize Ruby's memory usage to prevent performance issues.
00:00:20.820 I also want to take a moment to express my gratitude to AppFolio, where I'm a Ruby fellow.
00:00:30.430 They support my efforts in writing blog posts and running extensive benchmarks.
00:00:36.540 Now, let's dive into the topic at hand.
00:00:42.690 You're all here to learn how to improve the performance of Ruby applications facing memory problems.
00:00:49.559 Before we start, let me share a bit about my life.
00:00:54.840 I recently welcomed a three-month-old baby who is incredibly cute, which explains why I may look a bit tired.
00:01:07.400 Life is busy with two other kids and activities like visiting the City Museum.
00:01:14.520 If you're not familiar with it, I highly recommend checking out the beautiful hand-painted maps.
00:01:21.299 Now, let's focus on Ruby and its memory system.
00:01:26.640 To understand how to improve Ruby's performance, it's important to comprehend how its memory system functions.
00:01:33.450 We will explore the concepts of tiny, small, and big objects in Ruby.
00:01:39.180 Tiny objects are the smallest in Ruby and fit entirely inside their references.
00:01:44.790 Ruby maintains 64-bit references, generally pointing to something, but tiny objects fit in that reference.
00:01:52.070 These objects can include integers from about negative and positive one billion, symbols, floating-point numbers, and special values.
00:01:57.450 The significance of this is that tiny objects are efficient and fast.
00:02:02.909 When we discuss symbols being faster than strings, it refers to how symbols fit within the reference itself.
00:02:09.149 Conversely, let's now examine small objects.
00:02:15.990 Small objects occupy 40-byte slots, allowing for short strings and arrays, as well as small big decimal numbers.
00:02:23.730 Class instances with only a few instance variables also fit into these slots.
00:02:28.770 Allocating memory slots for small objects is economical since the memory allocator is called incrementally.
00:02:35.250 If small objects exceed their size limitations, they become big objects.
00:02:41.100 Big objects have more expensive allocation calls whenever they are created or resized.
00:02:49.380 Consequently, they are crucial to track down when diagnosing memory issues in your Ruby applications.
00:02:55.500 Now let's discuss garbage collection.
00:03:01.560 Garbage collection is the main aspect to address when dealing with memory in Ruby.
00:03:06.960 Ruby utilizes a generational mark-sweep garbage collector.
00:03:12.000 While it may not be the top garbage collector, it works well.
00:03:17.250 Generational garbage collection checks newer objects more frequently than older ones.
00:03:22.560 Objects that remain for a longer time are less frequently marked for collection since older objects might still be in use.
00:03:30.510 The mark phase examines all available objects to identify those no longer in use.
00:03:36.860 Once identified, the sweep phase collects and clears memory from those marked objects.
00:03:45.130 While Ruby's garbage collector is efficient, alternatives like those in the JVM are even superior.
00:03:50.480 Generational garbage collection works through two main types: major and minor.
00:03:58.190 The major garbage collection evaluates all objects, while minor garbage collection focuses specifically on newer ones.
00:04:04.640 Major collections occur when certain thresholds are crossed, prompting a complete check.
00:04:10.160 You can also manually trigger major garbage collection by calling `GC.start`, which initiates a thorough sweep.
00:04:17.670 During an application's startup phase, Ruby starts with a modest memory allocation.
00:04:24.750 As applications run, they accumulate more memory through a cycle of growth and garbage collection.
00:04:30.340 This cycle involves growing memory usage, performing garbage collection, and expanding available slots as needed.
00:04:36.860 The expansion phase results from running out of allocated memory slots.
00:04:41.680 Although most applications eventually stabilize to a certain size, monitoring growth patterns is important.
00:04:47.410 The overall goal is to recognize how your application behaves during its lifecycle and identify possible areas for optimization.
00:04:58.260 Now, let's discuss how to tackle and resolve memory issues.
00:05:06.640 The most effective way to improve garbage collection performance is to reduce object creation.
00:05:14.240 Less garbage generated leads to more efficient and faster garbage collection.
00:05:21.100 Utilizing more efficient algorithms, caching, or pre-calculating values can help minimize garbage.
00:05:27.460 Additionally, when dealing with large objects, consider combining multiple smaller objects into a single entity.
00:05:36.090 You can also take advantage of destructive operations to reuse data efficiently.
00:05:41.840 For instance, methods that modify strings and arrays in place reduce the creation of new objects.
00:05:46.080 This is critical for situations where low memory usage is imperative.
00:05:51.010 Further, using tools like `GC.stat` will provide insights into your memory configuration.
00:05:57.450 This tool can help identify available slots and how often garbage collections occur.
00:06:04.030 After gathering this information, adjusting environment variables can lead to improvements.
00:06:10.090 You can utilize options like `RUBY_GC_HEAP_FREE_SLOTS` to manage memory allocation effectively.
00:06:20.000 Proper allocation means starting your application with an optimal number of slots.
00:06:26.810 Maintaining a healthy ratio of free slots will also keep performance steady.
00:06:32.780 For instance, the `RUBY_GC_HEAP_GROWTH_MAX_SLOTS` variable can help expand available memory limits.
00:06:39.950 Considering all these aspects contributes greatly to effective memory management in Ruby.
00:06:46.540 Now, I want to introduce a tool I've been developing called `mmm`.
00:06:52.290 This tool aims to simplify the process of tuning memory configurations for Ruby applications.
00:06:59.980 By setting appropriate environment variables, it allows for optimal allocation from the start.
00:07:05.890 After running your application, you can use `mmm` to determine the suitable allocation sizes.
00:07:10.640 This can significantly improve startup times for large applications.
00:07:16.680 It's important to consider the impact of memory management on your application's performance.
00:07:22.930 Examples of real-world applications effectively using this concept showcase decreased warm-up times.
00:07:30.110 Therefore, if you're encountering performance hiccups during startup, this tool could be beneficial.
00:07:37.360 Next, I'd like to touch on memory allocators and their importance.
00:07:44.300 Most operating systems come with a memory allocator by default, like `malloc`.
00:07:52.600 However, there are alternative implementations, such as `jemalloc`, that offer significant advantages.
00:07:58.750 Increased efficiency in allocating memory leads to faster application performance.
00:08:05.010 For Ruby, integrating with `jemalloc` has been a game changer for several applications.
00:08:11.560 Testing has shown marked improvements, especially when running under load.
00:08:17.780 Implementing these optimizations could yield a 10-12% increase in overall speed.
00:08:23.920 Another allocator, `tcmalloc`, may also provide slight performance boosts.
00:08:30.570 It's crucial to evaluate the performance implications of your application and the allocator you choose.
00:08:37.200 To round out this discussion, always prioritize being on the latest version of Ruby.
00:08:43.700 This optimizes garbage collection and can lead to performance improvements.
00:08:51.100 As Ruby evolves, it brings enhanced features to memory management.
00:08:57.800 Lastly, addressing potential fragmentation issues and common pitfalls can yield better performance.
00:09:05.900 Make sure to regularly profile your application to identify areas needing improvement.
00:09:12.040 Utilizing tools like `GC::Profiler` can provide valuable insights.
00:09:19.450 Monitor garbage collection statistics for duration and frequency of pauses.
00:09:26.040 This will help determine if memory management is causing noticeable slowdowns.
00:09:35.310 As a practical exercise, compile Ruby with additional options to enhance profiling.
00:09:41.200 After extracting data, analyze it for any abnormalities.
00:09:48.150 Keep experimenting with different configurations to find an optimal setup.
00:09:54.070 The understanding of memory management will make a significant difference in Ruby applications.
00:10:00.350 As we conclude, feel free to ask any questions regarding memory, garbage collection, or anything we've discussed.
00:10:09.910 I'm here to assist and clarify anything that may be unclear.
00:10:16.790 Understanding and managing memory effectively can greatly improve performance.
00:10:21.269 Thank you for your attention!