Skip to content

Commit ce642b3

Browse files
pascoecyspolyakov
andauthored
Retain large allocations on the heap by default (#1213)
* Modify default policy to keep large allocations on the heap and never auto-trim * update Best_Performance.md * Improve explanation of memory allocation policy Clarify memory allocation behavior and performance impact. --------- Co-authored-by: yspolyakov <89226542+yspolyakov@users.noreply.github.com>
1 parent 67334e2 commit ce642b3

5 files changed

Lines changed: 78 additions & 2 deletions

File tree

docs/static_docs/Best_Performance.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,24 @@ Within OpenFHE, the use of hyperthreading can lead to decreased performance so t
4545

4646
If an alternative parallelization mechanism is used, e.g., pthreads, C++11 threads, or multiprocessing, OpenMP should be turned off by setting the `WITH_OPENMP` CMake flag to OFF.
4747

48+
# Memory Allocation Policy
49+
50+
OpenFHE allocates and frees many large polynomial buffers (~0.5–30 MB each) throughout normal operation. Under the default allocator policy, freed buffers of this size are returned to the OS as soon as they are released, resulting in many page faults when new buffers are requested in subsequent operations.
51+
When operations run back to back, this allocate / return / re-fault churn adds substantial page-fault and kernel overhead, resulting in reduced performance.
52+
53+
To combat this, OpenFHE modifies the default policy at library load to keep large allocations on the heap and never return them to the OS. Freed buffers stay resident and are reused by subsequent allocation requests instead of being re-acquired from the OS (similar behaviour to a memory pool), which minimizes page faults and the associated performance penalty. This is applied automatically and requires no user action.
54+
55+
The trade-off is that a process's resident set size (RSS) reflects its **peak** transient allocation rather than its steady-state working set — e.g., a process that runs `EvalBootstrap` and then idles keeps that peak footprint rather than releasing it.
56+
57+
**If reducing RSS matters for your application, you must explicitly release the retained memory at the application layer** via:
58+
59+
```cpp
60+
#include "utils/memory.h"
61+
lbcrypto::AllocTrim(); // return free heap memory to the OS
62+
```
63+
64+
Call `AllocTrim()` at a quiescent point after large, transient-footprint work has completed and its buffers have been freed (e.g., after a loop over `EvalBootstrap`, or once a batch of operations finishes) and before the process idles; calling it mid-computation is wasteful, since the heap will simply re-grow on the next allocation. OpenFHE invokes `AllocTrim()` automatically on context teardown (`ReleaseAllContexts()`, `ClearStaticMapsAndVectors()`) but deliberately **not** after individual operations, since only the application knows when it has reached a genuinely low-footprint point.
65+
4866
# Accelerating OpenFHE using Specialized Hardware Backends #
4967

5068
OpenFHE supports multiple hardware acceleration backends. Currently, one such backend has been released based on the Intel HEXL library for Intel processors with AVX-512 support.

src/core/include/utils/memory.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,24 @@ void MoveAppend(std::vector<X>& dst, std::vector<X>& src) {
5757
}
5858

5959
/**
60-
* @brief secure_memset() is a function with the same functionality which is provided by std::memset.
60+
* @brief secure_memset() is a function with the same functionality provided by std::memset.
6161
* Usually, the compiler optimizes a call to std::memset out if it is called for a memory which goes out of scope.
6262
* This function is never optimized out and used to re-initialize a memory for security reasons.
6363
*/
6464
void secure_memset(volatile void* mem, uint8_t c, size_t len);
6565

66+
/**
67+
* @brief AllocTrim() returns free (unused) heap memory to the operating system.
68+
* It is the companion to OpenFHE's retain-by-default malloc tuning: call it at a quiescent point after a large
69+
* transient-footprint operation (e.g., EvalBootstrap) to reclaim peak scratch memory.
70+
*
71+
* This is a performance/RSS utility, NOT a security primitive: it does not erase data. Use
72+
* secure_memset() to wipe sensitive memory before freeing. It is not async-signal-safe.
73+
*
74+
* @return true if a trim was attempted, false on unsupported platforms.
75+
*/
76+
bool AllocTrim();
77+
6678
} // namespace lbcrypto
6779

6880
#endif // LBCRYPTO_UTILS_MEMORY_H

src/core/lib/utils/memory.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,51 @@
3030
//==================================================================================
3131
#include "utils/memory.h"
3232

33+
#if defined(__GLIBC__)
34+
#include <malloc.h>
35+
#endif
36+
3337
namespace lbcrypto {
3438

39+
#if defined(__GLIBC__)
40+
// Tune glibc malloc for FHE allocation patterns. Each homomorphic operation allocates and
41+
// frees many large (~0.5-30 MB) polynomial coefficient buffers. By default glibc returns
42+
// these freed buffers to the OS (brk heap-trim / munmap) and re-faults the pages on the next
43+
// operation; under a single thread this page-fault / kernel-time overhead can dominate
44+
// runtime. Keeping large allocations on the heap (M_MMAP_MAX=0) and never trimming it back to
45+
// the OS (M_TRIM_THRESHOLD=-1) lets the buffers be reused, eliminating the churn. Runs once,
46+
// process-wide, at library load. NOTE: this retains freed memory in-process, so workloads
47+
// with large transient allocation peaks will show a higher resident set size. Use AllocTrim()
48+
// after large allocations are freed if this becomes an issue.
49+
namespace {
50+
[[maybe_unused]] const bool ofheGlibcMallocTuned = []() noexcept {
51+
mallopt(M_MMAP_MAX, 0);
52+
mallopt(M_TRIM_THRESHOLD, -1);
53+
return true;
54+
}();
55+
} // namespace
56+
#endif
57+
58+
// AllocTrim() asks the allocator to return free (unused) heap memory to the OS. It is the
59+
// companion to the retain-by-default malloc tuning above: call it at a quiescent point after a
60+
// large transient-footprint operation (e.g. EvalBootstrap) to reclaim peak scratch memory.
61+
//
62+
// WARNING: this is a performance / RSS tool, NOT a security primitive. It does not erase data --
63+
// it only releases already-free chunks (the bytes are not scrubbed; the kernel zero-fills pages
64+
// only when they are next handed out). To wipe sensitive data (keys, plaintext, noise) use
65+
// secure_memset() before freeing.
66+
//
67+
// Not async-signal-safe and acquires allocator locks; call only from normal execution context,
68+
// never from a signal handler. Returns true if a trim was attempted, false on unsupported builds.
69+
bool AllocTrim() {
70+
#if defined(__GLIBC__)
71+
malloc_trim(0);
72+
return true;
73+
#else
74+
return false;
75+
#endif
76+
}
77+
3578
void secure_memset(volatile void* mem, uint8_t c, size_t len) {
3679
volatile uint8_t* ptr = (volatile uint8_t*)mem;
3780
for (size_t i = 0; i < len; ++i)

src/pke/include/cryptocontextfactory.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "cryptocontext-fwd.h"
3636
#include "lattice/lat-hal.h"
3737
#include "scheme/scheme-id.h"
38+
#include "utils/memory.h"
3839

3940
#include <memory>
4041
#include <string>
@@ -65,8 +66,8 @@ class CryptoContextFactory {
6566
static void ReleaseAllContexts() {
6667
if (!AllContexts.empty())
6768
AllContexts[0]->ClearStaticMapsAndVectors();
68-
6969
AllContexts.clear();
70+
AllocTrim();
7071
}
7172

7273
static int GetContextCount() {

src/pke/lib/cryptocontext.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "math/chebyshev.h"
4040
#include "scheme/ckksrns/ckksrns-cryptoparameters.h"
4141
#include "schemerns/rns-scheme.h"
42+
#include "utils/memory.h"
4243

4344
namespace lbcrypto {
4445

@@ -63,6 +64,7 @@ void CryptoContextImpl<Element>::ClearStaticMapsAndVectors() {
6364
#ifdef WITH_NTL
6465
NTL::ChineseRemainderTransformFTTNtl<M6Vector>().Reset();
6566
#endif
67+
AllocTrim();
6668
}
6769

6870
template <typename Element>

0 commit comments

Comments
 (0)