Skip to content

Commit 97d9fed

Browse files
authored
Madvise Transparent Huge Pages for large allocations (#59858)
To get things going with #56521 I've made a minimal implementation that mirrors one from numpy (https://github.com/numpy/numpy/blob/7c0e2e4224c6feb04a2ac4aa851f49a2c2f6189f/numpy/_core/src/multiarray/alloc.c#L113). What this does: changes `jl_gc_managed_malloc(size_t sz)` to check whether requested allocation is big enough to benefit from huge pages. And if so, we ensure the allocation is page aligned and then appropriate `madvise` is called on the memory pointer. For a simple "fill memory" test I see around 2x timing improvement. ```julia function f(N) mem = Memory{Int}(undef, N) mem .= 0 mem[end] end f(1) @time f(1_000_000) ``` ``` 0.001464 seconds (2 allocations: 7.633 MiB) # this branch 0.003431 seconds (2 allocations: 7.633 MiB) # master ``` I would appreciate help with this PR as I have no experience writing C code and little knowledge of julia internals. In particular I think it would make sense to have a startup option controlling minimal eligible allocation size which should default to system's hugepage size - for this initial implementation the same constant as in numpy is hardcoded. --------- Co-authored-by: Artem Solod <>
1 parent d6f2a7e commit 97d9fed

File tree

7 files changed

+61
-1
lines changed

7 files changed

+61
-1
lines changed

src/gc-common.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
#include "julia_internal.h"
88
#ifndef _OS_WINDOWS_
99
#include <sys/mman.h>
10+
#ifndef MADV_HUGEPAGE
11+
#define MADV_HUGEPAGE 14 // Compatibility for kernels < 2.6.38 (as in numpy implementation)
12+
#endif
1013
#if defined(_OS_DARWIN_) && !defined(MAP_ANONYMOUS)
1114
#define MAP_ANONYMOUS MAP_ANON
1215
#endif
@@ -135,6 +138,7 @@ STATIC_INLINE void jl_free_aligned(void *p) JL_NOTSAFEPOINT
135138
#endif
136139
#define malloc_cache_align(sz) jl_malloc_aligned(sz, JL_CACHE_BYTE_ALIGNMENT)
137140
#define realloc_cache_align(p, sz, oldsz) jl_realloc_aligned(p, sz, oldsz, JL_CACHE_BYTE_ALIGNMENT)
141+
#define malloc_page_align(sz) jl_malloc_aligned(sz, jl_getpagesize())
138142

139143
// =========================================================================== //
140144
// Pointer tagging

src/gc-stock.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3854,7 +3854,22 @@ JL_DLLEXPORT void *jl_gc_managed_malloc(size_t sz)
38543854
#ifdef _OS_WINDOWS_
38553855
DWORD last_error = GetLastError();
38563856
#endif
3857-
void *b = malloc_cache_align(allocsz);
3857+
#ifdef MADV_HUGEPAGE
3858+
void *b = NULL;
3859+
if (allocsz >= 1u<<18u) {
3860+
b = malloc_page_align(allocsz);
3861+
if (jl_hugepage_size > 0) {
3862+
size_t leftover = jl_hugepage_size - (allocsz % jl_hugepage_size);
3863+
if ((leftover <= allocsz / 4) || (leftover == jl_hugepage_size)) // limit fragmentation
3864+
madvise(b, allocsz, MADV_HUGEPAGE);
3865+
}
3866+
}
3867+
else {
3868+
b = malloc_cache_align(allocsz);
3869+
}
3870+
#else
3871+
void *b = malloc_cache_align(allocsz);
3872+
#endif
38583873
if (b == NULL)
38593874
jl_throw(jl_memory_exception);
38603875

src/init.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ extern void jl_fin_stackwalk(void);
4848
jl_array_t *jl_module_init_order;
4949

5050
JL_DLLEXPORT size_t jl_page_size;
51+
JL_DLLEXPORT size_t jl_hugepage_size;
5152

5253
void jl_init_stack_limits(int ismaster, void **stack_lo, void **stack_hi)
5354
{
@@ -715,6 +716,7 @@ JL_DLLEXPORT void jl_init_(jl_image_buf_t sysimage)
715716
libsupport_init();
716717
jl_safepoint_init();
717718
jl_page_size = jl_getpagesize();
719+
jl_hugepage_size = (size_t)jl_gethugepagesize();
718720
htable_new(&jl_current_modules, 0);
719721
init_global_mutexes();
720722
jl_precompile_toplevel_module = NULL;

src/jl_exported_funcs.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@
182182
XX(jl_gensym) \
183183
XX(jl_getaffinity) \
184184
XX(jl_getallocationgranularity) \
185+
XX(jl_gethugepagesize) \
185186
XX(jl_getnameinfo) \
186187
XX(jl_getpagesize) \
187188
XX(jl_get_ARCH) \

src/julia.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2066,6 +2066,7 @@ JL_DLLEXPORT int jl_cpu_threads(void) JL_NOTSAFEPOINT;
20662066
JL_DLLEXPORT int jl_effective_threads(void) JL_NOTSAFEPOINT;
20672067
JL_DLLEXPORT long jl_getpagesize(void) JL_NOTSAFEPOINT;
20682068
JL_DLLEXPORT long jl_getallocationgranularity(void) JL_NOTSAFEPOINT;
2069+
JL_DLLEXPORT long jl_gethugepagesize(void) JL_NOTSAFEPOINT;
20692070
JL_DLLEXPORT int jl_is_debugbuild(void) JL_NOTSAFEPOINT;
20702071
JL_DLLEXPORT jl_sym_t *jl_get_UNAME(void) JL_NOTSAFEPOINT;
20712072
JL_DLLEXPORT jl_sym_t *jl_get_ARCH(void) JL_NOTSAFEPOINT;

src/julia_internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,7 @@ extern arraylist_t eytzinger_image_tree;
440440
extern arraylist_t eytzinger_idxs;
441441

442442
extern JL_DLLEXPORT size_t jl_page_size;
443+
extern JL_DLLEXPORT size_t jl_hugepage_size;
443444
extern JL_DLLEXPORT jl_value_t *jl_typeinf_func JL_GLOBALLY_ROOTED;
444445
extern JL_DLLEXPORT jl_value_t *jl_compile_and_emit_func JL_GLOBALLY_ROOTED;
445446
extern JL_DLLEXPORT size_t jl_typeinf_world;

src/sys.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <errno.h>
1111
#include <signal.h>
1212
#include <fcntl.h>
13+
#include <stdio.h>
1314

1415
#include "julia.h"
1516
#include "julia_internal.h"
@@ -610,6 +611,41 @@ JL_DLLEXPORT long jl_getallocationgranularity(void) JL_NOTSAFEPOINT
610611
}
611612
#endif
612613

614+
JL_DLLEXPORT long jl_gethugepagesize(void) JL_NOTSAFEPOINT
615+
{
616+
#if defined(_OS_LINUX_)
617+
long detected = 0;
618+
FILE *f = fopen("/sys/kernel/mm/transparent_hugepage/hpage_pmd_size", "r");
619+
if (f) {
620+
unsigned long long size = 0;
621+
if (fscanf(f, "%llu", &size) == 1 && size > 0) {
622+
detected = (long)size;
623+
}
624+
fclose(f);
625+
}
626+
if (detected == 0) {
627+
f = fopen("/proc/meminfo", "r");
628+
if (f) {
629+
char line[256];
630+
while (fgets(line, sizeof(line), f)) {
631+
unsigned long long kb = 0;
632+
if (sscanf(line, "Hugepagesize:%llu kB", &kb) == 1 && kb > 0) {
633+
detected = (long)(kb * 1024ULL);
634+
break;
635+
}
636+
}
637+
fclose(f);
638+
}
639+
}
640+
if (detected == 0) {
641+
detected = 2 * 1024 * 1024; // 2 MiB fallback
642+
}
643+
return detected;
644+
#else
645+
return 0;
646+
#endif
647+
}
648+
613649
JL_DLLEXPORT long jl_SC_CLK_TCK(void)
614650
{
615651
#ifndef _OS_WINDOWS_

0 commit comments

Comments
 (0)