2019-06-25 11:12:58 +00:00
|
|
|
|
.. SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
Copyright(c) 2016-2017 Intel Corporation.
|
2018-05-15 09:49:22 +00:00
|
|
|
|
|
|
|
|
|
.. _Efd_Library:
|
|
|
|
|
|
|
|
|
|
Elastic Flow Distributor Library
|
|
|
|
|
================================
|
|
|
|
|
|
|
|
|
|
Introduction
|
|
|
|
|
------------
|
|
|
|
|
|
|
|
|
|
In Data Centers today, clustering and scheduling of distributed workloads
|
|
|
|
|
is a very common task. Many workloads require a deterministic
|
|
|
|
|
partitioning of a flat key space among a cluster of machines. When a
|
|
|
|
|
packet enters the cluster, the ingress node will direct the packet to
|
|
|
|
|
its handling node. For example, data-centers with disaggregated storage
|
|
|
|
|
use storage metadata tables to forward I/O requests to the correct back end
|
|
|
|
|
storage cluster, stateful packet inspection will use match incoming
|
|
|
|
|
flows to signatures in flow tables to send incoming packets to their
|
|
|
|
|
intended deep packet inspection (DPI) devices, and so on.
|
|
|
|
|
|
|
|
|
|
EFD is a distributor library that uses perfect hashing to determine a
|
|
|
|
|
target/value for a given incoming flow key. It has the following
|
|
|
|
|
advantages: first, because it uses perfect hashing it does not store the
|
|
|
|
|
key itself and hence lookup performance is not dependent on the key
|
|
|
|
|
size. Second, the target/value can be any arbitrary value hence the
|
|
|
|
|
system designer and/or operator can better optimize service rates and
|
|
|
|
|
inter-cluster network traffic locating. Third, since the storage
|
|
|
|
|
requirement is much smaller than a hash-based flow table (i.e. better
|
|
|
|
|
fit for CPU cache), EFD can scale to millions of flow keys. Finally,
|
|
|
|
|
with the current optimized library implementation, performance is fully
|
|
|
|
|
scalable with any number of CPU cores.
|
|
|
|
|
|
|
|
|
|
Flow Based Distribution
|
|
|
|
|
-----------------------
|
|
|
|
|
|
|
|
|
|
Computation Based Schemes
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
Flow distribution and/or load balancing can be simply done using a
|
|
|
|
|
stateless computation, for instance using round-robin or a simple
|
|
|
|
|
computation based on the flow key as an input. For example, a hash
|
|
|
|
|
function can be used to direct a certain flow to a target based on
|
|
|
|
|
the flow key (e.g. ``h(key) mod n``) where h(key) is the hash value of the
|
|
|
|
|
flow key and n is the number of possible targets.
|
|
|
|
|
|
|
|
|
|
.. _figure_efd1:
|
|
|
|
|
|
|
|
|
|
.. figure:: img/efd_i1.*
|
|
|
|
|
|
|
|
|
|
Load Balancing Using Front End Node
|
|
|
|
|
|
|
|
|
|
In this scheme (:numref:`figure_efd1`), the front end server/distributor/load balancer
|
|
|
|
|
extracts the flow key from the input packet and applies a computation to determine where
|
|
|
|
|
this flow should be directed. Intuitively, this scheme is very simple
|
|
|
|
|
and requires no state to be kept at the front end node, and hence,
|
|
|
|
|
storage requirements are minimum.
|
|
|
|
|
|
|
|
|
|
.. _figure_efd2:
|
|
|
|
|
|
|
|
|
|
.. figure:: img/efd_i2.*
|
|
|
|
|
|
|
|
|
|
Consistent Hashing
|
|
|
|
|
|
|
|
|
|
A widely used flow distributor that belongs to the same category of
|
|
|
|
|
computation-based schemes is ``consistent hashing``, shown in :numref:`figure_efd2`.
|
|
|
|
|
Target destinations (shown in red) are hashed into the same space as the flow
|
|
|
|
|
keys (shown in blue), and keys are mapped to the nearest target in a clockwise
|
|
|
|
|
fashion. Dynamically adding and removing targets with consistent hashing
|
|
|
|
|
requires only K/n keys to be remapped on average, where K is the number of
|
|
|
|
|
keys, and n is the number of targets. In contrast, in a traditional hash-based
|
|
|
|
|
scheme, a change in the number of targets causes nearly all keys to be
|
|
|
|
|
remapped.
|
|
|
|
|
|
|
|
|
|
Although computation-based schemes are simple and need very little
|
|
|
|
|
storage requirement, they suffer from the drawback that the system
|
|
|
|
|
designer/operator can’t fully control the target to assign a specific
|
|
|
|
|
key, as this is dictated by the hash function.
|
|
|
|
|
Deterministically co-locating of keys together (for example, to minimize
|
|
|
|
|
inter-server traffic or to optimize for network traffic conditions,
|
|
|
|
|
target load, etc.) is simply not possible.
|
|
|
|
|
|
|
|
|
|
Flow-Table Based Schemes
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
When using a Flow-Table based scheme to handle flow distribution/load
|
|
|
|
|
balancing, in contrast with computation-based schemes, the system designer
|
|
|
|
|
has the flexibility of assigning a given flow to any given
|
|
|
|
|
target. The flow table (e.g. DPDK RTE Hash Library) will simply store
|
|
|
|
|
both the flow key and the target value.
|
|
|
|
|
|
|
|
|
|
.. _figure_efd3:
|
|
|
|
|
|
|
|
|
|
.. figure:: img/efd_i3.*
|
|
|
|
|
|
|
|
|
|
Table Based Flow Distribution
|
|
|
|
|
|
|
|
|
|
As shown in :numref:`figure_efd3`, when doing a lookup, the flow-table
|
|
|
|
|
is indexed with the hash of the flow key and the keys (more than one is possible,
|
|
|
|
|
because of hash collision) stored in this index and corresponding values
|
|
|
|
|
are retrieved. The retrieved key(s) is matched with the input flow key
|
|
|
|
|
and if there is a match the value (target id) is returned.
|
|
|
|
|
|
|
|
|
|
The drawback of using a hash table for flow distribution/load balancing
|
|
|
|
|
is the storage requirement, since the flow table need to store keys,
|
|
|
|
|
signatures and target values. This doesn't allow this scheme to scale to
|
|
|
|
|
millions of flow keys. Large tables will usually not fit in
|
|
|
|
|
the CPU cache, and hence, the lookup performance is degraded because of
|
|
|
|
|
the latency to access the main memory.
|
|
|
|
|
|
|
|
|
|
EFD Based Scheme
|
|
|
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
EFD combines the advantages of both flow-table based and computation-based
|
|
|
|
|
schemes. It doesn't require the large storage necessary for
|
|
|
|
|
flow-table based schemes (because EFD doesn't store the key as explained
|
|
|
|
|
below), and it supports any arbitrary value for any given key.
|
|
|
|
|
|
|
|
|
|
.. _figure_efd4:
|
|
|
|
|
|
|
|
|
|
.. figure:: img/efd_i4.*
|
|
|
|
|
|
|
|
|
|
Searching for Perfect Hash Function
|
|
|
|
|
|
|
|
|
|
The basic idea of EFD is when a given key is to be inserted, a family of
|
|
|
|
|
hash functions is searched until the correct hash function that maps the
|
|
|
|
|
input key to the correct value is found, as shown in :numref:`figure_efd4`.
|
|
|
|
|
However, rather than explicitly storing all keys and their associated values,
|
|
|
|
|
EFD stores only indices of hash functions that map keys to values, and
|
|
|
|
|
thereby consumes much less space than conventional flow-based tables.
|
|
|
|
|
The lookup operation is very simple, similar to a computational-based
|
|
|
|
|
scheme: given an input key the lookup operation is reduced to hashing
|
|
|
|
|
that key with the correct hash function.
|
|
|
|
|
|
|
|
|
|
.. _figure_efd5:
|
|
|
|
|
|
|
|
|
|
.. figure:: img/efd_i5.*
|
|
|
|
|
|
|
|
|
|
Divide and Conquer for Millions of Keys
|
|
|
|
|
|
|
|
|
|
Intuitively, finding a hash function that maps each of a large number
|
|
|
|
|
(millions) of input keys to the correct output value is effectively
|
|
|
|
|
impossible, as a result EFD, as shown in :numref:`figure_efd5`,
|
|
|
|
|
breaks the problem into smaller pieces (divide and conquer).
|
|
|
|
|
EFD divides the entire input key set into many small groups.
|
|
|
|
|
Each group consists of approximately 20-28 keys (a configurable parameter
|
|
|
|
|
for the library), then, for each small group, a brute force search to find
|
|
|
|
|
a hash function that produces the correct outputs for each key in the group.
|
|
|
|
|
|
|
|
|
|
It should be mentioned that, since the online lookup table for EFD
|
|
|
|
|
doesn't store the key itself, the size of the EFD table is independent
|
|
|
|
|
of the key size and hence EFD lookup performance which is almost
|
|
|
|
|
constant irrespective of the length of the key which is a highly
|
|
|
|
|
desirable feature especially for longer keys.
|
|
|
|
|
|
|
|
|
|
In summary, EFD is a set separation data structure that supports millions of
|
|
|
|
|
keys. It is used to distribute a given key to an intended target. By itself
|
|
|
|
|
EFD is not a FIB data structure with an exact match the input flow key.
|
|
|
|
|
|
|
|
|
|
.. _Efd_example:
|
|
|
|
|
|
|
|
|
|
Example of EFD Library Usage
|
|
|
|
|
----------------------------
|
|
|
|
|
|
|
|
|
|
EFD can be used along the data path of many network functions and middleboxes.
|
|
|
|
|
As previously mentioned, it can used as an index table for
|
|
|
|
|
<key,value> pairs, meta-data for objects, a flow-level load balancer, etc.
|
|
|
|
|
:numref:`figure_efd6` shows an example of using EFD as a flow-level load
|
|
|
|
|
balancer, where flows are received at a front end server before being forwarded
|
|
|
|
|
to the target back end server for processing. The system designer would
|
|
|
|
|
deterministically co-locate flows together in order to minimize cross-server
|
|
|
|
|
interaction.
|
|
|
|
|
(For example, flows requesting certain webpage objects are co-located
|
|
|
|
|
together, to minimize forwarding of common objects across servers).
|
|
|
|
|
|
|
|
|
|
.. _figure_efd6:
|
|
|
|
|
|
|
|
|
|
.. figure:: img/efd_i6.*
|
|
|
|
|
|
|
|
|
|
EFD as a Flow-Level Load Balancer
|
|
|
|
|
|
|
|
|
|
As shown in :numref:`figure_efd6`, the front end server will have an EFD table that
|
|
|
|
|
stores for each group what is the perfect hash index that satisfies the
|
|
|
|
|
correct output. Because the table size is small and fits in cache (since
|
|
|
|
|
keys are not stored), it sustains a large number of flows (N*X, where N
|
|
|
|
|
is the maximum number of flows served by each back end server of the X
|
|
|
|
|
possible targets).
|
|
|
|
|
|
|
|
|
|
With an input flow key, the group id is computed (for example, using
|
|
|
|
|
last few bits of CRC hash) and then the EFD table is indexed with the
|
|
|
|
|
group id to retrieve the corresponding hash index to use. Once the index
|
|
|
|
|
is retrieved the key is hashed using this hash function and the result
|
|
|
|
|
will be the intended correct target where this flow is supposed to be
|
|
|
|
|
processed.
|
|
|
|
|
|
|
|
|
|
It should be noted that as a result of EFD not matching the exact key but
|
|
|
|
|
rather distributing the flows to a target back end node based on the
|
|
|
|
|
perfect hash index, a key that has not been inserted before
|
|
|
|
|
will be distributed to a valid target. Hence, a local table which stores
|
|
|
|
|
the flows served at each node is used and is
|
|
|
|
|
exact matched with the input key to rule out new never seen before
|
|
|
|
|
flows.
|
|
|
|
|
|
|
|
|
|
.. _Efd_api:
|
|
|
|
|
|
|
|
|
|
Library API Overview
|
|
|
|
|
--------------------
|
|
|
|
|
|
|
|
|
|
The EFD library API is created with a very similar semantics of a
|
|
|
|
|
hash-index or a flow table. The application creates an EFD table for a
|
|
|
|
|
given maximum number of flows, a function is called to insert a flow key
|
|
|
|
|
with a specific target value, and another function is used to retrieve
|
|
|
|
|
target values for a given individual flow key or a bulk of keys.
|
|
|
|
|
|
|
|
|
|
EFD Table Create
|
|
|
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
The function ``rte_efd_create()`` is used to create and return a pointer
|
|
|
|
|
to an EFD table that is sized to hold up to num_flows key.
|
|
|
|
|
The online version of the EFD table (the one that does
|
|
|
|
|
not store the keys and is used for lookups) will be allocated and
|
|
|
|
|
created in the last level cache (LLC) of the socket defined by the
|
|
|
|
|
online_socket_bitmask, while the offline EFD table (the one that
|
|
|
|
|
stores the keys and is used for key inserts and for computing the
|
|
|
|
|
perfect hashing) is allocated and created in the LLC of the socket
|
|
|
|
|
defined by offline_socket_bitmask. It should be noted, that for
|
|
|
|
|
highest performance the socket id should match that where the thread is
|
|
|
|
|
running, i.e. the online EFD lookup table should be created on the same
|
|
|
|
|
socket as where the lookup thread is running.
|
|
|
|
|
|
|
|
|
|
EFD Insert and Update
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
The EFD function to insert a key or update a key to a new value is
|
|
|
|
|
``rte_efd_update()``. This function will update an existing key to
|
|
|
|
|
a new value (target) if the key has already been inserted
|
|
|
|
|
before, or will insert the <key,value> pair if this key has not been inserted
|
|
|
|
|
before. It will return 0 upon success. It will return
|
|
|
|
|
``EFD_UPDATE_WARN_GROUP_FULL (1)`` if the operation is insert, and the
|
|
|
|
|
last available space in the key's group was just used. It will return
|
|
|
|
|
``EFD_UPDATE_FAILED (2)`` when the insertion or update has failed (either it
|
|
|
|
|
failed to find a suitable perfect hash or the group was full). The function
|
|
|
|
|
will return ``EFD_UPDATE_NO_CHANGE (3)`` if there is no change to the EFD
|
|
|
|
|
table (i.e, same value already exists).
|
|
|
|
|
|
|
|
|
|
.. Note::
|
|
|
|
|
|
|
|
|
|
This function is not multi-thread safe and should only be called
|
|
|
|
|
from one thread.
|
|
|
|
|
|
|
|
|
|
EFD Lookup
|
|
|
|
|
~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
To lookup a certain key in an EFD table, the function ``rte_efd_lookup()``
|
|
|
|
|
is used to return the value associated with single key.
|
|
|
|
|
As previously mentioned, if the key has been inserted, the correct value
|
|
|
|
|
inserted is returned, if the key has not been inserted before,
|
|
|
|
|
a ‘random’ value (based on hashing of the key) is returned.
|
|
|
|
|
For better performance and to decrease the overhead of
|
|
|
|
|
function calls per key, it is always recommended to use a bulk lookup
|
|
|
|
|
function (simultaneous lookup of multiple keys) instead of a single key
|
|
|
|
|
lookup function. ``rte_efd_lookup_bulk()`` is the bulk lookup function,
|
|
|
|
|
that looks up num_keys simultaneously stored in the key_list and the
|
|
|
|
|
corresponding return values will be returned in the value_list.
|
|
|
|
|
|
|
|
|
|
.. Note::
|
|
|
|
|
|
|
|
|
|
This function is multi-thread safe, but there should not be other threads
|
|
|
|
|
writing in the EFD table, unless locks are used.
|
|
|
|
|
|
|
|
|
|
EFD Delete
|
|
|
|
|
~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
To delete a certain key in an EFD table, the function
|
|
|
|
|
``rte_efd_delete()`` can be used. The function returns zero upon success
|
|
|
|
|
when the key has been found and deleted. Socket_id is the parameter to
|
|
|
|
|
use to lookup the existing value, which is ideally the caller's socket id.
|
|
|
|
|
The previous value associated with this key will be returned
|
|
|
|
|
in the prev_value argument.
|
|
|
|
|
|
|
|
|
|
.. Note::
|
|
|
|
|
|
|
|
|
|
This function is not multi-thread safe and should only be called
|
|
|
|
|
from one thread.
|
|
|
|
|
|
|
|
|
|
.. _Efd_internals:
|
|
|
|
|
|
|
|
|
|
Library Internals
|
|
|
|
|
-----------------
|
|
|
|
|
|
|
|
|
|
This section provides the brief high-level idea and an overview
|
|
|
|
|
of the library internals to accompany the RFC. The intent of this
|
|
|
|
|
section is to explain to readers the high-level implementation of
|
|
|
|
|
insert, lookup and group rebalancing in the EFD library.
|
|
|
|
|
|
|
|
|
|
Insert Function Internals
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
As previously mentioned the EFD divides the whole set of keys into
|
|
|
|
|
groups of a manageable size (e.g. 28 keys) and then searches for the
|
|
|
|
|
perfect hash that satisfies the intended target value for each key. EFD
|
|
|
|
|
stores two version of the <key,value> table:
|
|
|
|
|
|
|
|
|
|
- Offline Version (in memory): Only used for the insertion/update
|
|
|
|
|
operation, which is less frequent than the lookup operation. In the
|
|
|
|
|
offline version the exact keys for each group is stored. When a new
|
|
|
|
|
key is added, the hash function is updated that will satisfy the
|
|
|
|
|
value for the new key together with the all old keys already inserted
|
|
|
|
|
in this group.
|
|
|
|
|
|
|
|
|
|
- Online Version (in cache): Used for the frequent lookup operation. In
|
|
|
|
|
the online version, as previously mentioned, the keys are not stored
|
|
|
|
|
but rather only the hash index for each group.
|
|
|
|
|
|
|
|
|
|
.. _figure_efd7:
|
|
|
|
|
|
|
|
|
|
.. figure:: img/efd_i7.*
|
|
|
|
|
|
|
|
|
|
Group Assignment
|
|
|
|
|
|
|
|
|
|
:numref:`figure_efd7` depicts the group assignment for 7 flow keys as an example.
|
|
|
|
|
Given a flow key, a hash function (in our implementation CRC hash) is
|
|
|
|
|
used to get the group id. As shown in the figure, the groups can be
|
|
|
|
|
unbalanced. (We highlight group rebalancing further below).
|
|
|
|
|
|
|
|
|
|
.. _figure_efd8:
|
|
|
|
|
|
|
|
|
|
.. figure:: img/efd_i8.*
|
|
|
|
|
|
|
|
|
|
Perfect Hash Search - Assigned Keys & Target Value
|
|
|
|
|
|
|
|
|
|
Focusing on one group that has four keys, :numref:`figure_efd8` depicts the search
|
|
|
|
|
algorithm to find the perfect hash function. Assuming that the target
|
|
|
|
|
value bit for the keys is as shown in the figure, then the online EFD
|
|
|
|
|
table will store a 16 bit hash index and 16 bit lookup table per group
|
|
|
|
|
per value bit.
|
|
|
|
|
|
|
|
|
|
.. _figure_efd9:
|
|
|
|
|
|
|
|
|
|
.. figure:: img/efd_i9.*
|
|
|
|
|
|
|
|
|
|
Perfect Hash Search - Satisfy Target Values
|
|
|
|
|
|
|
|
|
|
For a given keyX, a hash function ``(h(keyX, seed1) + index * h(keyX, seed2))``
|
|
|
|
|
is used to point to certain bit index in the 16bit lookup_table value,
|
|
|
|
|
as shown in :numref:`figure_efd9`.
|
|
|
|
|
The insert function will brute force search for all possible values for the
|
|
|
|
|
hash index until a non conflicting lookup_table is found.
|
|
|
|
|
|
|
|
|
|
.. _figure_efd10:
|
|
|
|
|
|
|
|
|
|
.. figure:: img/efd_i10.*
|
|
|
|
|
|
|
|
|
|
Finding Hash Index for Conflict Free lookup_table
|
|
|
|
|
|
|
|
|
|
For example, since both key3 and key7 have a target bit value of 1, it
|
|
|
|
|
is okay if the hash function of both keys point to the same bit in the
|
|
|
|
|
lookup table. A conflict will occur if a hash index is used that maps
|
|
|
|
|
both Key4 and Key7 to the same index in the lookup_table,
|
|
|
|
|
as shown in :numref:`figure_efd10`, since their target value bit are not the same.
|
|
|
|
|
Once a hash index is found that produces a lookup_table with no
|
|
|
|
|
contradictions, this index is stored for this group. This procedure is
|
|
|
|
|
repeated for each bit of target value.
|
|
|
|
|
|
|
|
|
|
Lookup Function Internals
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
The design principle of EFD is that lookups are much more frequent than
|
|
|
|
|
inserts, and hence, EFD's design optimizes for the lookups which are
|
|
|
|
|
faster and much simpler than the slower insert procedure (inserts are
|
|
|
|
|
slow, because of perfect hash search as previously discussed).
|
|
|
|
|
|
|
|
|
|
.. _figure_efd11:
|
|
|
|
|
|
|
|
|
|
.. figure:: img/efd_i11.*
|
|
|
|
|
|
|
|
|
|
EFD Lookup Operation
|
|
|
|
|
|
|
|
|
|
:numref:`figure_efd11` depicts the lookup operation for EFD. Given an input key,
|
|
|
|
|
the group id is computed (using CRC hash) and then the hash index for this
|
|
|
|
|
group is retrieved from the EFD table. Using the retrieved hash index,
|
|
|
|
|
the hash function ``h(key, seed1) + index *h(key, seed2)`` is used which will
|
|
|
|
|
result in an index in the lookup_table, the bit corresponding to this
|
|
|
|
|
index will be the target value bit. This procedure is repeated for each
|
|
|
|
|
bit of the target value.
|
|
|
|
|
|
|
|
|
|
Group Rebalancing Function Internals
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
When discussing EFD inserts and lookups, the discussion is simplified by
|
|
|
|
|
assuming that a group id is simply a result of hash function. However,
|
|
|
|
|
since hashing in general is not perfect and will not always produce a
|
|
|
|
|
uniform output, this simplified assumption will lead to unbalanced
|
|
|
|
|
groups, i.e., some group will have more keys than other groups.
|
|
|
|
|
Typically, and to minimize insert time with an increasing number of keys,
|
|
|
|
|
it is preferable that all groups will have a balanced number of keys, so
|
|
|
|
|
the brute force search for the perfect hash terminates with a valid hash
|
|
|
|
|
index. In order to achieve this target, groups are rebalanced during
|
|
|
|
|
runtime inserts, and keys are moved around from a busy group to a less
|
|
|
|
|
crowded group as the more keys are inserted.
|
|
|
|
|
|
|
|
|
|
.. _figure_efd12:
|
|
|
|
|
|
|
|
|
|
.. figure:: img/efd_i12.*
|
|
|
|
|
|
|
|
|
|
Runtime Group Rebalancing
|
|
|
|
|
|
|
|
|
|
:numref:`figure_efd12` depicts the high level idea of group rebalancing, given an
|
|
|
|
|
input key the hash result is split into two parts a chunk id and 8-bit
|
|
|
|
|
bin id. A chunk contains 64 different groups and 256 bins (i.e. for any
|
|
|
|
|
given bin it can map to 4 distinct groups). When a key is inserted, the
|
|
|
|
|
bin id is computed, for example in :numref:`figure_efd12` bin_id=2,
|
|
|
|
|
and since each bin can be mapped to one of four different groups (2 bit storage),
|
|
|
|
|
the four possible mappings are evaluated and the one that will result in a
|
|
|
|
|
balanced key distribution across these four is selected the mapping result
|
|
|
|
|
is stored in these two bits.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.. _Efd_references:
|
|
|
|
|
|
|
|
|
|
References
|
|
|
|
|
-----------
|
|
|
|
|
|
|
|
|
|
1- EFD is based on collaborative research work between Intel and
|
|
|
|
|
Carnegie Mellon University (CMU), interested readers can refer to the paper
|
|
|
|
|
“Scaling Up Clustered Network Appliances with ScaleBricks;” Dong Zhou et al.
|
|
|
|
|
at SIGCOMM 2015 (`http://conferences.sigcomm.org/sigcomm/2015/pdf/papers/p241.pdf`)
|
|
|
|
|
for more information.
|