[前][次][番号順一覧][スレッド一覧]

ruby-changes:60963

From: Takashi <ko1@a...>
Date: Fri, 1 May 2020 17:40:03 +0900 (JST)
Subject: [ruby-changes:60963] 818d6d3336 (master): Deduplicate functions in compacted JIT code

https://git.ruby-lang.org/ruby.git/commit/?id=818d6d3336

From 818d6d33368a396d9cd3d1a34a84015a9e76c5c8 Mon Sep 17 00:00:00 2001
From: Takashi Kokubun <takashikkbn@g...>
Date: Thu, 30 Apr 2020 23:58:50 -0700
Subject: Deduplicate functions in compacted JIT code

to improve code locality.

Using benchmark-driver/sinatra with 100 methods JIT-ed,

[Before] 12149.97 rps
1.3M    /tmp/_ruby_mjit_p31171u145.so

[After] 12818.83 rps
260K    /tmp/_ruby_mjit_p32155u145.so

(VM is 13714.89 rps)

diff --git a/mjit.c b/mjit.c
index 59e58f3..2c2af0a 100644
--- a/mjit.c
+++ b/mjit.c
@@ -191,7 +191,7 @@ free_list(struct rb_mjit_unit_list *list, bool close_handle_p) https://github.com/ruby/ruby/blob/trunk/mjit.c#L191
             if (unit->handle && dlclose(unit->handle)) {
                 mjit_warning("failed to close handle for u%d: %s", unit->id, dlerror());
             }
-            clean_object_files(unit);
+            clean_temp_files(unit);
             free(unit);
         }
         else {
@@ -889,7 +889,7 @@ skip_cleaning_object_files(struct rb_mjit_unit_list *list) https://github.com/ruby/ruby/blob/trunk/mjit.c#L889
     // No mutex for list, assuming MJIT worker does not exist yet since it's immediately after fork.
     list_for_each_safe(&list->head, unit, next, unode) {
 #ifndef _MSC_VER // Actually mswin does not reach here since it doesn't have fork
-        if (unit->o_file) unit->o_file_inherited_p = true;
+        if (unit->c_file) unit->c_file_inherited_p = true;
 #endif
 
 #if defined(_WIN32) // mswin doesn't reach here either. This is for MinGW.
diff --git a/mjit_worker.c b/mjit_worker.c
index 791a6ab..b64c769 100644
--- a/mjit_worker.c
+++ b/mjit_worker.c
@@ -146,11 +146,11 @@ struct rb_mjit_unit { https://github.com/ruby/ruby/blob/trunk/mjit_worker.c#L146
     rb_iseq_t *iseq;
 #ifndef _MSC_VER
     // This value is always set for `compact_all_jit_code`. Also used for lazy deletion.
-    char *o_file;
+    char *c_file;
     // true if it's inherited from parent Ruby process and lazy deletion should be skipped.
-    // `o_file = NULL` can't be used to skip lazy deletion because `o_file` could be used
+    // `c_file = NULL` can't be used to skip lazy deletion because `c_file` could be used
     // by child for `compact_all_jit_code`.
-    bool o_file_inherited_p;
+    bool c_file_inherited_p;
 #endif
 #if defined(_WIN32)
     // DLL cannot be removed while loaded on Windows. If this is set, it'll be lazily deleted.
@@ -388,20 +388,20 @@ remove_file(const char *filename) https://github.com/ruby/ruby/blob/trunk/mjit_worker.c#L388
     }
 }
 
-// Lazily delete .o and/or .so files.
+// Lazily delete .c and/or .so files.
 static void
-clean_object_files(struct rb_mjit_unit *unit)
+clean_temp_files(struct rb_mjit_unit *unit)
 {
 #ifndef _MSC_VER
-    if (unit->o_file) {
-        char *o_file = unit->o_file;
+    if (unit->c_file) {
+        char *c_file = unit->c_file;
 
-        unit->o_file = NULL;
-        // For compaction, unit->o_file is always set when compilation succeeds.
+        unit->c_file = NULL;
+        // For compaction, unit->c_file is always set when compilation succeeds.
         // So save_temps needs to be checked here.
-        if (!mjit_opts.save_temps && !unit->o_file_inherited_p)
-            remove_file(o_file);
-        free(o_file);
+        if (!mjit_opts.save_temps && !unit->c_file_inherited_p)
+            remove_file(c_file);
+        free(c_file);
     }
 #endif
 
@@ -439,7 +439,7 @@ free_unit(struct rb_mjit_unit *unit) https://github.com/ruby/ruby/blob/trunk/mjit_worker.c#L439
     if (unit->handle && dlclose(unit->handle)) { // handle is NULL if it's in queue
         mjit_warning("failed to close handle for u%d: %s", unit->id, dlerror());
     }
-    clean_object_files(unit);
+    clean_temp_files(unit);
     free(unit);
 }
 
@@ -683,7 +683,7 @@ remove_so_file(const char *so_file, struct rb_mjit_unit *unit) https://github.com/ruby/ruby/blob/trunk/mjit_worker.c#L683
 {
 #if defined(_WIN32)
     // Windows can't remove files while it's used.
-    unit->so_file = strdup(so_file); // lazily delete on `clean_object_files()`
+    unit->so_file = strdup(so_file); // lazily delete on `clean_temp_files()`
     if (unit->so_file == NULL)
         mjit_warning("failed to allocate memory to lazily remove '%s': %s", so_file, strerror(errno));
 #else
@@ -844,19 +844,23 @@ make_pch(void) https://github.com/ruby/ruby/blob/trunk/mjit_worker.c#L844
     CRITICAL_SECTION_FINISH(3, "in make_pch");
 }
 
-// Compile .c file to .o file. It returns true if it succeeds. (non-mswin)
+// Compile .c file to .so file. It returns true if it succeeds. (non-mswin)
 static bool
-compile_c_to_o(const char *c_file, const char *o_file)
+compile_c_to_so(const char *c_file, const char *so_file)
 {
-    const char *files[] = {
-        "-o", o_file, c_file,
+    const char *options[] = {
+        "-o", so_file, c_file,
 # ifdef __clang__
         "-include-pch", pch_file,
 # endif
-        "-c", NULL
+# ifdef _WIN32
+        libruby_pathflag,
+# endif
+        NULL
     };
 
-    char **args = form_args(5, cc_common_args, CC_CODEFLAG_ARGS, cc_added_args, files, CC_LINKER_ARGS);
+    char **args = form_args(6, CC_LDSHARED_ARGS, CC_CODEFLAG_ARGS,
+            options, CC_LIBS, CC_DLDFLAGS_ARGS, CC_LINKER_ARGS);
     if (args == NULL)
         return false;
 
@@ -864,47 +868,52 @@ compile_c_to_o(const char *c_file, const char *o_file) https://github.com/ruby/ruby/blob/trunk/mjit_worker.c#L868
     free(args);
 
     if (exit_code != 0)
-        verbose(2, "compile_c_to_o: compile error: %d", exit_code);
+        verbose(2, "compile_c_to_so: compile error: %d", exit_code);
     return exit_code == 0;
 }
 
-// Link .o files to .so file. It returns true if it succeeds. (non-mswin)
+static void compile_prelude(FILE *f);
+
+static const int c_file_access_mode =
+#ifdef O_BINARY
+    O_BINARY|
+#endif
+    O_WRONLY|O_EXCL|O_CREAT;
+
 static bool
-link_o_to_so(const char **o_files, const char *so_file)
+compile_compact_jit_code(char* c_file)
 {
-    const char *options[] = {
-        "-o", so_file,
-# ifdef _WIN32
-        libruby_pathflag,
-# endif
-        NULL
-    };
-
-    char **args = form_args(7, CC_LDSHARED_ARGS, CC_CODEFLAG_ARGS,
-            options, o_files, CC_LIBS, CC_DLDFLAGS_ARGS, CC_LINKER_ARGS);
-    if (args == NULL)
+    FILE *f;
+    int fd = rb_cloexec_open(c_file, c_file_access_mode, 0600);
+    if (fd < 0 || (f = fdopen(fd, "w")) == NULL) {
+        int e = errno;
+        if (fd >= 0) (void)close(fd);
+        verbose(1, "Failed to fopen '%s', giving up JIT for it (%s)", c_file, strerror(e));
         return false;
+    }
 
-    int exit_code = exec_process(cc_path, args);
-    free(args);
+    compile_prelude(f);
 
-    if (exit_code != 0)
-        verbose(2, "link_o_to_so: link error: %d", exit_code);
-    return exit_code == 0;
+    struct rb_mjit_unit *cur = 0;
+    list_for_each(&active_units.head, cur, unode) {
+        fprintf(f, "#include \"%s\"\n", cur->c_file);
+    }
+
+    fclose(f);
+    return true;
 }
 
-// Link all cached .o files and build a .so file. Reload all JIT func from it. This
-// allows to avoid JIT code fragmentation and improve performance to call JIT-ed code.
+// Compile all cached .c files and build a single .so file. Reload all JIT func from it.
+// This improves the code locality for better performance in terms of iTLB and iCache.
 static void
 compact_all_jit_code(void)
 {
 # ifndef _WIN32 // This requires header transformation but we don't transform header on Windows for now
     struct rb_mjit_unit *unit, *cur = 0;
     double start_time, end_time;
+    static const char c_ext[] = ".c";
     static const char so_ext[] = DLEXT;
-    char so_file[MAXPATHLEN];
-    const char **o_files;
-    int i = 0;
+    char c_file[MAXPATHLEN], so_file[MAXPATHLEN];
 
     // Abnormal use case of rb_mjit_unit that doesn't have ISeq
     unit = calloc(1, sizeof(struct rb_mjit_unit)); // To prevent GC, don't use ZALLOC
@@ -912,24 +921,23 @@ compact_all_jit_code(void) https://github.com/ruby/ruby/blob/trunk/mjit_worker.c#L921
     unit->id = current_unit_num++;
     sprint_uniq_filename(so_file, (int)sizeof(so_file), unit->id, MJIT_TMP_PREFIX, so_ext);
 
-    // NULL-ending for form_args
-    o_files = alloca(sizeof(char *) * (active_units.length + 1));
-    o_files[active_units.length] = NULL;
-    CRITICAL_SECTION_START(3, "in compact_all_jit_code to guard .o files from unload_units");
-    list_for_each(&active_units.head, cur, unode) {
-        o_files[i] = cur->o_file;
-        i++;
-    }
+    sprint_uniq_filename(c_file, (int)sizeof(c_file), unit->id, MJIT_TMP_PREFIX, c_ext);
+    CRITICAL_SECTION_START(3, "in compact_all_jit_code to guard .c files from unload_units");
+    bool success = compile_compact_jit_code(c_file);
     in_compact = true;
-    CRITICAL_SECTION_FINISH(3, "in compact_all_jit_code to guard .o files from unload_units");
+    CRITICAL_SECTION_FINISH(3, "in compact_all_jit_code to guard .c files from unload_units");
 
     start_time = real_ms_time();
-    bool success = link_o_to_so(o_files, so_file);
+    if (success) {
+        success = compile_c_to_so(c_file, so_file);
+        if (!mjit_opts.save_temps)
+            remove_file(c_file);
+    }
     end_time = real_ms_time();
 
-    CRITICAL_SECTION_START(3, "in compact_all_jit_code to release .o files");
+    CRITICAL_SECTION_START(3, "in compact_all_jit_code to release .c files");
     in_compact = false;
-    CRITICAL_SECTION_FINISH(3, "in compact_all_jit_code to release .o files");
+    CRITICAL_SECTION_FINISH(3, "in compact_all_jit_code to release .c files");
 
     if (success) {
         void *handle = dlopen(so_file, RTLD_NOW);
@@ -963,7 +971,7 @@ compact_all_jit_code(void) https://github.com/ruby/ruby/blob/trunk/mjit_worker.c#L971
             }
         }
         CRITICAL_SECTION_FINISH(3, "in compact_all_jit_code to read list");
-        verbose(1, "JIT compaction (%.1fms): Compacted %d methods -> %s", end_time - start_time, active_units. (... truncated)

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]