From: Michael Tremer Date: Fri, 23 Sep 2016 19:14:20 +0000 (+0200) Subject: Implement using Linux's SMP affinity X-Git-Tag: 009~276 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=de72bd9187aab8b27e4931bcd54a2ab6c091b332;p=network.git Implement using Linux's SMP affinity This feature is enabled by default and will distribute interrupt handling across multiple processors which will (especially on slower hardware) reduce cache access, decrease latency and quite possibly increase throughput. Signed-off-by: Michael Tremer --- diff --git a/Makefile.am b/Makefile.am index a8ca8a1b..07779486 100644 --- a/Makefile.am +++ b/Makefile.am @@ -128,6 +128,7 @@ dist_network_SCRIPTS = \ src/functions/functions.hostapd \ src/functions/functions.hotplug \ src/functions/functions.http \ + src/functions/functions.interrupts \ src/functions/functions.ip \ src/functions/functions.iptables \ src/functions/functions.ip-tunnel \ @@ -151,6 +152,7 @@ dist_network_SCRIPTS = \ src/functions/functions.settings \ src/functions/functions.stp \ src/functions/functions.sysctl \ + src/functions/functions.system \ src/functions/functions.triggers \ src/functions/functions.usb \ src/functions/functions.util \ @@ -334,6 +336,7 @@ MANPAGES = \ man/network-device.8 \ man/network-dhcp.8 \ man/network-dns-server.8 \ + man/network-performance-tuning.8 \ man/network-port.8 \ man/network-port-batman-adv.8 \ man/network-port-batman-adv-port.8 \ diff --git a/man/network-performance-tuning.xml b/man/network-performance-tuning.xml new file mode 100644 index 00000000..898f1421 --- /dev/null +++ b/man/network-performance-tuning.xml @@ -0,0 +1,73 @@ + + + + + + network-performance-tuning + network + + + + Developer + Michael + Tremer + michael.tremer@ipfire.org + + + + + + network-performance-tuning + 8 + + + + network-performance-tuning + Network Configuration Control Program + + + + Description + + + This page contains a summary of some performance tuning techniques + that this system is using. + + + + + SMP Affinity + + + This system is automatically using SMP affinity for every physical + network controller, if supported. + + + + A processor core is assigned to handle all interrupts of a certain + network controller which will result in minimising cache misses, + reducing network latency and quite possibly increasing throughput. + + + + The algorithm is trying to balance all network controllers across + all processors. + + + + See /proc/interrups for the distribution of interrupts. + + + + + See Also + + + + network + 8 + + + + diff --git a/man/network.xml b/man/network.xml index f40ad843..3b773c01 100644 --- a/man/network.xml +++ b/man/network.xml @@ -331,6 +331,10 @@ network-dns-server 8 , + + network-performance-tuning + 8 + , network-port 8 diff --git a/src/functions/functions.device b/src/functions/functions.device index 314386b9..15416596 100644 --- a/src/functions/functions.device +++ b/src/functions/functions.device @@ -578,6 +578,13 @@ device_set_up() { log DEBUG "Setting up device '${device}'" ip link set ${device} up + + # Set SMP affinity + if interrupt_use_smp_affinity; then + device_auto_configure_smp_affinity "${port}" + fi + + return ${EXIT_OK} } device_set_parent_up() { @@ -884,3 +891,103 @@ device_get_link_string() { print "${s}" } + +device_auto_configure_smp_affinity() { + assert [ $# -eq 1 ] + + local device=${1} + + if lock_acquire "smp-affinity"; then + device_set_smp_affinity "${port}" auto + + lock_release "smp-affinity" + fi +} + +device_set_smp_affinity() { + assert [ $# -eq 2 ] + + local device=${1} + local mode=${2} + + # mode can be auto which will automatically try to find + # the least busy processor, or an integer for the desired + # processor that should handle this device + + local num_processors=$(system_get_processors) + + if [ "${mode}" = "auto" ]; then + local processor=$(interrupt_choose_least_busy_processor) + else + assert isinteger mode + local processor=${mode} + + if [ ${processor} -gt ${num_processors} ]; then + log ERROR "Processor ${processor} does not exist" + return ${EXIT_ERROR} + fi + fi + + local interrupts=$(interrupts_for_device ${device}) + if ! isset interrupts; then + log DEBUG "${device} has no interrupts. Not changing SMP affinity" + return ${EXIT_OK} + fi + + # Set SMP affinity + local interrupt + for interrupt in ${interrupts}; do + interrupt_set_smp_affinity ${interrupt} ${processor} + done + + # Find all queues and assign them to the next processor + local queue + for queue in $(device_get_queues ${device}); do + case "${queue}" in + # Only handle receive queues + rx-*) + for interrupt in $(interrupts_for_device_queue ${device} ${queue}); do + interrupt_set_smp_affinity ${interrupt} ${processor} + done + + device_queue_set_smp_affinity ${device} ${queue} ${processor} + ;; + + # Ignore the rest + *) + continue + ;; + esac + + # Get the next available processor if in auto mode + [ "${mode}" = "auto" ] && processor=$(system_get_next_processor ${processor}) + done + + return ${EXIT_OK} +} + +device_get_queues() { + assert [ $# -eq 1 ] + + local device=${1} + + local queue + for queue in ${SYS_CLASS_NET}/${device}/queues/*; do + basename "${queue}" + done +} + +device_queue_set_smp_affinity() { + assert [ $# -eq 3 ] + + local device=${1} + local queue=${2} + local processor=${3} + + local path="${SYS_CLASS_NET}/${device}/queues/${queue}/rps_cpus" + assert [ -w "${path}" ] + + log DEBUG "Setting SMP affinity of ${device} (${queue}) to processor ${processor}" + + __processor_id_to_bitmap ${processor} > ${path} +} diff --git a/src/functions/functions.interrupts b/src/functions/functions.interrupts new file mode 100644 index 00000000..e050c6a6 --- /dev/null +++ b/src/functions/functions.interrupts @@ -0,0 +1,158 @@ +#!/bin/bash +############################################################################### +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2016 IPFire Network Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +############################################################################### + +interrupts_list() { + local interrupt + for interrupt in /proc/irq/*; do + [ -d "${interrupt}" ] || continue + + basename "${interrupt}" + done + + return ${EXIT_OK} +} + +interrupt_use_smp_affinity() { + local processors=$(system_get_processors) + + # There is no point in this feature when there is only one processor + [ ${processors} -eq 1 ] && return ${EXIT_FALSE} + + return ${EXIT_TRUE} +} + +__interrupts_for() { + local path=${1} + + local f + for f in ${path}; do + [ -d "${f}" ] || continue + + local interrupt=$(dirname ${f}) + basename ${interrupt} + done +} + +interrupts_for_device() { + assert [ $# -eq 1 ] + + local device=${1} + + __interrupts_for "/proc/irq/*/${device}" +} + +interrupts_for_device_queue() { + assert [ $# -eq 2 ] + + local device="${1}" + local queue="${2}" + + __interrupts_for "/proc/irq/*/${device}-[rt]x${queue}" +} + +interrupt_get_smp_affinity() { + assert [ $# -eq 1 ] + + local interrupt=${1} + + local path="/proc/irq/${interrupt}/smp_affinity" + assert [ -r "${path}" ] + + # Convert bitmap to list of processors + __bitmap_to_processor_id $(<${path}) +} + +__bitmap_to_processor_id() { + local bitmap=${1} + + # This function shifts the bit map to the right + # and if the least significant bit equals one + # the index of that bit is returned. + + local id=0 + while [ $(( 0x${bitmap} )) -gt 0 ]; do + if [ $(( 0x${bitmap} & 0x1 )) -eq 1 ]; then + print "${id}" + fi + + bitmap=$(( 0x${bitmap} >> 1 )) + ((id++)) + done +} + +__processor_id_to_bitmap() { + hex $(( 1 << $@ )) +} + +interrupt_set_smp_affinity() { + assert [ $# -eq 2 ] + + local interrupt=${1} + local processor=${2} + + # Processor ID must be greater or equal than zero + # and not larger than the highest processor index + local num_processors=$(system_get_processors) + if [ ${processor} -ge ${num_processors} ]; then + error "Invalid processor ID ${processor}" + return ${EXIT_ERROR} + fi + + local path="/proc/irq/${interrupt}/smp_affinity" + assert [ -w "${path}" ] + + log DEBUG "Setting SMP affinity for interrupt ${interrupt} to processor ${processor}" + + # Write processor ID as hex value + __processor_id_to_bitmap ${processor} > ${path} +} + +interrupt_choose_least_busy_processor() { + local processors=$(system_get_processors) + local -a interrupts + + # Create an array with the number of interrupts + # already handled by each processor + + local i + for i in $(range ${processors}); do + interrupts[${i}]=0 + done + + local processor interrupt + for interrupt in $(interrupts_list); do + for processor in $(interrupt_get_smp_affinity ${interrupt}); do + interrupts[${processor}]=$(( ${interrupts[${processor}]} + 1 )) + done + done + + # Walk through that map and find the first processor with the + # smallest number of interrupts handled so far + + local least_busy_index=0 + for i in $(range ${processors}); do + if [ ${interrupts[${least_busy_index}]} -gt ${interrupts[${i}]} ]; then + least_busy_index=${i} + fi + done + + print "${least_busy_index}" +} diff --git a/src/functions/functions.system b/src/functions/functions.system new file mode 100644 index 00000000..bc5f05b1 --- /dev/null +++ b/src/functions/functions.system @@ -0,0 +1,39 @@ +#!/bin/bash +############################################################################### +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2016 IPFire Network Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +############################################################################### + +_getconf() { + getconf $@ +} + +# Returns the number of online processors +system_get_processors() { + _getconf _NPROCESSORS_ONLN +} + +system_get_next_processor() { + assert [ $# -eq 1 ] + + local processor=${1} + local processors=$(system_get_processors) + + # Pick the next one + print $(( (${processor} + 1) % ${processors} )) +} diff --git a/src/functions/functions.util b/src/functions/functions.util index 8ae38ee6..4b6f9566 100644 --- a/src/functions/functions.util +++ b/src/functions/functions.util @@ -470,6 +470,20 @@ seq() { fi } +range() { + eval echo {0..$(( ${1} - 1 ))} +} + +count() { + local i=0 + + while read; do + ((i++)) + done + + echo ${i} +} + which() { type -P $@ }