Skip to content

Commit 03d5daf

Browse files
committed
Add jemalloc support
Large graphical Qt applications heavily rely on heap allocations. Jemalloc is a general-purpose malloc(3) implementation designed to reduce heap fragmentation and improve scalability. It also provides extensive tuning options. Add a -jemalloc configure option, disabled by default. When enabled, Qt and user code link to jemalloc, overriding the system's default malloc(). Add cooperation with jemalloc for some Qt key classes: QArrayData (used by QByteArray, QString and QList<T>), QBindingStoragePrivate, QDataBuffer (used by the Qt Quick renderer), QDistanceFieldData, QImageData, QObjectPrivate::TaggedSignalVector, QVarLengthArray. This cooperation relies on two jemalloc-specific optimizations: 1. Efficient allocation via fittedMalloc(): Determine the actual allocation size using nallocx(), then adjust the container’s capacity to match. This minimizes future reallocations. Note: we round allocSize to a multiple of sizeof(T) to ensure that we can later recompute the exact allocation size during deallocation. 2. Optimized deallocation via sizedFree(): Use sdallocx(), which is faster than free when the allocation size is known, as it avoids internal size lookups. Adapt the QVarLengthArray auto tests on capacity. Non-standard functions docs are at https://jemalloc.net/jemalloc.3.html [ChangeLog][QtCore] Added optional support for the jemalloc allocator, and optimized memory allocations and deallocations in core Qt classes to cooperate with it. Change-Id: I6166e64e66876dee22662d3f3ea3e42a6647cfeb Reviewed-by: Thiago Macieira <[email protected]>
1 parent fc277e3 commit 03d5daf

17 files changed

+267
-38
lines changed

cmake/FindJeMalloc.cmake

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright (C) 2025 The Qt Company Ltd.
2+
# SPDX-License-Identifier: BSD-3-Clause
3+
4+
find_package(PkgConfig QUIET)
5+
6+
pkg_check_modules(JeMalloc IMPORTED_TARGET "jemalloc")
7+
8+
if (NOT TARGET PkgConfig::JeMalloc)
9+
set(JeMalloc_FOUND 0)
10+
endif()

config_help.txt

+1
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ Core options:
272272
-glib ................ Enable Glib support [no; auto on Unix]
273273
-inotify ............. Enable inotify support
274274
-icu ................. Enable ICU support [auto]
275+
-jemalloc ............ Enable jemalloc support and cooperation [no]
275276
-pcre ................ Select used libpcre2 [system/qt/no]
276277
-zlib ................ Select used zlib [system/qt]
277278

src/corelib/CMakeLists.txt

+6
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ qt_internal_add_module(Core
4040
# Keep the rest alphabetical
4141
compat/removed_api.cpp
4242
global/archdetect.cpp
43+
global/qalloc.cpp global/qalloc.h
4344
global/qassert.cpp global/qassert.h
4445
global/qcheckedint_impl.h
4546
global/qcompare_impl.h
@@ -922,6 +923,11 @@ qt_internal_extend_target(Core CONDITION UNIX AND NOT MACOS AND NOT QT_FEATURE_i
922923
text/qcollator_posix.cpp
923924
)
924925

926+
qt_internal_extend_target(Core CONDITION QT_FEATURE_jemalloc
927+
PUBLIC_LIBRARIES
928+
PkgConfig::JeMalloc
929+
)
930+
925931
qt_internal_extend_target(Core CONDITION QT_FEATURE_regularexpression
926932
SOURCES
927933
text/qregularexpression.cpp text/qregularexpression.h

src/corelib/configure.cmake

+7
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ qt_find_package(ICU 50.1 COMPONENTS i18n uc data PROVIDED_TARGETS ICU::i18n ICU:
3535
if(QT_FEATURE_dlopen)
3636
qt_add_qmake_lib_dependency(icu libdl)
3737
endif()
38+
qt_find_package(JeMalloc PROVIDED_TARGETS PkgConfig::JeMalloc MODULE_NAME core QMAKE_LIB jemalloc)
3839
qt_find_package(Libsystemd PROVIDED_TARGETS PkgConfig::Libsystemd MODULE_NAME core QMAKE_LIB journald)
3940
qt_find_package(WrapAtomic PROVIDED_TARGETS WrapAtomic::WrapAtomic MODULE_NAME core QMAKE_LIB libatomic)
4041
qt_find_package(Libb2 PROVIDED_TARGETS Libb2::Libb2 MODULE_NAME core QMAKE_LIB libb2)
@@ -733,6 +734,11 @@ qt_feature("ipc_posix"
733734
)
734735
)
735736
qt_feature_definition("ipc_posix" "QT_POSIX_IPC")
737+
qt_feature("jemalloc" PUBLIC PRIVATE
738+
LABEL "JeMalloc"
739+
AUTODETECT OFF
740+
CONDITION JeMalloc_FOUND
741+
)
736742
qt_feature("journald" PRIVATE
737743
LABEL "journald"
738744
AUTODETECT OFF
@@ -1172,6 +1178,7 @@ qt_configure_add_summary_entry(ARGS "system-doubleconversion")
11721178
qt_configure_add_summary_entry(ARGS "forkfd_pidfd" CONDITION LINUX)
11731179
qt_configure_add_summary_entry(ARGS "glib")
11741180
qt_configure_add_summary_entry(ARGS "icu")
1181+
qt_configure_add_summary_entry(ARGS "jemalloc")
11751182
qt_configure_add_summary_entry(ARGS "timezone_tzdb")
11761183
qt_configure_add_summary_entry(ARGS "system-libb2")
11771184
qt_configure_add_summary_entry(ARGS "mimetype-database")

src/corelib/global/qalloc.cpp

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (C) 2025 The Qt Company Ltd.
2+
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3+
4+
#include "qalloc.h"
5+
6+
#include <QtCore/qalgorithms.h>
7+
#include <QtCore/qtpreprocessorsupport.h>
8+
9+
#include <cstdlib>
10+
11+
#if QT_CONFIG(jemalloc)
12+
#include <jemalloc/jemalloc.h>
13+
#endif
14+
15+
QT_BEGIN_NAMESPACE
16+
17+
size_t QtPrivate::expectedAllocSize(size_t allocSize, size_t alignment) noexcept
18+
{
19+
Q_ASSERT(qPopulationCount(alignment) == 1);
20+
#if QT_CONFIG(jemalloc)
21+
return ::nallocx(allocSize, MALLOCX_ALIGN(alignment));
22+
#endif
23+
Q_UNUSED(allocSize);
24+
Q_UNUSED(alignment);
25+
return 0;
26+
}
27+
28+
void QtPrivate::sizedFree(void *ptr, size_t allocSize) noexcept
29+
{
30+
#if QT_CONFIG(jemalloc)
31+
// jemalloc is okay with free(nullptr), as required by the standard,
32+
// but will asssert (in debug) or invoke UB (in release) on sdallocx(nullptr, ...),
33+
// so don't allow Qt to do that.
34+
if (Q_LIKELY(ptr)) {
35+
::sdallocx(ptr, allocSize, 0);
36+
return;
37+
}
38+
#endif
39+
Q_UNUSED(allocSize);
40+
::free(ptr);
41+
}
42+
43+
QT_END_NAMESPACE

src/corelib/global/qalloc.h

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright (C) 2025 The Qt Company Ltd.
2+
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3+
4+
#ifndef QALLOC_H
5+
#define QALLOC_H
6+
7+
//
8+
// W A R N I N G
9+
// -------------
10+
//
11+
// This file is not part of the Qt API. It exists purely as an
12+
// implementation detail. This header file may change from version to
13+
// version without notice, or even be removed.
14+
//
15+
// We mean it.
16+
//
17+
18+
#include <QtCore/qtconfigmacros.h>
19+
#include <QtCore/qtcoreexports.h>
20+
#include <QtCore/qnumeric.h>
21+
#include <QtCore/qtypeinfo.h>
22+
23+
#include <cstddef>
24+
25+
QT_BEGIN_NAMESPACE
26+
27+
namespace QtPrivate {
28+
29+
/**
30+
* \internal
31+
* \return the size that would be allocated for the given request.
32+
*
33+
* Computes the actual allocation size for \a allocSize and \a alignment,
34+
* as determined by the active allocator, without performing the allocation.
35+
*
36+
* In practice, it only returns nonzero when using jemalloc.
37+
*/
38+
Q_CORE_EXPORT Q_DECL_PURE_FUNCTION
39+
size_t expectedAllocSize(size_t allocSize, size_t alignment) noexcept;
40+
41+
/**
42+
* \internal
43+
* \brief Computes the best allocation size for the requested minimum capacity, and updates capacity.
44+
*
45+
* Computes the allocation size starting from \a headerSize and a requested minimum capacity in \a capacity,
46+
* multiplied by the \a elementSize and adjusted by the \a unusedCapacity.
47+
* The final capacity is written back into \a capacity.
48+
* The \a headerSize and \a unusedCapacity values are not included in the final reported capacity.
49+
*/
50+
inline size_t fittedAllocSize(size_t headerSize, size_t *capacity,
51+
size_t elementSize, size_t unusedCapacity, size_t alignment) noexcept
52+
{
53+
size_t totalCapacity = 0; // = capacity + unusedCapacity
54+
if (Q_UNLIKELY(qAddOverflow(*capacity, unusedCapacity, &totalCapacity)))
55+
return 0; // or handle error
56+
57+
size_t payloadSize = 0; // = totalCapacity * elementSize
58+
if (Q_UNLIKELY(qMulOverflow(totalCapacity, elementSize, &payloadSize)))
59+
return 0;
60+
61+
size_t allocSize = 0; // = headerSize + payloadSize
62+
if (Q_UNLIKELY(qAddOverflow(headerSize, payloadSize, &allocSize)))
63+
return 0;
64+
65+
if (size_t fittedSize = expectedAllocSize(allocSize, alignment); fittedSize != 0) {
66+
// no need to overflow/underflow check from fittedSize,
67+
// since allocSize <= fittedSize <= SIZE_T_MAX
68+
*capacity = (fittedSize - headerSize) / elementSize - unusedCapacity;
69+
size_t newTotalCapacity = *capacity + unusedCapacity;
70+
size_t newPayloadSize = newTotalCapacity * elementSize;
71+
return headerSize + newPayloadSize;
72+
}
73+
74+
return allocSize;
75+
}
76+
77+
#ifdef Q_CC_GNU
78+
__attribute__((malloc))
79+
#endif
80+
inline void *fittedMalloc(size_t headerSize, size_t *capacity,
81+
size_t elementSize, size_t unusedCapacity) noexcept
82+
{
83+
size_t allocSize = fittedAllocSize(headerSize, capacity,
84+
elementSize, unusedCapacity, alignof(std::max_align_t));
85+
if (Q_LIKELY(allocSize != 0))
86+
return malloc(allocSize);
87+
else
88+
return nullptr;
89+
}
90+
inline void *fittedMalloc(size_t headerSize, qsizetype *capacity,
91+
size_t elementSize, size_t unusedCapacity = 0) noexcept
92+
{
93+
size_t uCapacity = size_t(*capacity);
94+
void *ptr = fittedMalloc(headerSize, &uCapacity, elementSize, unusedCapacity);
95+
*capacity = qsizetype(uCapacity);
96+
return ptr;
97+
}
98+
99+
inline void *fittedRealloc(void *ptr, size_t headerSize, size_t *capacity,
100+
size_t elementSize, size_t unusedCapacity) noexcept
101+
{
102+
size_t allocSize = fittedAllocSize(headerSize, capacity,
103+
elementSize, unusedCapacity, alignof(std::max_align_t));
104+
if (Q_LIKELY(allocSize != 0))
105+
return realloc(ptr, allocSize);
106+
else
107+
return nullptr;
108+
}
109+
inline void *fittedRealloc(void *ptr, size_t headerSize, qsizetype *capacity,
110+
size_t elementSize, size_t unusedCapacity = 0) noexcept
111+
{
112+
size_t uCapacity = size_t(*capacity);
113+
ptr = fittedRealloc(ptr, headerSize, &uCapacity, elementSize, unusedCapacity);
114+
*capacity = qsizetype(uCapacity);
115+
return ptr;
116+
}
117+
118+
Q_CORE_EXPORT void sizedFree(void *ptr, size_t allocSize) noexcept;
119+
inline void sizedFree(void *ptr, size_t capacity, size_t elementSize) noexcept
120+
{
121+
sizedFree(ptr, capacity * elementSize);
122+
}
123+
124+
} // namespace QtPrivate
125+
126+
QT_END_NAMESPACE
127+
128+
#endif // QALLOC_H

src/corelib/global/qconfig-bootstrapped.h

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
#define QT_FEATURE_itemmodel -1
6363
#define QT_FEATURE_islamiccivilcalendar -1
6464
#define QT_FEATURE_jalalicalendar -1
65+
#define QT_FEATURE_jemalloc -1
6566
#define QT_FEATURE_journald -1
6667
#define QT_FEATURE_library -1
6768
#ifdef __linux__

src/corelib/kernel/qobject_p_p.h

+5-3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
// code lives here is that some special apps/libraries for e.g., QtJambi,
2121
// Gammaray need access to the structs in this file.
2222

23+
#include <QtCore/qalloc.h>
2324
#include <QtCore/qobject.h>
2425
#include <QtCore/private/qobject_p.h>
2526

@@ -152,8 +153,9 @@ struct QObjectPrivate::ConnectionData
152153
deleteOrphaned(c);
153154
SignalVector *v = signalVector.loadRelaxed();
154155
if (v) {
156+
const size_t allocSize = sizeof(SignalVector) + (v->allocated + 1) * sizeof(ConnectionList);
155157
v->~SignalVector();
156-
free(v);
158+
QtPrivate::sizedFree(v, allocSize);
157159
}
158160
}
159161

@@ -179,13 +181,13 @@ struct QObjectPrivate::ConnectionData
179181
return signalVector.loadRelaxed()->at(signal);
180182
}
181183

182-
void resizeSignalVector(uint size)
184+
void resizeSignalVector(size_t size)
183185
{
184186
SignalVector *vector = this->signalVector.loadRelaxed();
185187
if (vector && vector->allocated > size)
186188
return;
187189
size = (size + 7) & ~7;
188-
void *ptr = malloc(sizeof(SignalVector) + (size + 1) * sizeof(ConnectionList));
190+
void *ptr = QtPrivate::fittedMalloc(sizeof(SignalVector), &size, sizeof(ConnectionList), 1);
189191
auto newVector = new (ptr) SignalVector;
190192

191193
int start = -1;

src/corelib/kernel/qproperty.cpp

+5-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include <qscopedvaluerollback.h>
88
#include <QScopeGuard>
9+
#include <QtCore/qalloc.h>
910
#include <QtCore/qloggingcategory.h>
1011
#include <QThread>
1112
#include <QtCore/qmetaobject.h>
@@ -2212,7 +2213,8 @@ struct QBindingStoragePrivate
22122213
}
22132214
}
22142215
// data has been moved, no need to call destructors on old Pairs
2215-
free(d);
2216+
const size_t oldAllocSize = sizeof(QBindingStorageData) + d->size*sizeof(Pair);
2217+
QtPrivate::sizedFree(d, oldAllocSize);
22162218
d = newData;
22172219
}
22182220

@@ -2269,7 +2271,8 @@ struct QBindingStoragePrivate
22692271
p->~Pair();
22702272
++p;
22712273
}
2272-
free(d);
2274+
const size_t allocSize = sizeof(QBindingStorageData) + d->size*sizeof(Pair);
2275+
QtPrivate::sizedFree(d, allocSize);
22732276
}
22742277
};
22752278

src/corelib/qt_cmdline.cmake

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ qt_commandline_option(doubleconversion TYPE enum VALUES no qt system)
55
qt_commandline_option(glib TYPE boolean)
66
qt_commandline_option(icu TYPE boolean)
77
qt_commandline_option(inotify TYPE boolean)
8+
qt_commandline_option(jemalloc TYPE boolean)
89
qt_commandline_option(journald TYPE boolean)
910
qt_commandline_option(libb2 TYPE enum VALUES no qt system)
1011
qt_commandline_option(mimetype-database TYPE boolean)

0 commit comments

Comments
 (0)