affinity: bind worker memory to local NUMA node

Suricata already pins worker threads to NUMA-local CPUs via the
existing cpu-affinity logic, but every heap allocation those workers
make still goes through plain malloc and lands on whatever NUMA node
the kernel picked for the first touch. When workers and their flow
records end up on different nodes the cross-NUMA traffic shows up as
packet loss on multi-socket boxes.

Add a new threading.set-memory-affinity option, opt-in, that calls
numa_set_localalloc() on each worker right after CPU pinning so all
subsequent allocations on that thread prefer the local node.
Requires libnuma at build time and is a no-op on single-node
systems.

Bug: #8141.
pull/15352/head
Samaresh Kumar Singh 6 months ago
parent 8968b1c5a1
commit 3c639c38d4

@ -1415,13 +1415,22 @@
AC_CHECK_FUNCS([bpf_xdp_query_id])
])
# Optional libnuma probe (used for memory affinity binding, also
# mandatory for DPDK and Napatech below). Sets HAVE_LIBNUMA and links
# -lnuma when available.
AC_CHECK_LIB(numa, numa_available, [
have_libnuma=yes
AC_DEFINE([HAVE_LIBNUMA], [1], [Define to 1 if you have libnuma.])
LIBS="${LIBS} -lnuma"
], [have_libnuma=no])
# DPDK support
enable_dpdk_bond_pmd="no"
AC_ARG_ENABLE(dpdk,
AS_HELP_STRING([--enable-dpdk], [Enable DPDK support [default=no]]),
[enable_dpdk=$enableval],[enable_dpdk=no])
AS_IF([test "x$enable_dpdk" = "xyes"], [
AC_CHECK_LIB(numa, numa_available,, [numa_found="no"])
if test "$have_libnuma" = "no"; then numa_found="no"; fi
if test "$numa_found" = "no"; then
echo
echo " ERROR! libnuma not found by pkg-config, go get it"

@ -919,6 +919,17 @@ packets to process. The detect threads running on other cores will
process more packets. This is only the case after setting the option
to 'yes'.
On NUMA systems where ``set-cpu-affinity`` is enabled you can also opt
into binding each thread's memory to its local NUMA node so per-flow,
per-thread and packet pool allocations stay on the same node as the
worker CPU::
set-memory-affinity: yes
This requires Suricata to be built with libnuma (already pulled in by
``--enable-dpdk`` and the Napatech build). On a single NUMA node system
the option has no effect.
*Example 7 Balancing workload*
.. image:: suricata-yaml/balancing_workload.png

@ -60,6 +60,7 @@
int debuglog_enabled = 0;
bool threading_set_cpu_affinity = false;
bool threading_set_memory_affinity = false;
uint64_t threading_set_stack_size = 0;
/* Runmode Global Thread Names */
@ -963,6 +964,25 @@ void RunModeInitializeThreadSettings(void)
if (threading_set_cpu_affinity) {
AffinitySetupLoadFromConfig();
}
int memory_affinity = 0;
if ((SCConfGetBool("threading.set-memory-affinity", &memory_affinity)) == 0) {
threading_set_memory_affinity = false;
} else {
threading_set_memory_affinity = memory_affinity == 1;
}
if (threading_set_memory_affinity && !threading_set_cpu_affinity) {
SCLogWarning("threading.set-memory-affinity has no effect without "
"threading.set-cpu-affinity, ignoring");
threading_set_memory_affinity = false;
}
#ifndef HAVE_LIBNUMA
if (threading_set_memory_affinity) {
SCLogWarning("threading.set-memory-affinity requested but Suricata "
"was built without libnuma, ignoring");
threading_set_memory_affinity = false;
}
#endif
if ((SCConfGetFloat("threading.detect-thread-ratio", &threading_detect_ratio)) != 1) {
if (SCConfGetNode("threading.detect-thread-ratio") != NULL)
WarnInvalidConfEntry("threading.detect-thread-ratio", "%s", "1");

@ -104,6 +104,7 @@ void RunModeEnablesBypassManager(void);
int RunModeNeedsBypassManager(void);
extern bool threading_set_cpu_affinity;
extern bool threading_set_memory_affinity;
extern float threading_detect_ratio;
extern uint64_t threading_set_stack_size;

@ -49,6 +49,10 @@
#include "util-validate.h"
#include "source-pcap-file-helper.h"
#ifdef HAVE_LIBNUMA
#include <numa.h>
#endif
#ifdef PROFILE_LOCKING
thread_local uint64_t mutex_lock_contention;
thread_local uint64_t mutex_lock_wait_ticks;
@ -856,6 +860,33 @@ int TmThreadGetNbThreads(uint8_t type)
return thread_affinity[type].nb_threads;
}
/**
* \brief Bind subsequent allocations on this thread to the local NUMA node.
*
* Must be called after the thread's CPU affinity has been pinned. Uses
* MPOL_LOCAL (via numa_set_localalloc) so allocations prefer the node of
* the running CPU but can fall back to other nodes when local memory is
* exhausted. No-op when libnuma is not compiled in or the system has a
* single NUMA node.
*/
static void SetMemoryAffinityLocal(const char *name)
{
#ifdef HAVE_LIBNUMA
if (!threading_set_memory_affinity)
return;
if (numa_available() == -1)
return;
if (numa_max_node() < 1)
return;
numa_set_localalloc();
SCLogPerf("Setting memory affinity for thread \"%s\" to local NUMA node, "
"thread id %lu",
name, SCGetThreadIdLong());
#else
(void)name;
#endif
}
/**
* \brief Set the thread options (cpu affinitythread).
* Priority should be already set by pthread_create.
@ -869,6 +900,7 @@ TmEcode TmThreadSetupOptions(ThreadVars *tv)
"%"PRIu16", thread id %lu", tv->name, tv->cpu_affinity,
SCGetThreadIdLong());
SetCPUAffinity(tv->cpu_affinity);
SetMemoryAffinityLocal(tv->name);
}
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
@ -915,6 +947,7 @@ TmEcode TmThreadSetupOptions(ThreadVars *tv)
"thread id %lu", tv->thread_priority,
tv->name, SCGetThreadIdLong());
}
SetMemoryAffinityLocal(tv->name);
TmThreadSetPrio(tv);
}
#endif

@ -1907,6 +1907,12 @@ spm-algo: auto
# Suricata is multi-threaded. Here the threading can be influenced.
threading:
set-cpu-affinity: no
# When set-cpu-affinity is enabled, also bind thread heap allocations
# to the local NUMA node so per-flow, per-thread and packet pool memory
# stays on the same NUMA as the worker CPU. Requires libnuma at build
# time (already present when DPDK or Napatech is enabled). Has no effect
# on single-NUMA systems.
set-memory-affinity: no
autopin: no
# Tune cpu affinity of threads. Each family of threads can be bound
# to specific CPUs.

Loading…
Cancel
Save