Cheng Shao pushed to branch wip/hugepages at Glasgow Haskell Compiler / GHC

Commits:

9 changed files:

Changes:

  • docs/users_guide/runtime_control.rst
    ... ... @@ -384,6 +384,16 @@ Miscellaneous RTS options
    384 384
         If given, instruct the runtime linker to try to continue linking in the
    
    385 385
         presence of an unresolved symbol.
    
    386 386
     
    
    387
    +.. rts-flag:: -xH
    
    388
    +
    
    389
    +    This option enables using huge pages to back memory allocations.
    
    390
    +    Use of huge pages can make memory lookups more efficient for applications
    
    391
    +    with high memory usage.
    
    392
    +    Currently we only support 2MB hugepages on Linux.
    
    393
    +
    
    394
    +    If huge pages aren't available to back allocations, then we fall back to
    
    395
    +    regular pages.
    
    396
    +
    
    387 397
     .. _rts-options-gc:
    
    388 398
     
    
    389 399
     RTS options to control the garbage collector
    

  • rts/RtsFlags.c
    ... ... @@ -182,6 +182,7 @@ void initRtsFlagsDefaults(void)
    182 182
         RtsFlags.GcFlags.allocLimitGrace    = (100*1024) / BLOCK_SIZE;
    
    183 183
         RtsFlags.GcFlags.numa               = false;
    
    184 184
         RtsFlags.GcFlags.numaMask           = 1;
    
    185
    +    RtsFlags.GcFlags.hugepages          = false;
    
    185 186
         RtsFlags.GcFlags.ringBell           = false;
    
    186 187
         RtsFlags.GcFlags.longGCSync         = 0; /* detection turned off */
    
    187 188
     
    
    ... ... @@ -570,7 +571,10 @@ usage_text[] = {
    570 571
     #endif
    
    571 572
     "  -xq        The allocation limit given to a thread after it receives",
    
    572 573
     "             an AllocationLimitExceeded exception. (default: 100k)",
    
    574
    +#if defined(HUGEPAGE_FLAGS)
    
    575
    +"  -xH        Try to use hugepages to allocate memory.",
    
    573 576
     "",
    
    577
    +#endif
    
    574 578
     #if defined(USE_LARGE_ADDRESS_SPACE)
    
    575 579
     "  -xr        The size of virtual memory address space reserved by the",
    
    576 580
     "             two step allocator (default: 1T)",
    
    ... ... @@ -1848,11 +1852,11 @@ error = true;
    1848 1852
                        */
    
    1849 1853
     
    
    1850 1854
                     case 'q':
    
    1851
    -                  OPTION_UNSAFE;
    
    1852
    -                  RtsFlags.GcFlags.allocLimitGrace
    
    1853
    -                      = decodeSize(rts_argv[arg], 3, BLOCK_SIZE, HS_INT_MAX)
    
    1854
    -                          / BLOCK_SIZE;
    
    1855
    -                  break;
    
    1855
    +                    OPTION_UNSAFE;
    
    1856
    +                    RtsFlags.GcFlags.allocLimitGrace
    
    1857
    +                        = decodeSize(rts_argv[arg], 3, BLOCK_SIZE, HS_INT_MAX)
    
    1858
    +                            / BLOCK_SIZE;
    
    1859
    +                    break;
    
    1856 1860
     
    
    1857 1861
                     case 'r':
    
    1858 1862
                         OPTION_UNSAFE;
    
    ... ... @@ -1860,7 +1864,16 @@ error = true;
    1860 1864
                           = decodeSize(rts_argv[arg], 3, MBLOCK_SIZE, HS_WORD64_MAX);
    
    1861 1865
                         break;
    
    1862 1866
     
    
    1863
    -                  default:
    
    1867
    +                case 'H':
    
    1868
    +                    OPTION_UNSAFE;
    
    1869
    +#if defined(HUGEPAGE_FLAGS)
    
    1870
    +                    RtsFlags.GcFlags.hugepages = true;
    
    1871
    +#else
    
    1872
    +                    errorBelch("Program not compiled with hugepages support.");
    
    1873
    +#endif
    
    1874
    +                    break;
    
    1875
    +
    
    1876
    +                default:
    
    1864 1877
                         OPTION_SAFE;
    
    1865 1878
                         errorBelch("unknown RTS option: %s",rts_argv[arg]);
    
    1866 1879
                         error = true;
    

  • rts/configure.ac
    ... ... @@ -96,7 +96,7 @@ dnl off_t, because it will affect the result of that test.
    96 96
     AC_SYS_LARGEFILE
    
    97 97
     
    
    98 98
     dnl ** check for specific header (.h) files that we are interested in
    
    99
    -AC_CHECK_HEADERS([ctype.h dlfcn.h errno.h fcntl.h limits.h locale.h nlist.h pthread.h signal.h sys/param.h sys/mman.h sys/resource.h sys/select.h sys/time.h sys/timeb.h sys/timerfd.h sys/timers.h sys/times.h sys/utsname.h sys/wait.h termios.h utime.h windows.h winsock.h sched.h])
    
    99
    +AC_CHECK_HEADERS([ctype.h dlfcn.h errno.h fcntl.h limits.h locale.h nlist.h pthread.h signal.h sys/param.h sys/mman.h linux/mman.h sys/resource.h sys/select.h sys/time.h sys/timeb.h sys/timerfd.h sys/timers.h sys/times.h sys/utsname.h sys/wait.h termios.h utime.h windows.h winsock.h sched.h])
    
    100 100
     
    
    101 101
     dnl sys/cpuset.h needs sys/param.h to be included first on FreeBSD 9.1; #7708
    
    102 102
     AC_CHECK_HEADERS([sys/cpuset.h], [], [],
    

  • rts/include/rts/Flags.h
    ... ... @@ -91,6 +91,7 @@ typedef struct _GC_FLAGS {
    91 91
         StgWord numaMask;
    
    92 92
     
    
    93 93
         StgWord64 addressSpaceSize;  /* large address space size in bytes */
    
    94
    +    bool hugepages;              /* Enable hugepages support */
    
    94 95
     } GC_FLAGS;
    
    95 96
     
    
    96 97
     /* See Note [Synchronization of flags and base APIs] */
    

  • rts/posix/OSMem.c
    ... ... @@ -73,6 +73,11 @@
    73 73
     # endif
    
    74 74
     #endif
    
    75 75
     
    
    76
    +#if defined(HUGEPAGE_FLAGS)
    
    77
    +static int huge_tried = 0;
    
    78
    +static int huge_failed = 0;
    
    79
    +#endif
    
    80
    +
    
    76 81
     static void *next_request = 0;
    
    77 82
     
    
    78 83
     void osMemInit(void)
    
    ... ... @@ -233,12 +238,28 @@ my_mmap (void *addr, W_ size, int operation)
    233 238
             errorBelch("my_mmap(,,MEM_RESERVE) not supported on this platform");
    
    234 239
     # endif
    
    235 240
         } else if (operation == MEM_COMMIT) {
    
    236
    -        flags = MAP_FIXED | MAP_ANON | MAP_PRIVATE;
    
    241
    +        flags = MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE;
    
    242
    +#if defined(HUGEPAGE_FLAGS)
    
    243
    +        if ( RtsFlags.GcFlags.hugepages &&
    
    244
    +            (size & (HUGEPAGE_SIZE - 1)) == 0) {
    
    245
    +          huge_tried += 1;
    
    246
    +          flags |= HUGEPAGE_FLAGS;
    
    247
    +        }
    
    248
    +#endif /* defined(HUGEPAGE_FLAGS) */
    
    237 249
         } else {
    
    238 250
             flags = MAP_ANON | MAP_PRIVATE;
    
    239 251
         }
    
    240 252
     
    
    241 253
         ret = mmap(addr, size, prot, flags, -1, 0);
    
    254
    +#if defined(HUGEPAGE_FLAGS)
    
    255
    +    // If the mmap failed, and we tried with HUGEPAGE_FLAGS
    
    256
    +    // then retry without.
    
    257
    +    if (ret == MAP_FAILED && flags & HUGEPAGE_FLAGS){
    
    258
    +      huge_failed += 1;
    
    259
    +      flags &= ~HUGEPAGE_FLAGS;
    
    260
    +      ret = mmap(addr, size, prot, flags, -1, 0);
    
    261
    +    }
    
    262
    +#endif
    
    242 263
     # if defined(linux_HOST_OS)
    
    243 264
         if (ret == MAP_FAILED && errno == EPERM) {
    
    244 265
             // Linux may return EPERM if it tried to give us
    
    ... ... @@ -457,6 +478,7 @@ StgWord64 getPhysicalMemorySize (void)
    457 478
     
    
    458 479
     #if defined(USE_LARGE_ADDRESS_SPACE)
    
    459 480
     
    
    481
    +
    
    460 482
     static void *
    
    461 483
     osTryReserveHeapMemory (W_ len, void *hint)
    
    462 484
     {
    
    ... ... @@ -470,6 +492,7 @@ osTryReserveHeapMemory (W_ len, void *hint)
    470 492
            and then we discard what we don't need */
    
    471 493
     
    
    472 494
         base = my_mmap(hint, len + MBLOCK_SIZE, MEM_RESERVE);
    
    495
    +
    
    473 496
         if (base == NULL)
    
    474 497
             return NULL;
    
    475 498
     
    

  • rts/sm/BlockAlloc.c
    ... ... @@ -25,7 +25,8 @@
    25 25
     
    
    26 26
     #include <string.h>
    
    27 27
     
    
    28
    -static void  initMBlock(void *mblock, uint32_t node);
    
    28
    +static void initMBlock(void *mblock, uint32_t node);
    
    29
    +static void free_mega_group (bdescr *mg);
    
    29 30
     
    
    30 31
     /*
    
    31 32
      * By default the DEBUG RTS is built with block allocator assertions
    
    ... ... @@ -505,13 +506,30 @@ alloc_mega_group (uint32_t node, StgWord mblocks)
    505 506
         else
    
    506 507
         {
    
    507 508
             void *mblock;
    
    509
    +        StgWord hugepage_mblocks;
    
    510
    +        if(RtsFlags.GcFlags.hugepages) {
    
    511
    +          // Round up allocation to hugepage size
    
    512
    +          hugepage_mblocks = MBLOCK_ROUND_UP_HUGEPAGE(mblocks);
    
    513
    +        }
    
    514
    +        else {
    
    515
    +          hugepage_mblocks = mblocks;
    
    516
    +        }
    
    517
    +
    
    508 518
             if (RtsFlags.GcFlags.numa) {
    
    509
    -            mblock = getMBlocksOnNode(node, mblocks);
    
    519
    +            mblock = getMBlocksOnNode(node, hugepage_mblocks);
    
    510 520
             } else {
    
    511
    -            mblock = getMBlocks(mblocks);
    
    521
    +            mblock = getMBlocks(hugepage_mblocks);
    
    512 522
             }
    
    513 523
             initMBlock(mblock, node); // only need to init the 1st one
    
    514 524
             bd = FIRST_BDESCR(mblock);
    
    525
    +
    
    526
    +        // Free the slop
    
    527
    +        if(hugepage_mblocks > mblocks) {
    
    528
    +          bdescr *mblock_slop_bd = FIRST_BDESCR((uintptr_t)mblock + (uintptr_t)mblocks*MBLOCK_SIZE);
    
    529
    +          initMBlock(MBLOCK_ROUND_DOWN(mblock_slop_bd), node);
    
    530
    +          mblock_slop_bd->blocks = MBLOCK_GROUP_BLOCKS(hugepage_mblocks - mblocks);
    
    531
    +          free_mega_group(mblock_slop_bd);
    
    532
    +        }
    
    515 533
         }
    
    516 534
         bd->blocks = MBLOCK_GROUP_BLOCKS(mblocks);
    
    517 535
         return bd;
    
    ... ... @@ -839,7 +857,7 @@ coalesce_mblocks (bdescr *p)
    839 857
         return q;
    
    840 858
     }
    
    841 859
     
    
    842
    -static void
    
    860
    +void
    
    843 861
     free_mega_group (bdescr *mg)
    
    844 862
     {
    
    845 863
         bdescr *bd, *prev;
    
    ... ... @@ -1226,10 +1244,17 @@ uint32_t returnMemoryToOS(uint32_t n /* megablocks */)
    1226 1244
         return 0;
    
    1227 1245
     #else
    
    1228 1246
         bdescr *bd;
    
    1247
    +    bdescr *rejects;
    
    1248
    +    bdescr *next;
    
    1229 1249
         uint32_t node;
    
    1230
    -    StgWord size;
    
    1250
    +    StgWord size, unaligned_size, freeable_size;
    
    1231 1251
         uint32_t init_n;
    
    1232 1252
         init_n = n;
    
    1253
    +    if(RtsFlags.GcFlags.hugepages) {
    
    1254
    +      // Invariant: n is always a multiple of the hugepage size
    
    1255
    +      // as we can only free whole hugepages.
    
    1256
    +      n = MBLOCK_ROUND_DOWN_HUGEPAGE(n);
    
    1257
    +    }
    
    1233 1258
     
    
    1234 1259
         // TODO: This is inefficient because this loop will essentially result in
    
    1235 1260
         // quadratic runtime behavior: for each call to `freeMBlocks`, the
    
    ... ... @@ -1242,22 +1267,72 @@ uint32_t returnMemoryToOS(uint32_t n /* megablocks */)
    1242 1267
         // ToDo: not fair, we free all the memory starting with node 0.
    
    1243 1268
         for (node = 0; n > 0 && node < n_numa_nodes; node++) {
    
    1244 1269
             bd = free_mblock_list[node];
    
    1270
    +        // 'rejects' is a reversed list of mblocks that need to go back on the
    
    1271
    +        // free list.
    
    1272
    +        rejects = NULL;
    
    1245 1273
             while ((n > 0) && (bd != NULL)) {
    
    1246 1274
                 size = BLOCKS_TO_MBLOCKS(bd->blocks);
    
    1247
    -            if (size > n) {
    
    1248
    -                StgWord newSize = size - n;
    
    1249
    -                char *freeAddr = MBLOCK_ROUND_DOWN(bd->start);
    
    1250
    -                freeAddr += newSize * MBLOCK_SIZE;
    
    1251
    -                bd->blocks = MBLOCK_GROUP_BLOCKS(newSize);
    
    1252
    -                freeMBlocks(freeAddr, n);
    
    1253
    -                n = 0;
    
    1275
    +            next = bd->link;
    
    1276
    +            char *aligned_start;
    
    1277
    +
    
    1278
    +            if(RtsFlags.GcFlags.hugepages) {
    
    1279
    +              // we can only free hugepage aligned mblock groups
    
    1280
    +              aligned_start = (char*)MBLOCK_ROUND_DOWN(bd) + ((uintptr_t)MBLOCK_ROUND_DOWN(bd) & HUGEPAGE_MASK);
    
    1281
    +              unaligned_size = (aligned_start - (char*)MBLOCK_ROUND_DOWN(bd)) / MBLOCK_SIZE;
    
    1282
    +              freeable_size = MBLOCK_ROUND_DOWN_HUGEPAGE(size - unaligned_size);
    
    1254 1283
                 }
    
    1255 1284
                 else {
    
    1256
    -                char *freeAddr = MBLOCK_ROUND_DOWN(bd->start);
    
    1257
    -                n -= size;
    
    1258
    -                bd = bd->link;
    
    1259
    -                freeMBlocks(freeAddr, size);
    
    1285
    +              aligned_start = (char*)MBLOCK_ROUND_DOWN(bd);
    
    1286
    +              unaligned_size = 0;
    
    1287
    +              freeable_size = size;
    
    1260 1288
                 }
    
    1289
    +
    
    1290
    +            // We cannot free more than n
    
    1291
    +            // Note: n is a multiple of the hugepage size,
    
    1292
    +            // so freeable_size will also continue to be a multiple.
    
    1293
    +            freeable_size = stg_min(n, freeable_size);
    
    1294
    +
    
    1295
    +            // Place the front unaligned section back on the list.
    
    1296
    +            // If we can't free any of it then this is the entire thing.
    
    1297
    +            if (unaligned_size > 0 || freeable_size == 0) {
    
    1298
    +              bd->link = rejects;
    
    1299
    +              rejects = bd;
    
    1300
    +              // We are freeing some mblocks from the middle
    
    1301
    +              if (freeable_size > 0) {
    
    1302
    +                bd->blocks = MBLOCK_GROUP_BLOCKS(unaligned_size);
    
    1303
    +                bdescr *aligned_bd;
    
    1304
    +                aligned_bd = FIRST_BDESCR(aligned_start);
    
    1305
    +                aligned_bd->blocks = MBLOCK_GROUP_BLOCKS(freeable_size);
    
    1306
    +              }
    
    1307
    +            }
    
    1308
    +
    
    1309
    +            if(freeable_size > 0) {
    
    1310
    +                // Free the mblocks
    
    1311
    +                n -= freeable_size;
    
    1312
    +                freeMBlocks(aligned_start, freeable_size);
    
    1313
    +                // add the slop to the rejects list
    
    1314
    +                if (size - unaligned_size - freeable_size > 0)
    
    1315
    +                {
    
    1316
    +                  void *slop = aligned_start + freeable_size * MBLOCK_SIZE;
    
    1317
    +                  bdescr* slop_bd = FIRST_BDESCR(slop);
    
    1318
    +                  slop_bd->blocks = MBLOCK_GROUP_BLOCKS(size - unaligned_size - freeable_size);
    
    1319
    +                  slop_bd->link = rejects;
    
    1320
    +                  initMBlock(slop, node);
    
    1321
    +                  rejects = slop_bd;
    
    1322
    +                }
    
    1323
    +            }
    
    1324
    +            bd = next;
    
    1325
    +        }
    
    1326
    +        // Place the rejected mblocks back on the free list.
    
    1327
    +        // Note: this preserves the order.
    
    1328
    +        while(rejects) {
    
    1329
    +          // pop the top of the rejects list.
    
    1330
    +          next = rejects;
    
    1331
    +          rejects = next->link;
    
    1332
    +          // place it back on the free list.
    
    1333
    +          next->link = bd;
    
    1334
    +          ASSERT(next < bd || bd == NULL);
    
    1335
    +          bd = next;
    
    1261 1336
             }
    
    1262 1337
             free_mblock_list[node] = bd;
    
    1263 1338
         }
    

  • rts/sm/OSMem.h
    ... ... @@ -8,7 +8,21 @@
    8 8
     
    
    9 9
     #pragma once
    
    10 10
     
    
    11
    +#if defined(HAVE_LINUX_MMAN_H)
    
    12
    +#include <linux/mman.h>
    
    13
    +
    
    14
    +#define HUGEPAGE_SHIFT 21
    
    15
    +#define HUGEPAGE_FLAGS (MAP_HUGETLB | MAP_HUGE_2MB)
    
    16
    +#else
    
    17
    +#define HUGEPAGE_SHIFT MBLOCK_SHIFT
    
    18
    +#endif
    
    19
    +
    
    11 20
     #include "BeginPrivate.h"
    
    21
    +GHC_STATIC_ASSERT(HUGEPAGE_SHIFT >= MBLOCK_SHIFT, "mblock size must not exceed 2MB huge page size");
    
    22
    +#define HUGEPAGE_SIZE (1 << HUGEPAGE_SHIFT)
    
    23
    +#define HUGEPAGE_MASK ((1 << HUGEPAGE_SHIFT) - 1)
    
    24
    +#define MBLOCK_ROUND_DOWN_HUGEPAGE(x) ((x) & ~(HUGEPAGE_SHIFT - MBLOCK_SHIFT))
    
    25
    +#define MBLOCK_ROUND_UP_HUGEPAGE(x) ((x) + ((x) & (HUGEPAGE_SHIFT - MBLOCK_SHIFT)))
    
    12 26
     
    
    13 27
     void osMemInit(void);
    
    14 28
     void *osGetMBlocks(uint32_t n);
    

  • testsuite/tests/rts/all.T
    ... ... @@ -15,6 +15,12 @@ test('testmblockalloc',
    15 15
     # which will crash because the mblocks we allocate are not in a state
    
    16 16
     # the leak detector is expecting.
    
    17 17
     
    
    18
    +# A variant of the above that tries to use hugepages
    
    19
    +test('testhugepagesmblockalloc',
    
    20
    +     [c_src, only_ways(['normal','threaded1']), extra_run_opts('+RTS -I0 -xr0.125T -xH'),
    
    21
    +      unless(opsys('linux'), skip)], # Huge pages are only currently supported on Linux
    
    22
    +     compile_and_run, [''])
    
    23
    +
    
    18 24
     
    
    19 25
     # See bug #101, test requires +RTS -c (or equivalently +RTS -M<something>)
    
    20 26
     # only GHCi triggers the bug, but we run the test all ways for completeness.
    

  • testsuite/tests/rts/testhugepagesmblockalloc.c
    1
    +#include "Rts.h"
    
    2
    +
    
    3
    +#include <stdio.h>
    
    4
    +
    
    5
    +// 16 * 64 == max 1GB
    
    6
    +const int MAXALLOC = 16;
    
    7
    +const int ARRSIZE  = 64;
    
    8
    +
    
    9
    +const int LOOPS    = 1000;
    
    10
    +const int SEED     = 0xf00f00;
    
    11
    +
    
    12
    +extern StgWord mblocks_allocated;
    
    13
    +
    
    14
    +int main (int argc, char *argv[])
    
    15
    +{
    
    16
    +    int i, j, b;
    
    17
    +
    
    18
    +    void *a[ARRSIZE];
    
    19
    +    uint32_t sizes[ARRSIZE];
    
    20
    +
    
    21
    +    srand(SEED);
    
    22
    +
    
    23
    +    {
    
    24
    +        RtsConfig conf = defaultRtsConfig;
    
    25
    +        conf.rts_opts_enabled = RtsOptsAll;
    
    26
    +        hs_init_ghc(&argc, &argv, conf);
    
    27
    +    }
    
    28
    +
    
    29
    +   // repeatedly sweep though the array, allocating new random-sized
    
    30
    +   // objects and deallocating the old ones.
    
    31
    +   for (i=0; i < LOOPS; i++)
    
    32
    +   {
    
    33
    +       for (j=0; j < ARRSIZE; j++)
    
    34
    +       {
    
    35
    +           if (i > 0)
    
    36
    +           {
    
    37
    +               freeMBlocks(a[j], sizes[j]);
    
    38
    +           }
    
    39
    +           b = (rand() % MAXALLOC) + 1;
    
    40
    +           a[j] = getMBlocks(b);
    
    41
    +           sizes[j] = b;
    
    42
    +       }
    
    43
    +   }
    
    44
    +
    
    45
    +   releaseFreeMemory();
    
    46
    +
    
    47
    +   for (j=0; j < ARRSIZE; j++)
    
    48
    +   {
    
    49
    +       freeMBlocks(a[j], sizes[j]);
    
    50
    +   }
    
    51
    +
    
    52
    +   releaseFreeMemory();
    
    53
    +
    
    54
    +    // this time, sweep forwards allocating new blocks, and then
    
    55
    +    // backwards deallocating them.
    
    56
    +    for (i=0; i < LOOPS; i++)
    
    57
    +    {
    
    58
    +        for (j=0; j < ARRSIZE; j++)
    
    59
    +        {
    
    60
    +            b = (rand() % MAXALLOC) + 1;
    
    61
    +            a[j] = getMBlocks(b);
    
    62
    +            sizes[j] = b;
    
    63
    +        }
    
    64
    +        for (j=ARRSIZE-1; j >= 0; j--)
    
    65
    +        {
    
    66
    +            freeMBlocks(a[j], sizes[j]);
    
    67
    +        }
    
    68
    +    }
    
    69
    +
    
    70
    +    releaseFreeMemory();
    
    71
    +
    
    72
    +    hs_exit(); // will do a memory leak test
    
    73
    +
    
    74
    +    exit(0);
    
    75
    +}