Optimized ConsistentHash with array and binarySearch, see #944

* Benchmarked with https://gist.github.com/3720666
* Instead of using TreeMap and rangeImpl this uses
  an sorted array of hash values of the nodes, and
  uses binarySearch in java.util.Arrays to find
  next node clockwise
* Benchmarked with https://gist.github.com/3720666
* Benchmark results show improvement from 500 kmsg/s
  to 700 kmsg/s, round-robin handles 1300 kmsg/s
This commit is contained in:
Patrik Nordwall 2012-09-14 11:06:55 +02:00
parent a515c28e93
commit e3bd02b82c

View file

@ -5,6 +5,7 @@
package akka.routing
import scala.collection.immutable.TreeMap
import java.util.Arrays
// =============================================================================================
// Adapted from HashRing.scala in Debasish Ghosh's Redis Client, licensed under Apache 2 license
@ -20,19 +21,26 @@ import scala.collection.immutable.TreeMap
* hash, i.e. make sure it is different for different nodes.
*
*/
class ConsistentHash[T] private (nodeRing: TreeMap[Int, T], virtualNodesFactor: Int) {
class ConsistentHash[T] private (nodes: Map[Int, T], virtualNodesFactor: Int) {
import ConsistentHash._
if (virtualNodesFactor < 1) throw new IllegalArgumentException("virtualNodesFactor must be >= 1")
// sorted hash values of the nodes
private val nodeRing: Array[Int] = {
val nodeRing = nodes.keys.toArray
Arrays.sort(nodeRing)
nodeRing
}
/**
* Adds a node to the node ring.
* Note that the instance is immutable and this
* operation returns a new instance.
*/
def :+(node: T): ConsistentHash[T] =
new ConsistentHash(nodeRing ++ ((1 to virtualNodesFactor) map { r (nodeHashFor(node, r) -> node) }), virtualNodesFactor)
new ConsistentHash(nodes ++ ((1 to virtualNodesFactor) map { r (nodeHashFor(node, r) -> node) }), virtualNodesFactor)
/**
* Adds a node to the node ring.
@ -48,7 +56,7 @@ class ConsistentHash[T] private (nodeRing: TreeMap[Int, T], virtualNodesFactor:
* operation returns a new instance.
*/
def :-(node: T): ConsistentHash[T] =
new ConsistentHash(nodeRing -- ((1 to virtualNodesFactor) map { r nodeHashFor(node, r) }), virtualNodesFactor)
new ConsistentHash(nodes -- ((1 to virtualNodesFactor) map { r nodeHashFor(node, r) }), virtualNodesFactor)
/**
* Removes a node from the node ring.
@ -65,15 +73,25 @@ class ConsistentHash[T] private (nodeRing: TreeMap[Int, T], virtualNodesFactor:
*/
def nodeFor(key: Array[Byte]): T = {
if (isEmpty) throw new IllegalStateException("Can't get node for [%s] from an empty node ring" format key)
val hash = hashFor(key)
// find the next node clockwise in the nodeRing, pick first if end of tree
nodeRing.rangeImpl(Some(hash), None).headOption.getOrElse(nodeRing.head)._2
// converts the result of Arrays.binarySearch into a index in the nodeRing array
// see documentation of Arrays.binarySearch for what it returns
def idx(i: Int): Int = {
if (i >= 0) i // exact match
else {
val j = math.abs(i + 1)
if (j >= nodeRing.length) 0 // after last, use first
else j // next node clockwise
}
}
val nodeRingIndex = idx(Arrays.binarySearch(nodeRing, hashFor(key)))
nodes(nodeRing(nodeRingIndex))
}
/**
* Is the node ring empty, i.e. no nodes added or all removed.
*/
def isEmpty: Boolean = nodeRing.isEmpty
def isEmpty: Boolean = nodes.isEmpty
}