Commit 809fa972 authored by Hannes Frederic Sowa's avatar Hannes Frederic Sowa Committed by David S. Miller

reciprocal_divide: update/correction of the algorithm

Jakub Zawadzki noticed that some divisions by reciprocal_divide()
were not correct [1][2], which he could also show with BPF code
after divisions are transformed into reciprocal_value() for runtime
invariance which can be passed to reciprocal_divide() later on;
reverse in BPF dump ended up with a different, off-by-one K in
some situations.

This has been fixed by Eric Dumazet in commit aee636c4
("bpf: do not use reciprocal divide"). This follow-up patch
improves reciprocal_value() and reciprocal_divide() to work in
all cases by using Granlund and Montgomery method, so that also
future use is safe and without any non-obvious side-effects.
Known problems with the old implementation were that division by 1
always returned 0 and some off-by-ones when the dividend and divisor
where very large. This seemed to not be problematic with its
current users, as far as we can tell. Eric Dumazet checked for
the slab usage, we cannot surely say so in the case of flex_array.
Still, in order to fix that, we propose an extension from the
original implementation from commit 6a2d7a95 resp. [3][4],
by using the algorithm proposed in "Division by Invariant Integers
Using Multiplication" [5], Torbjörn Granlund and Peter L.
Montgomery, that is, pseudocode for q = n/d where q, n, d is in
u32 universe:

1) Initialization:

  int l = ceil(log_2 d)
  uword m' = floor((1<<32)*((1<<l)-d)/d)+1
  int sh_1 = min(l,1)
  int sh_2 = max(l-1,0)

2) For q = n/d, all uword:

  uword t = (n*m')>>32
  q = (t+((n-t)>>sh_1))>>sh_2

The assembler implementation from Agner Fog [6] also helped a lot
while implementing. We have tested the implementation on x86_64,
ppc64, i686, s390x; on x86_64/haswell we're still half the latency
compared to normal divide.

Joint work with Daniel Borkmann.

  [1] http://www.wireshark.org/~darkjames/reciprocal-buggy.c
  [2] http://www.wireshark.org/~darkjames/set-and-dump-filter-k-bug.c
  [3] https://gmplib.org/~tege/division-paper.pdf
  [4] http://homepage.cs.uiowa.edu/~jones/bcd/divide.html
  [5] http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.1.2556
  [6] http://www.agner.org/optimize/asmlib.zipReported-by: default avatarJakub Zawadzki <darkjames-ws@darkjames.pl>
Cc: Eric Dumazet <eric.dumazet@gmail.com>
Cc: Austin S Hemmelgarn <ahferroin7@gmail.com>
Cc: linux-kernel@vger.kernel.org
Cc: Jesse Gross <jesse@nicira.com>
Cc: Jamal Hadi Salim <jhs@mojatatu.com>
Cc: Stephen Hemminger <stephen@networkplumber.org>
Cc: Matt Mackall <mpm@selenic.com>
Cc: Pekka Enberg <penberg@kernel.org>
Cc: Christoph Lameter <cl@linux-foundation.org>
Cc: Andy Gospodarek <andy@greyhouse.net>
Cc: Veaceslav Falico <vfalico@redhat.com>
Cc: Jay Vosburgh <fubar@us.ibm.com>
Cc: Jakub Zawadzki <darkjames-ws@darkjames.pl>
Signed-off-by: default avatarDaniel Borkmann <dborkman@redhat.com>
Signed-off-by: default avatarHannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 89770b0a
...@@ -79,7 +79,6 @@ ...@@ -79,7 +79,6 @@
#include <net/pkt_sched.h> #include <net/pkt_sched.h>
#include <linux/rculist.h> #include <linux/rculist.h>
#include <net/flow_keys.h> #include <net/flow_keys.h>
#include <linux/reciprocal_div.h>
#include "bonding.h" #include "bonding.h"
#include "bond_3ad.h" #include "bond_3ad.h"
#include "bond_alb.h" #include "bond_alb.h"
...@@ -3596,8 +3595,9 @@ static void bond_xmit_slave_id(struct bonding *bond, struct sk_buff *skb, int sl ...@@ -3596,8 +3595,9 @@ static void bond_xmit_slave_id(struct bonding *bond, struct sk_buff *skb, int sl
*/ */
static u32 bond_rr_gen_slave_id(struct bonding *bond) static u32 bond_rr_gen_slave_id(struct bonding *bond)
{ {
int packets_per_slave = bond->params.packets_per_slave;
u32 slave_id; u32 slave_id;
struct reciprocal_value reciprocal_packets_per_slave;
int packets_per_slave = bond->params.packets_per_slave;
switch (packets_per_slave) { switch (packets_per_slave) {
case 0: case 0:
...@@ -3607,8 +3607,10 @@ static u32 bond_rr_gen_slave_id(struct bonding *bond) ...@@ -3607,8 +3607,10 @@ static u32 bond_rr_gen_slave_id(struct bonding *bond)
slave_id = bond->rr_tx_counter; slave_id = bond->rr_tx_counter;
break; break;
default: default:
reciprocal_packets_per_slave =
bond->params.reciprocal_packets_per_slave;
slave_id = reciprocal_divide(bond->rr_tx_counter, slave_id = reciprocal_divide(bond->rr_tx_counter,
packets_per_slave); reciprocal_packets_per_slave);
break; break;
} }
bond->rr_tx_counter++; bond->rr_tx_counter++;
...@@ -4343,10 +4345,18 @@ static int bond_check_params(struct bond_params *params) ...@@ -4343,10 +4345,18 @@ static int bond_check_params(struct bond_params *params)
params->resend_igmp = resend_igmp; params->resend_igmp = resend_igmp;
params->min_links = min_links; params->min_links = min_links;
params->lp_interval = lp_interval; params->lp_interval = lp_interval;
if (packets_per_slave > 1)
params->packets_per_slave = reciprocal_value(packets_per_slave);
else
params->packets_per_slave = packets_per_slave; params->packets_per_slave = packets_per_slave;
if (packets_per_slave > 0) {
params->reciprocal_packets_per_slave =
reciprocal_value(packets_per_slave);
} else {
/* reciprocal_packets_per_slave is unused if
* packets_per_slave is 0 or 1, just initialize it
*/
params->reciprocal_packets_per_slave =
(struct reciprocal_value) { 0 };
}
if (primary) { if (primary) {
strncpy(params->primary, primary, IFNAMSIZ); strncpy(params->primary, primary, IFNAMSIZ);
params->primary[IFNAMSIZ - 1] = 0; params->primary[IFNAMSIZ - 1] = 0;
......
...@@ -19,7 +19,6 @@ ...@@ -19,7 +19,6 @@
#include <linux/if_ether.h> #include <linux/if_ether.h>
#include <net/netlink.h> #include <net/netlink.h>
#include <net/rtnetlink.h> #include <net/rtnetlink.h>
#include <linux/reciprocal_div.h>
#include "bonding.h" #include "bonding.h"
int bond_get_slave(struct net_device *slave_dev, struct sk_buff *skb) int bond_get_slave(struct net_device *slave_dev, struct sk_buff *skb)
...@@ -452,9 +451,6 @@ static int bond_fill_info(struct sk_buff *skb, ...@@ -452,9 +451,6 @@ static int bond_fill_info(struct sk_buff *skb,
goto nla_put_failure; goto nla_put_failure;
packets_per_slave = bond->params.packets_per_slave; packets_per_slave = bond->params.packets_per_slave;
if (packets_per_slave > 1)
packets_per_slave = reciprocal_value(packets_per_slave);
if (nla_put_u32(skb, IFLA_BOND_PACKETS_PER_SLAVE, if (nla_put_u32(skb, IFLA_BOND_PACKETS_PER_SLAVE,
packets_per_slave)) packets_per_slave))
goto nla_put_failure; goto nla_put_failure;
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/rwlock.h> #include <linux/rwlock.h>
#include <linux/rcupdate.h> #include <linux/rcupdate.h>
#include <linux/reciprocal_div.h>
#include "bonding.h" #include "bonding.h"
int bond_option_mode_set(struct bonding *bond, int mode) int bond_option_mode_set(struct bonding *bond, int mode)
...@@ -671,11 +670,17 @@ int bond_option_packets_per_slave_set(struct bonding *bond, ...@@ -671,11 +670,17 @@ int bond_option_packets_per_slave_set(struct bonding *bond,
pr_warn("%s: Warning: packets_per_slave has effect only in balance-rr mode\n", pr_warn("%s: Warning: packets_per_slave has effect only in balance-rr mode\n",
bond->dev->name); bond->dev->name);
if (packets_per_slave > 1)
bond->params.packets_per_slave =
reciprocal_value(packets_per_slave);
else
bond->params.packets_per_slave = packets_per_slave; bond->params.packets_per_slave = packets_per_slave;
if (packets_per_slave > 0) {
bond->params.reciprocal_packets_per_slave =
reciprocal_value(packets_per_slave);
} else {
/* reciprocal_packets_per_slave is unused if
* packets_per_slave is 0 or 1, just initialize it
*/
bond->params.reciprocal_packets_per_slave =
(struct reciprocal_value) { 0 };
}
return 0; return 0;
} }
......
...@@ -39,7 +39,6 @@ ...@@ -39,7 +39,6 @@
#include <net/net_namespace.h> #include <net/net_namespace.h>
#include <net/netns/generic.h> #include <net/netns/generic.h>
#include <linux/nsproxy.h> #include <linux/nsproxy.h>
#include <linux/reciprocal_div.h>
#include "bonding.h" #include "bonding.h"
...@@ -1374,10 +1373,6 @@ static ssize_t bonding_show_packets_per_slave(struct device *d, ...@@ -1374,10 +1373,6 @@ static ssize_t bonding_show_packets_per_slave(struct device *d,
{ {
struct bonding *bond = to_bond(d); struct bonding *bond = to_bond(d);
unsigned int packets_per_slave = bond->params.packets_per_slave; unsigned int packets_per_slave = bond->params.packets_per_slave;
if (packets_per_slave > 1)
packets_per_slave = reciprocal_value(packets_per_slave);
return sprintf(buf, "%u\n", packets_per_slave); return sprintf(buf, "%u\n", packets_per_slave);
} }
......
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
#include <linux/netpoll.h> #include <linux/netpoll.h>
#include <linux/inetdevice.h> #include <linux/inetdevice.h>
#include <linux/etherdevice.h> #include <linux/etherdevice.h>
#include <linux/reciprocal_div.h>
#include "bond_3ad.h" #include "bond_3ad.h"
#include "bond_alb.h" #include "bond_alb.h"
...@@ -171,6 +173,7 @@ struct bond_params { ...@@ -171,6 +173,7 @@ struct bond_params {
int resend_igmp; int resend_igmp;
int lp_interval; int lp_interval;
int packets_per_slave; int packets_per_slave;
struct reciprocal_value reciprocal_packets_per_slave;
}; };
struct bond_parm_tbl { struct bond_parm_tbl {
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
#define _FLEX_ARRAY_H #define _FLEX_ARRAY_H
#include <linux/types.h> #include <linux/types.h>
#include <linux/reciprocal_div.h>
#include <asm/page.h> #include <asm/page.h>
#define FLEX_ARRAY_PART_SIZE PAGE_SIZE #define FLEX_ARRAY_PART_SIZE PAGE_SIZE
...@@ -22,7 +23,7 @@ struct flex_array { ...@@ -22,7 +23,7 @@ struct flex_array {
int element_size; int element_size;
int total_nr_elements; int total_nr_elements;
int elems_per_part; int elems_per_part;
u32 reciprocal_elems; struct reciprocal_value reciprocal_elems;
struct flex_array_part *parts[]; struct flex_array_part *parts[];
}; };
/* /*
......
...@@ -4,29 +4,32 @@ ...@@ -4,29 +4,32 @@
#include <linux/types.h> #include <linux/types.h>
/* /*
* This file describes reciprocical division. * This algorithm is based on the paper "Division by Invariant
* Integers Using Multiplication" by Torbjörn Granlund and Peter
* L. Montgomery.
* *
* This optimizes the (A/B) problem, when A and B are two u32 * The assembler implementation from Agner Fog, which this code is
* and B is a known value (but not known at compile time) * based on, can be found here:
* http://www.agner.org/optimize/asmlib.zip
* *
* The math principle used is : * This optimization for A/B is helpful if the divisor B is mostly
* Let RECIPROCAL_VALUE(B) be (((1LL << 32) + (B - 1))/ B) * runtime invariant. The reciprocal of B is calculated in the
* Then A / B = (u32)(((u64)(A) * (R)) >> 32) * slow-path with reciprocal_value(). The fast-path can then just use
* * a much faster multiplication operation with a variable dividend A
* This replaces a divide by a multiply (and a shift), and * to calculate the division A/B.
* is generally less expensive in CPU cycles.
*/ */
/* struct reciprocal_value {
* Computes the reciprocal value (R) for the value B of the divisor. u32 m;
* Should not be called before each reciprocal_divide(), u8 sh1, sh2;
* or else the performance is slower than a normal divide. };
*/
extern u32 reciprocal_value(u32 B);
struct reciprocal_value reciprocal_value(u32 d);
static inline u32 reciprocal_divide(u32 A, u32 R) static inline u32 reciprocal_divide(u32 a, struct reciprocal_value R)
{ {
return (u32)(((u64)A * R) >> 32); u32 t = (u32)(((u64)a * R.m) >> 32);
return (t + ((a - t) >> R.sh1)) >> R.sh2;
} }
#endif
#endif /* _LINUX_RECIPROCAL_DIV_H */
#ifndef _LINUX_SLAB_DEF_H #ifndef _LINUX_SLAB_DEF_H
#define _LINUX_SLAB_DEF_H #define _LINUX_SLAB_DEF_H
#include <linux/reciprocal_div.h>
/* /*
* Definitions unique to the original Linux SLAB allocator. * Definitions unique to the original Linux SLAB allocator.
*/ */
...@@ -12,7 +14,7 @@ struct kmem_cache { ...@@ -12,7 +14,7 @@ struct kmem_cache {
unsigned int shared; unsigned int shared;
unsigned int size; unsigned int size;
u32 reciprocal_buffer_size; struct reciprocal_value reciprocal_buffer_size;
/* 2) touched by every alloc & free from the backend */ /* 2) touched by every alloc & free from the backend */
unsigned int flags; /* constant flags */ unsigned int flags; /* constant flags */
......
...@@ -130,7 +130,8 @@ struct red_parms { ...@@ -130,7 +130,8 @@ struct red_parms {
u32 qth_max; /* Max avg length threshold: Wlog scaled */ u32 qth_max; /* Max avg length threshold: Wlog scaled */
u32 Scell_max; u32 Scell_max;
u32 max_P; /* probability, [0 .. 1.0] 32 scaled */ u32 max_P; /* probability, [0 .. 1.0] 32 scaled */
u32 max_P_reciprocal; /* reciprocal_value(max_P / qth_delta) */ /* reciprocal_value(max_P / qth_delta) */
struct reciprocal_value max_P_reciprocal;
u32 qth_delta; /* max_th - min_th */ u32 qth_delta; /* max_th - min_th */
u32 target_min; /* min_th + 0.4*(max_th - min_th) */ u32 target_min; /* min_th + 0.4*(max_th - min_th) */
u32 target_max; /* min_th + 0.6*(max_th - min_th) */ u32 target_max; /* min_th + 0.6*(max_th - min_th) */
......
...@@ -90,8 +90,8 @@ struct flex_array *flex_array_alloc(int element_size, unsigned int total, ...@@ -90,8 +90,8 @@ struct flex_array *flex_array_alloc(int element_size, unsigned int total,
{ {
struct flex_array *ret; struct flex_array *ret;
int elems_per_part = 0; int elems_per_part = 0;
int reciprocal_elems = 0;
int max_size = 0; int max_size = 0;
struct reciprocal_value reciprocal_elems = { 0 };
if (element_size) { if (element_size) {
elems_per_part = FLEX_ARRAY_ELEMENTS_PER_PART(element_size); elems_per_part = FLEX_ARRAY_ELEMENTS_PER_PART(element_size);
...@@ -119,6 +119,11 @@ EXPORT_SYMBOL(flex_array_alloc); ...@@ -119,6 +119,11 @@ EXPORT_SYMBOL(flex_array_alloc);
static int fa_element_to_part_nr(struct flex_array *fa, static int fa_element_to_part_nr(struct flex_array *fa,
unsigned int element_nr) unsigned int element_nr)
{ {
/*
* if element_size == 0 we don't get here, so we never touch
* the zeroed fa->reciprocal_elems, which would yield invalid
* results
*/
return reciprocal_divide(element_nr, fa->reciprocal_elems); return reciprocal_divide(element_nr, fa->reciprocal_elems);
} }
......
#include <linux/kernel.h>
#include <asm/div64.h> #include <asm/div64.h>
#include <linux/reciprocal_div.h> #include <linux/reciprocal_div.h>
#include <linux/export.h> #include <linux/export.h>
u32 reciprocal_value(u32 k) /*
* For a description of the algorithm please have a look at
* include/linux/reciprocal_div.h
*/
struct reciprocal_value reciprocal_value(u32 d)
{ {
u64 val = (1LL << 32) + (k - 1); struct reciprocal_value R;
do_div(val, k); u64 m;
return (u32)val; int l;
l = fls(d - 1);
m = ((1ULL << 32) * ((1ULL << l) - d));
do_div(m, d);
++m;
R.m = (u32)m;
R.sh1 = min(l, 1);
R.sh2 = max(l - 1, 0);
return R;
} }
EXPORT_SYMBOL(reciprocal_value); EXPORT_SYMBOL(reciprocal_value);
...@@ -91,7 +91,7 @@ struct netem_sched_data { ...@@ -91,7 +91,7 @@ struct netem_sched_data {
u64 rate; u64 rate;
s32 packet_overhead; s32 packet_overhead;
u32 cell_size; u32 cell_size;
u32 cell_size_reciprocal; struct reciprocal_value cell_size_reciprocal;
s32 cell_overhead; s32 cell_overhead;
struct crndstate { struct crndstate {
...@@ -725,9 +725,11 @@ static void get_rate(struct Qdisc *sch, const struct nlattr *attr) ...@@ -725,9 +725,11 @@ static void get_rate(struct Qdisc *sch, const struct nlattr *attr)
q->rate = r->rate; q->rate = r->rate;
q->packet_overhead = r->packet_overhead; q->packet_overhead = r->packet_overhead;
q->cell_size = r->cell_size; q->cell_size = r->cell_size;
q->cell_overhead = r->cell_overhead;
if (q->cell_size) if (q->cell_size)
q->cell_size_reciprocal = reciprocal_value(q->cell_size); q->cell_size_reciprocal = reciprocal_value(q->cell_size);
q->cell_overhead = r->cell_overhead; else
q->cell_size_reciprocal = (struct reciprocal_value) { 0 };
} }
static int get_loss_clg(struct Qdisc *sch, const struct nlattr *attr) static int get_loss_clg(struct Qdisc *sch, const struct nlattr *attr)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment