Skip to content

Commit 1fe2358

Browse files
committed
System image compression with zstd
Co-authored-by: Gabriel Baraldi <[email protected]> madvise with MADV_WILLNEED before accessing sysimage pages MADV_WILLNEED sysimage pages before checksumming or decompressing Even without compression, this gives about an 8% improvement in load times. Fix using jl_page_size before it is initialized Add zstd to ANALYSIS_DEPS now that it is used in staticdata.c Fix jl_page_size being uninitialized in jl_prefetch_system_image Add --compress-sysimage flag; use zstd-15 when enabled
1 parent a372fbc commit 1fe2358

File tree

8 files changed

+154
-41
lines changed

8 files changed

+154
-41
lines changed

Make.inc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1406,7 +1406,7 @@ JLDFLAGS += -Wl,--stack,8388608 --disable-auto-import --disable-runtime-pseudo-r
14061406
ifeq ($(ARCH),i686)
14071407
JLDFLAGS += -Wl,--large-address-aware
14081408
endif
1409-
JCPPFLAGS += -D_WIN32_WINNT=0x0502
1409+
JCPPFLAGS += -D_WIN32_WINNT=_WIN32_WINNT_WIN8
14101410
UNTRUSTED_SYSTEM_LIBM := 1
14111411
# Use hard links for files on windows, rather than soft links
14121412
# https://stackoverflow.com/questions/3648819/how-to-make-a-symbolic-link-with-cygwin-in-windows-7

base/options.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ struct JLOptions
5858
strip_ir::Int8
5959
permalloc_pkgimg::Int8
6060
heap_size_hint::UInt64
61+
compress_sysimage::Int8
6162
end
6263

6364
# This runs early in the sysimage != is not defined yet

base/util.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,9 @@ function julia_cmd(julia=joinpath(Sys.BINDIR, julia_exename()); cpu_target::Unio
245245
if opts.use_sysimage_native_code == 0
246246
push!(addflags, "--sysimage-native-code=no")
247247
end
248+
if opts.compress_sysimage == 1
249+
push!(addflags, "--compress-sysimage=yes")
250+
end
248251
return `$julia -C $cpu_target -J$image_file $addflags`
249252
end
250253

src/Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ endif
171171

172172
COMMON_LIBPATHS := -L$(build_libdir) -L$(build_shlibdir)
173173
RT_LIBS := $(WHOLE_ARCHIVE) $(LIBUV) $(WHOLE_ARCHIVE) $(LIBUTF8PROC) $(NO_WHOLE_ARCHIVE) $(LIBUNWIND) $(RT_LLVMLINK) $(OSLIBS) $(LIBTRACYCLIENT) $(LIBITTAPI) $(LIBZSTD)
174-
CG_LIBS := $(LIBUNWIND) $(CG_LLVMLINK) $(OSLIBS) $(LIBTRACYCLIENT) $(LIBITTAPI)
174+
CG_LIBS := $(LIBUNWIND) $(CG_LLVMLINK) $(OSLIBS) $(LIBTRACYCLIENT) $(LIBITTAPI) $(LIBZSTD)
175175
RT_DEBUG_LIBS := $(COMMON_LIBPATHS) $(WHOLE_ARCHIVE) $(BUILDDIR)/flisp/libflisp-debug.a $(WHOLE_ARCHIVE) $(BUILDDIR)/support/libsupport-debug.a -ljulia-debug $(RT_LIBS)
176176
CG_DEBUG_LIBS := $(COMMON_LIBPATHS) $(CG_LIBS) -ljulia-debug -ljulia-internal-debug
177177
RT_RELEASE_LIBS := $(COMMON_LIBPATHS) $(WHOLE_ARCHIVE) $(BUILDDIR)/flisp/libflisp.a $(WHOLE_ARCHIVE) $(BUILDDIR)/support/libsupport.a -ljulia $(RT_LIBS)
@@ -474,7 +474,7 @@ $(build_shlibdir)/lib%Plugin.$(SHLIB_EXT): $(SRCDIR)/clangsa/%.cpp $(LLVM_CONFIG
474474
# before attempting this static analysis, so that all necessary headers
475475
# and dependencies are properly installed:
476476
# make -C src install-analysis-deps
477-
ANALYSIS_DEPS := llvm clang llvm-tools libuv utf8proc
477+
ANALYSIS_DEPS := llvm clang llvm-tools libuv utf8proc zstd
478478
ifeq ($(OS),Darwin)
479479
ANALYSIS_DEPS += llvmunwind
480480
else ifneq ($(OS),WINNT)

src/aotcompile.cpp

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@
5757
#include <llvm/Support/FormatAdapters.h>
5858
#include <llvm/Linker/Linker.h>
5959

60-
6160
using namespace llvm;
6261

62+
#include <zstd.h>
63+
6364
#include "jitlayers.h"
6465
#include "serialize.h"
6566
#include "julia_assert.h"
@@ -1659,27 +1660,53 @@ void jl_dump_native_impl(void *native_code,
16591660
sysimgM.setDataLayout(DL);
16601661
sysimgM.setStackProtectorGuard(StackProtectorGuard);
16611662
sysimgM.setOverrideStackAlignment(OverrideStackAlignment);
1662-
Constant *data = ConstantDataArray::get(Context,
1663-
ArrayRef<uint8_t>((const unsigned char*)z->buf, z->size));
1663+
1664+
int compression = jl_options.compress_sysimage ? 15 : 0;
1665+
ArrayRef<char> sysimg_data{z->buf, (size_t)z->size};
1666+
SmallVector<char, 0> compressed_data;
1667+
if (compression) {
1668+
compressed_data.resize(ZSTD_compressBound(z->size));
1669+
size_t comp_size = ZSTD_compress(compressed_data.data(), compressed_data.size(),
1670+
z->buf, z->size, compression);
1671+
compressed_data.resize(comp_size);
1672+
sysimg_data = compressed_data;
1673+
ios_close(z);
1674+
free(z);
1675+
}
1676+
1677+
Constant *data = ConstantDataArray::get(Context, sysimg_data);
16641678
auto sysdata = new GlobalVariable(sysimgM, data->getType(), false,
16651679
GlobalVariable::ExternalLinkage,
16661680
data, "jl_system_image_data");
1667-
sysdata->setAlignment(Align(64));
1681+
sysdata->setAlignment(Align(jl_page_size));
16681682
#if JL_LLVM_VERSION >= 180000
16691683
sysdata->setCodeModel(CodeModel::Large);
16701684
#else
16711685
if (TheTriple.isX86() && TheTriple.isArch64Bit() && TheTriple.isOSLinux())
16721686
sysdata->setSection(".ldata");
16731687
#endif
16741688
addComdat(sysdata, TheTriple);
1675-
Constant *len = ConstantInt::get(sysimgM.getDataLayout().getIntPtrType(Context), z->size);
1689+
Constant *len = ConstantInt::get(sysimgM.getDataLayout().getIntPtrType(Context), sysimg_data.size());
16761690
addComdat(new GlobalVariable(sysimgM, len->getType(), true,
16771691
GlobalVariable::ExternalLinkage,
16781692
len, "jl_system_image_size"), TheTriple);
1679-
// Free z here, since we've copied out everything into data
1680-
// Results in serious memory savings
1681-
ios_close(z);
1682-
free(z);
1693+
1694+
const char *unpack_func = compression ? "jl_image_unpack_zstd" : "jl_image_unpack_uncomp";
1695+
auto unpack = new GlobalVariable(sysimgM, DL.getIntPtrType(Context), true,
1696+
GlobalVariable::ExternalLinkage, nullptr,
1697+
unpack_func);
1698+
addComdat(new GlobalVariable(sysimgM, PointerType::getUnqual(Context), true,
1699+
GlobalVariable::ExternalLinkage, unpack,
1700+
"jl_image_unpack"),
1701+
TheTriple);
1702+
1703+
if (!compression) {
1704+
// Free z here, since we've copied out everything into data
1705+
// Results in serious memory savings
1706+
ios_close(z);
1707+
free(z);
1708+
}
1709+
compressed_data.clear();
16831710
// Note that we don't set z to null, this allows the check in WRITE_ARCHIVE
16841711
// to function as expected
16851712
// no need to free the module/context, destructor handles that

src/jloptions.c

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ JL_DLLEXPORT void jl_init_options(void)
101101
0, // strip-ir
102102
0, // permalloc_pkgimg
103103
0, // heap-size-hint
104+
0, // compress_sysimage
104105
};
105106
jl_options_initialized = 1;
106107
}
@@ -222,10 +223,13 @@ static const char opts_hidden[] =
222223
" Enable or disable JIT compiler, or request exhaustive or minimal compilation\n\n"
223224

224225
// compiler output options
225-
" --output-o <name> Generate an object file (including system image data)\n"
226-
" --output-ji <name> Generate a system image data file (.ji)\n"
227-
" --strip-metadata Remove docstrings and source location info from system image\n"
228-
" --strip-ir Remove IR (intermediate representation) of compiled functions\n\n"
226+
" --output-o <name> Generate an object file (including system image data)\n"
227+
" --output-ji <name> Generate a system image data file (.ji)\n"
228+
" --strip-metadata Remove docstrings and source location info from system image\n"
229+
" --strip-ir Remove IR (intermediate representation) of compiled functions\n"
230+
" --compress-sysimage={yes|no*} Compress the sys/pkgimage heap at the expense of\n"
231+
" slightly increased load time.\n"
232+
"\n"
229233

230234
// compiler debugging (see the devdocs for tips on using these options)
231235
" --output-unopt-bc <name> Generate unoptimized LLVM bitcode (.bc)\n"
@@ -282,7 +286,8 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
282286
opt_strip_ir,
283287
opt_heap_size_hint,
284288
opt_gc_threads,
285-
opt_permalloc_pkgimg
289+
opt_permalloc_pkgimg,
290+
opt_compress_sysimage,
286291
};
287292
static const char* const shortopts = "+vhqH:e:E:L:J:C:it:p:O:g:";
288293
static const struct option longopts[] = {
@@ -344,6 +349,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
344349
{ "strip-ir", no_argument, 0, opt_strip_ir },
345350
{ "permalloc-pkgimg",required_argument, 0, opt_permalloc_pkgimg },
346351
{ "heap-size-hint", required_argument, 0, opt_heap_size_hint },
352+
{ "compress-sysimage", required_argument, 0, opt_compress_sysimage },
347353
{ 0, 0, 0, 0 }
348354
};
349355

@@ -895,6 +901,12 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
895901
else
896902
jl_errorf("julia: invalid argument to --permalloc-pkgimg={yes|no} (%s)", optarg);
897903
break;
904+
case opt_compress_sysimage:
905+
if (!strcmp(optarg,"yes"))
906+
jl_options.compress_sysimage = 1;
907+
else if (!strcmp(optarg,"no"))
908+
jl_options.compress_sysimage = 0;
909+
break;
898910
default:
899911
jl_errorf("julia: unhandled option -- %c\n"
900912
"This is a bug, please report it.", c);

src/jloptions.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ typedef struct {
6262
int8_t strip_ir;
6363
int8_t permalloc_pkgimg;
6464
uint64_t heap_size_hint;
65+
int8_t compress_sysimage;
6566
} jl_options_t;
6667

6768
#endif

src/staticdata.c

Lines changed: 93 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,20 @@ External links:
7474
#include <stdio.h> // printf
7575
#include <inttypes.h> // PRIxPTR
7676

77+
#include <zstd.h>
78+
7779
#include "julia.h"
7880
#include "julia_internal.h"
7981
#include "julia_gcext.h"
8082
#include "builtin_proto.h"
8183
#include "processor.h"
8284
#include "serialize.h"
8385

84-
#ifndef _OS_WINDOWS_
86+
#ifdef _OS_WINDOWS_
87+
#include <memoryapi.h>
88+
#else
8589
#include <dlfcn.h>
90+
#include <sys/mman.h>
8691
#endif
8792

8893
#include "valgrind.h"
@@ -3080,14 +3085,75 @@ JL_DLLEXPORT jl_image_buf_t jl_preload_sysimg(const char *fname)
30803085
}
30813086
}
30823087

3083-
// From a shared library handle, verify consistency and return a jl_image_buf_t
3084-
static jl_image_buf_t get_image_buf(void *handle, int is_pkgimage)
3088+
typedef void jl_image_unpack_func_t(void *handle, jl_image_buf_t *image);
3089+
3090+
static void jl_prefetch_system_image(const char *data, size_t size)
3091+
{
3092+
size_t page_size = jl_getpagesize(); /* jl_page_size is not set yet when loading sysimg */
3093+
void *start = (void *)((uintptr_t)data & ~(page_size - 1));
3094+
size_t size_aligned = LLT_ALIGN(size, page_size);
3095+
#ifdef _OS_WINDOWS_
3096+
WIN32_MEMORY_RANGE_ENTRY entry = {start, size_aligned};
3097+
PrefetchVirtualMemory(GetCurrentProcess(), 1, &entry, 0);
3098+
#else
3099+
madvise(start, size_aligned, MADV_WILLNEED);
3100+
#endif
3101+
}
3102+
3103+
JL_DLLEXPORT void jl_image_unpack_uncomp(void *handle, jl_image_buf_t *image)
3104+
{
3105+
size_t *plen;
3106+
jl_dlsym(handle, "jl_system_image_size", (void **)&plen, 1);
3107+
jl_dlsym(handle, "jl_system_image_data", (void **)&image->data, 1);
3108+
jl_dlsym(handle, "jl_image_pointers", (void**)&image->pointers, 1);
3109+
image->size = *plen;
3110+
jl_prefetch_system_image(image->data, image->size);
3111+
}
3112+
3113+
JL_DLLEXPORT void jl_image_unpack_zstd(void *handle, jl_image_buf_t *image)
30853114
{
30863115
size_t *plen;
30873116
const char *data;
3088-
const void *pointers;
3089-
uint64_t base;
3117+
jl_dlsym(handle, "jl_system_image_size", (void **)&plen, 1);
3118+
jl_dlsym(handle, "jl_system_image_data", (void **)&data, 1);
3119+
jl_dlsym(handle, "jl_image_pointers", (void **)&image->pointers, 1);
3120+
jl_prefetch_system_image(data, *plen);
3121+
image->size = ZSTD_getFrameContentSize(data, *plen);
3122+
size_t page_size = jl_getpagesize(); /* jl_page_size is not set yet when loading sysimg */
3123+
size_t aligned_size = LLT_ALIGN(image->size, page_size);
3124+
#if defined(_OS_WINDOWS_)
3125+
size_t large_page_size = GetLargePageMinimum();
3126+
if (image->size > 4 * large_page_size) {
3127+
size_t aligned_size = LLT_ALIGN(image->size, large_page_size);
3128+
image->data = (char *)VirtualAlloc(
3129+
NULL, aligned_size, MEM_COMMIT | MEM_RESERVE | MEM_LARGE_PAGES, PAGE_READWRITE);
3130+
}
3131+
else {
3132+
image->data = (char *)VirtualAlloc(NULL, aligned_size, MEM_COMMIT | MEM_RESERVE,
3133+
PAGE_READWRITE);
3134+
}
3135+
#else
3136+
image->data =
3137+
(char *)mmap(NULL, aligned_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
3138+
#endif
3139+
if (!image->data || image->data == (void *)-1) {
3140+
jl_printf(JL_STDERR, "ERROR: failed to allocate space for system image\n");
3141+
jl_exit(1);
3142+
}
3143+
3144+
ZSTD_decompress((void *)image->data, image->size, data, *plen);
3145+
size_t len = (*plen) & ~(page_size - 1);
3146+
#ifdef _OS_WINDOWS_
3147+
if (len)
3148+
VirtualFree((void *)data, len, MEM_RELEASE);
3149+
#else
3150+
munmap((void *)data, len);
3151+
#endif
3152+
}
30903153

3154+
// From a shared library handle, verify consistency and return a jl_image_buf_t
3155+
static jl_image_buf_t get_image_buf(void *handle, int is_pkgimage)
3156+
{
30913157
// verify that the linker resolved the symbols in this image against ourselves (libjulia-internal)
30923158
void** (*get_jl_RTLD_DEFAULT_handle_addr)(void) = NULL;
30933159
if (handle != jl_RTLD_DEFAULT_handle) {
@@ -3096,39 +3162,42 @@ static jl_image_buf_t get_image_buf(void *handle, int is_pkgimage)
30963162
jl_error("Image file failed consistency check: maybe opened the wrong version?");
30973163
}
30983164

3165+
jl_image_unpack_func_t **unpack;
3166+
jl_image_buf_t image = {
3167+
.kind = JL_IMAGE_KIND_SO,
3168+
.handle = handle,
3169+
.pointers = NULL,
3170+
.data = NULL,
3171+
.size = 0,
3172+
.base = 0,
3173+
};
3174+
30993175
// verification passed, lookup the buffer pointers
31003176
if (jl_system_image_size == 0 || is_pkgimage) {
31013177
// in the usual case, the sysimage was not statically linked to libjulia-internal
31023178
// look up the external sysimage symbols via the dynamic linker
3103-
jl_dlsym(handle, "jl_system_image_size", (void **)&plen, 1);
3104-
jl_dlsym(handle, "jl_system_image_data", (void **)&data, 1);
3105-
jl_dlsym(handle, "jl_image_pointers", (void**)&pointers, 1);
3106-
} else {
3179+
jl_dlsym(handle, "jl_image_unpack", (void **)&unpack, 1);
3180+
(*unpack)(handle, &image);
3181+
}
3182+
else {
31073183
// the sysimage was statically linked directly against libjulia-internal
31083184
// use the internal symbols
3109-
plen = &jl_system_image_size;
3110-
pointers = &jl_image_pointers;
3111-
data = &jl_system_image_data;
3185+
image.size = jl_system_image_size;
3186+
image.pointers = &jl_image_pointers;
3187+
image.data = &jl_system_image_data;
31123188
}
31133189

31143190
#ifdef _OS_WINDOWS_
3115-
base = (intptr_t)handle;
3191+
image.base = (intptr_t)handle;
31163192
#else
31173193
Dl_info dlinfo;
3118-
if (dladdr((void*)pointers, &dlinfo) != 0)
3119-
base = (intptr_t)dlinfo.dli_fbase;
3194+
if (dladdr((void*)image.pointers, &dlinfo) != 0)
3195+
image.base = (intptr_t)dlinfo.dli_fbase;
31203196
else
3121-
base = 0;
3197+
image.base = 0;
31223198
#endif
31233199

3124-
return (jl_image_buf_t) {
3125-
.kind = JL_IMAGE_KIND_SO,
3126-
.handle = handle,
3127-
.pointers = pointers,
3128-
.data = data,
3129-
.size = *plen,
3130-
.base = base,
3131-
};
3200+
return image;
31323201
}
31333202

31343203
// Allow passing in a module handle directly, rather than a path

0 commit comments

Comments
 (0)