]> git.ipfire.org Git - people/ms/network.git/commitdiff
Implement using Linux's SMP affinity
authorMichael Tremer <michael.tremer@ipfire.org>
Fri, 23 Sep 2016 19:14:20 +0000 (21:14 +0200)
committerMichael Tremer <michael.tremer@ipfire.org>
Fri, 23 Sep 2016 19:14:20 +0000 (21:14 +0200)
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 <michael.tremer@ipfire.org>
Makefile.am
man/network-performance-tuning.xml [new file with mode: 0644]
man/network.xml
src/functions/functions.device
src/functions/functions.interrupts [new file with mode: 0644]
src/functions/functions.system [new file with mode: 0644]
src/functions/functions.util

index a8ca8a1b79547ebf22a95c3274c6a8b7a96264c8..07779486c8e4cd5c8c4b505b377163fd13e48455 100644 (file)
@@ -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 (file)
index 0000000..898f142
--- /dev/null
@@ -0,0 +1,73 @@
+<?xml version="1.0"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS/DTD DocBook XML V4.2//EN"
+       "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<refentry id="network-performance-tuning">
+       <refentryinfo>
+               <title>network-performance-tuning</title>
+               <productname>network</productname>
+
+               <authorgroup>
+                       <author>
+                               <contrib>Developer</contrib>
+                               <firstname>Michael</firstname>
+                               <surname>Tremer</surname>
+                               <email>michael.tremer@ipfire.org</email>
+                       </author>
+               </authorgroup>
+       </refentryinfo>
+
+       <refmeta>
+               <refentrytitle>network-performance-tuning</refentrytitle>
+               <manvolnum>8</manvolnum>
+       </refmeta>
+
+       <refnamediv>
+               <refname>network-performance-tuning</refname>
+               <refpurpose>Network Configuration Control Program</refpurpose>
+       </refnamediv>
+
+       <refsect1>
+               <title>Description</title>
+
+               <para>
+                       This page contains a summary of some performance tuning techniques
+                       that this system is using.
+               </para>
+       </refsect1>
+
+       <refsect2>
+               <title>SMP Affinity</title>
+
+               <para>
+                       This system is automatically using SMP affinity for every physical
+                       network controller, if supported.
+               </para>
+
+               <para>
+                       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.
+               </para>
+
+               <para>
+                       The algorithm is trying to balance all network controllers across
+                       all processors.
+               </para>
+
+               <para>
+                       See /proc/interrups for the distribution of interrupts. 
+               </para>
+       </refsect2>
+
+       <refsect1>
+               <title>See Also</title>
+
+               <para>
+                       <citerefentry>
+                               <refentrytitle>network</refentrytitle>
+                               <manvolnum>8</manvolnum>
+                       </citerefentry>
+               </para>
+       </refsect1>
+</refentry>
index f40ad843d94c7c952fc8a010b12446ebf7244ddb..3b773c01f0e4dbea86cd1fcd6ab4c844c708eb82 100644 (file)
                                <refentrytitle>network-dns-server</refentrytitle>
                                <manvolnum>8</manvolnum>
                        </citerefentry>,
+                       <citerefentry>
+                               <refentrytitle>network-performance-tuning</refentrytitle>
+                               <manvolnum>8</manvolnum>
+                       </citerefentry>,
                        <citerefentry>
                                <refentrytitle>network-port</refentrytitle>
                                <manvolnum>8</manvolnum>
index 314386b947d754487e09ec88e9db655506198898..154165965237549f895b25a616c16f9cb6b6a2e1 100644 (file)
@@ -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 (file)
index 0000000..e050c6a
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+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 (file)
index 0000000..bc5f05b
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+_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} ))
+}
index 8ae38ee63d38f7d8ab1f5b26e5dfc51f503926a0..4b6f956633a20bdad2779be98dc1d34e294dc4e5 100644 (file)
@@ -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 $@
 }