ruby-core@ruby-lang.org archive (unofficial mirror)
 help / color / mirror / Atom feed
From: "byroot (Jean Boussier)" <noreply@ruby-lang.org>
To: ruby-core@ruby-lang.org
Subject: [ruby-core:109470] [Ruby master Feature#18885] End of boot advisory API for RubyVM
Date: Wed, 10 Aug 2022 18:24:54 +0000 (UTC)	[thread overview]
Message-ID: <redmine.journal-98631.20220810182454.7941@ruby-lang.org> (raw)
In-Reply-To: redmine.issue-18885.20220628132154.7941@ruby-lang.org

Issue #18885 has been updated by byroot (Jean Boussier).


> Is there any point to precompute inline caches if there is no fork?

Yes, the first "request" (or whatever your unit of work is) won't have to do it. So you are moving some work to boot time, instead of user input processing time.

> these processes would benefit from this API.

For the CoW parts no, not much. If the child isn't going to live for long, it's unlikely to invalidate that many pages. 

----------------------------------------
Feature #18885: End of boot advisory API for RubyVM
https://bugs.ruby-lang.org/issues/18885#change-98631

* Author: byroot (Jean Boussier)
* Status: Open
* Priority: Normal
----------------------------------------
### Context

Many optimizations in the Ruby VM rely on lazily computed caches: Strings coderange, constant caches, method caches, etc etc.
As such even without JIT, some operations need a bit of a warm up, and might be flushed if new constants are defined, new code is loaded, or some objects are mutated.

Additionally these lazily computed caches can cause increased memory usage for applications relying on Copy-on-Write memory.
Whenever one of these caches is updated post fork, the entire memory page is invalidated. Precomputing these caches at the end of boot,
even if based on heuristic, could improve Copy-on-Write performance.

The classic example is the objects generation, young objects must be promoted to the old generation before forking, otherwise they'll get invalidated on the next GC run. That's what https://github.com/ko1/nakayoshi_fork addresses.

But there are other sources of CoW invalidation that could be addressed by MRI if it had a clear notification when it needs to be done.

### Proposal

If applications had an API to notify the virtual machine that they're done loading code and are about to start processing user input,
it would give the VM a good point in time to perform optimizations on the existing code and objects.

e.g. could be something like `RubyVM.prepare`, or `RubyVM.ready`.

It's somewhat similar to [Matz's static barrier idea from RubyConf 2020](https://youtu.be/JojpqfaPhjI?t=1908), except that it wouldn't disable any feature.

### Potential optimizations

`nakayoshi_fork` already does the following:

  - Do a major GC run to get rid of as many dangling objects as possible.
  - Promote all surviving objects to the highest generation
  - Compact the heap.

But it would be much simpler to do this from inside the VM rather than do cryptic things such as `4.times { GC.start }` from the Ruby side.

It's also not good to do this on every fork, once you fork the first long lived child, you shouldn't run it again. So decorating `fork` is not a good hook point. 

Also after discussing with @jhawthorn, @tenderlovemaking and @alanwu, we believe this would open the door to several other CoW optimizations:

#### Precompute inline caches

Even though we don't have hard data to prove it, we are convinced that a big source of CoW invalidation are inline caches. Most ISeq are never invoked during initialization, so child processed are forked with mostly cold caches. As a result the first time a method is executed in the child, many memory pages holding ISeq are invalidated as caches get updated.

We think MRI could try to precompute these caches before forking children. Constant cache particularly should be resolvable statically see https://github.com/ruby/ruby/pull/6187.

Method caches are harder to resolve statically, but we can probably apply some heuristics to at least reduce the cache misses.

#### Copy on Write aware GC

We could also keep some metadata about which memory pages are shared, or even introduce a "permanent" generation. [The Instagram engineering team introduced something like that in Python](https://instagram-engineering.com/copy-on-write-friendly-python-garbage-collection-ad6ed5233ddf) ([ticket](https://bugs.python.org/issue31558), [PR](https://github.com/python/cpython/pull/3705)).

That makes the GC aware of which objects live on a shared page. With this information the GC can decide to no free dangling objects leaving on these pages, not to compact these pages, etc.

#### Scan the coderange of all strings

Strings have a lazily computed `coderange` attribute in their flags. So if a string is allocated at boot, but only used after fork, on first use its coderange will mayneed to be computed and the string mutated.

Using https://github.com/ruby/ruby/pull/6076, I noticed that 58% of the strings retained at the end of the boot sequence had an `UNKNOWN` coderange.

So eagerly scanning the coderange of all strings could also improve Copy on Write performance.

#### malloc_trim

This hook will also be a good point to release unused pages to the system with `malloc_trim`.



-- 
https://bugs.ruby-lang.org/

  parent reply	other threads:[~2022-08-10 18:24 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-06-28 13:21 [ruby-core:109081] [Ruby master Feature#18885] Long lived fork advisory API (potential Copy on Write optimizations) byroot (Jean Boussier)
2022-06-30  9:27 ` [ruby-core:109098] " byroot (Jean Boussier)
2022-07-16  3:19 ` [ruby-core:109227] " ioquatix (Samuel Williams)
2022-07-27 16:55 ` [ruby-core:109339] " byroot (Jean Boussier)
2022-07-30  2:33 ` [ruby-core:109380] " Dan0042 (Daniel DeLorme)
2022-07-30  6:19 ` [ruby-core:109381] " byroot (Jean Boussier)
2022-08-02  8:56 ` [ruby-core:109409] " mame (Yusuke Endoh)
2022-08-02  9:03 ` [ruby-core:109410] " byroot (Jean Boussier)
2022-08-03  1:32 ` [ruby-core:109417] " mame (Yusuke Endoh)
2022-08-03  6:42 ` [ruby-core:109420] " byroot (Jean Boussier)
2022-08-03  7:10 ` [ruby-core:109421] [Ruby master Feature#18885] End of boot advisory API for RubyVM byroot (Jean Boussier)
2022-08-10 18:21 ` [ruby-core:109469] " Dan0042 (Daniel DeLorme)
2022-08-10 18:24 ` byroot (Jean Boussier) [this message]
2022-08-18  6:51 ` [ruby-core:109528] " matz (Yukihiro Matsumoto)
2022-08-18  6:55 ` [ruby-core:109529] " byroot (Jean Boussier)
2022-08-18  7:14 ` [ruby-core:109531] " Eregon (Benoit Daloze)
2022-08-18  7:16 ` [ruby-core:109533] " byroot (Jean Boussier)
2022-09-15 13:16 ` [ruby-core:109901] " byroot (Jean Boussier)
2022-09-22  5:52 ` [ruby-core:109989] " ioquatix (Samuel Williams)
2022-09-23 12:57 ` [ruby-core:110045] " Dan0042 (Daniel DeLorme)
2022-10-07 14:38 ` [ruby-core:110231] " matz (Yukihiro Matsumoto)
2022-10-07 15:05 ` [ruby-core:110232] " byroot (Jean Boussier)
2023-04-13  7:21 ` [ruby-core:113213] " ioquatix (Samuel Williams) via ruby-core

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-list from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.ruby-lang.org/en/community/mailing-lists/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=redmine.journal-98631.20220810182454.7941@ruby-lang.org \
    --to=ruby-core@ruby-lang.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).