Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
projectsroutingproposal [2015/09/23 21:52] – AlexanderChernikovprojectsroutingproposal [2016/01/18 19:23] (current) – glebius
Line 1: Line 1:
-This article outlines some routing-related problems in our network stack and describes some solutions proposed in projects/routing+This article outlines some generic routing-related problems in our network stack and describes the solutions proposed in projects/routing 
 ==== Current problems ==== ==== Current problems ====
 === Performance issues === === Performance issues ===
-Many people considered routing lookups slow (and created various mechanisms of avoiding such lookups, like route caching in tunnels (or even protocols), or having per-flow hash record (FLOWTABLE%%))%%.+Many people considered routing lookups slow (and created various mechanisms of avoiding such lookups, like route caching in tunnels (or even protocols), 
 +or having per-flow hash record (FLOWTABLE%%))%%.
  
 Typical packet processing (forwarding for router, or output for web server) path consists of: Typical packet processing (forwarding for router, or output for web server) path consists of:
-  * doing routing lookup (radix read rwlock + rte mutex lock)+  * doing routing lookup (radix read rwlock + routing entry (rte) mutex lock)
   * (optionally) interface address (ifa) atomic refcount acquire/release   * (optionally) interface address (ifa) atomic refcount acquire/release
-  * doing LLE lookup (afdata read rwlock + llentry read (or write) lock) +  * doing link level entry (lle, llentry) lookup (afdata read rwlock + llentry read (or write) lock) 
-Actually, given number of locks is roughly like having single Giant lock: single-cpu performance (600-1mpps) is very similar to the results you can get using modern dual-cpu system with 16/32 cores. The most expensive operation is acquiring //struct rtentry// mutex (think about default route delivering HUGE contention on single lock, which is also hold for plenty of time). However, other locks do heavily impact processing path.+Actually, given number of locks is roughly like having single Giant lock: single-cpu performance (600-1mpps) is very similar to the results you can get 
 +using modern dual-cpu system with 16/32 cores. The most expensive operation is acquiring //struct rtentry// mutex (think about default route delivering 
 +HUGE contention on single lock, which is also hold for plenty of time). However, other locks do heavily impact processing path.
 === Routing code itself === === Routing code itself ===
 Dealing with performance issues revealed number of problems which also have to be dealt with: Dealing with performance issues revealed number of problems which also have to be dealt with:
Line 26: Line 30:
 We managed to remove rwlocks/mutexes from hot path, so these changes permits us to scale close-to linear based on number of cores. We managed to remove rwlocks/mutexes from hot path, so these changes permits us to scale close-to linear based on number of cores.
 In fact, we were able to reach close to 12MPPS for IPv4 forwarding (2xE2660, 8 cores) which is 5-10 times faster than current benchmarks. In fact, we were able to reach close to 12MPPS for IPv4 forwarding (2xE2660, 8 cores) which is 5-10 times faster than current benchmarks.
-It should also be noted, that, in general, (current) changes available in project/routing are not about performance as is: they just _permit_ us to start performance-oriented work with regular benchmarks, etc - since tremendous number of locks did not permit us measure anything. 
  
 +==== Some benchmarks ====
 +=== Forwarding benchmark ===
 +{{projectsroutingproposal/ipv4_routing_1.png}}
 +  * "routing" represents projects/routing branch results
 +  * "unlocked" represents projects/routing branch with route/llable rmlock calls commented out
 +  * "linear" represents linear appoximation of single-core projects/routing results
 +  * "head" represetns HEAD@r287996 branch
 +=== Forwarding/locking benhmark ===
 +{{projectsroutingproposal/lock_fwd.png}}
 +  * "rwlocked" represents HEAD w/o lle read lock/rtentry lock (lltable/radix are rwlocked)
 +  * "rmlocked" represents HEAD w/o lle read lock/rtentry lock (lltable/radix are rmlocked)
 +  * "rmlocked" represents HEAD w/o lle read lock/rtentry lock (lltable/radix are also not locked) (e.g. this is performance w/o locking impact
 +  * "cached" represents HEAD w/ lle & rtentry passed to ip_fastwrd (e.g. this is performance w/o locking AND w/o radix/lltable lookup impact)
 +  * "linear" represents linear appoximation of single-core "cached" results
 ==== High-level overview ==== ==== High-level overview ====
 The key concept is hiding all routing internals and providing pre-computed easy-to-use result to all consumers with minimal locking. The key concept is hiding all routing internals and providing pre-computed easy-to-use result to all consumers with minimal locking.
-We provide separate functions for each address family (fib4/rib4/fib6/rib6).+We provide separate functions for each address family (fib4/fib6/rib).
 We also provide several different (in terms of returned info and relative overhead) methods of retrieving routing data. We also provide several different (in terms of returned info and relative overhead) methods of retrieving routing data.
-Typically, lookup result is pre-computed next hop data, copied to caller-provided 64-bytes nhop_* structure, with all necessary data _including_ the data that needs to be prepended to mbuf (currently - pre-computed L2 header). +Typically, lookup result is pre-computed next hop data, copied to caller-provided 64-bytes nhop_* structure, with (optionally) all necessary data _including_ the data that needs to be prepended to mbuf (currently - pre-computed L2 header).
- +
-New api/structutes: [[https://svnweb.freebsd.org/base/projects/routing/sys/netinet/in_fib.c?view=markup|in_fib.c]] +
-[[https://svnweb.freebsd.org/base/projects/routing/sys/netinet/in_fib.h?view=markup|in_fib.h]] +
-[[https://svnweb.freebsd.org/base/projects/routing/sys/netinet6/in6_fib.c?view=markup|in6_fib.c]] +
-[[https://svnweb.freebsd.org/base/projects/routing/sys/netinet6/in6_fib.h?view=markup|in6_fib.h]] +
-[[https://svnweb.freebsd.org/base/projects/routing/sys/net/rt_nhops.h?view=markup|rt_nhops.h]] +
- +
-It is worth noting that all API/internal function were converted from sockaddr to plain addresses/addresses + scopes. +
-While it might have seen as a good "generic" approach in 80's, it is not really the case. After converting most kernel consumers to new API I'd say that per-AF functions and address-based approach is really simpler and easier to use. The only example of the opposite was some code in NFS. Additionally, sizeof(struct sockaddr_in6%%))%% once seen should make anyone avoid using it as much as possible. +
- +
-Another related change is avoiding IPv6 addresses with embedded scope: you could not be sure when the IP address has its scope embedded. Additionally, set/clearscope functions are REALLY complicated and might acquire locks. So, API/internals were designed to explicitly pass scopeid as another parameter.+
  
 === Locking changes === === Locking changes ===
- +  * Route table is [[https://svnweb.freebsd.org/base?view=revision&revision=287078|switched]] to ipfw-like dual-locking mode: fast path is protected with rmlock, configuration changes requires to acquire cfg rwlock AND write rmlock.
-  * Radix is [[https://svnweb.freebsd.org/base?view=revision&revision=274336|switched]] to ipfw-like dual-locking mode: fast path is protected with rmlock, configuration changes requires ackquiring cfg rwlock AND write rmlock.+
   * rte mutexes are mostly unused leading to single rmlock necessary for L3 lookup   * rte mutexes are mostly unused leading to single rmlock necessary for L3 lookup
   * LLE if_afdata lock uses the same dual-locking approach. (there are plans to move lltable lock inside lltable)   * LLE if_afdata lock uses the same dual-locking approach. (there are plans to move lltable lock inside lltable)
   * Additionally, number of steps are taken to avoid acquiring lle rwlock as much as we can   * Additionally, number of steps are taken to avoid acquiring lle rwlock as much as we can
 +
 +New api/structures are described separately in [[ProjectsRoutingProposal/API|API]] page.
  
 === More details on routing === === More details on routing ===
Line 58: Line 65:
   - Forwarding code (_output() routines) Here again we need very basic info, so we can use optimised fib table lookups.   - Forwarding code (_output() routines) Here again we need very basic info, so we can use optimised fib table lookups.
   - Control plane code (rtsock, address routines) Use slower rib-based procedures returning individual rtentries   - Control plane code (rtsock, address routines) Use slower rib-based procedures returning individual rtentries
- 
-//struct route// was converted to //struct nhop_info// which is used for similar purpose: 
-  * pass recently-looked up next top 
-  * pass (properly referenced, maybe multipath) next hop 
-  * pass address family (loopback case) 
  
 === More details on LLE === === More details on LLE ===
Line 76: Line 78:
 ==== Merge plan ==== ==== Merge plan ====
 Basic plan looks like the following: Basic plan looks like the following:
-  - Merge most of LLE changes not related to locking/timers (e.g. all new lltable callbacks/enumerators/etc) +  - <del>Merge most of LLE changes not related to locking/timers (e.g. all new lltable callbacks/enumerators/etc)</del> 
-  - Discuss/merge LLE locking/timers stuff +  - <del>Discuss/merge LLE locking/timers stuff [[https://reviews.freebsd.org/D3688|D3688]] [[https://reviews.freebsd.org/D3780|D3780]]</del> 
-  - Discuss/merge new routing lookup API +  - Perform one-by-one conversion of non-critical route(4) consumers [PARTIALLY DONE] 
-  - Perform one-by-one conversion of route(4) consumers+  - Discuss/merge new routing lookup API for data path 
 +  - Discuss/merge lltable locking (afdata rwlock -> lltable rmlock+rwlock)
   - Discuss/merge routing locking changes   - Discuss/merge routing locking changes
  
-=== Considerations/Open questions ===+==== Status ==== 
 +Current conversion status is available [[ProjectsRoutingProposal/ConversionStatus|here]]. 
 +==== Considerations/Open questions ====
   - Wider use of rmlock in kernel. Rmlock has side effect of "pinning" current thread which might lead to more severe consequences when using (contested) mutexes/rwlocks. Here we try to avoid acquiring any additional locks as much as possible (no locks for routing lookups, rare lookups for LLE stuff). However, more stuff could be done (for example, explicitly avoid acquiring lock when sending LOTS of traffic to single dead host with no L2 lle info). Additionally, write lock costs a LOT more, so some sort of batching should be considered for inserting kernel routes/lle entries.   - Wider use of rmlock in kernel. Rmlock has side effect of "pinning" current thread which might lead to more severe consequences when using (contested) mutexes/rwlocks. Here we try to avoid acquiring any additional locks as much as possible (no locks for routing lookups, rare lookups for LLE stuff). However, more stuff could be done (for example, explicitly avoid acquiring lock when sending LOTS of traffic to single dead host with no L2 lle info). Additionally, write lock costs a LOT more, so some sort of batching should be considered for inserting kernel routes/lle entries.
   - Could we teach PFIL to start looking at mbuf starting from certain offset? (eliminate 2x LLE header copy)   - Could we teach PFIL to start looking at mbuf starting from certain offset? (eliminate 2x LLE header copy)
  • projectsroutingproposal.1443045161.txt.gz
  • Last modified: 2015/09/23 21:52
  • by AlexanderChernikov