From 1faebac0ca4fb0a8c319abb1b15ea3e3d562be9d Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Wed, 8 Feb 2017 11:52:48 +0100 Subject: [PATCH] JAVA-1392: Reduce lock contention in RPTokenFactory Motivation: RPTokenFactory.md5() is called for almost every query plan by TokenAwarePolicy. This method in turn calls MessageDigest.getInstance(String), which is known to create lock contentions (see https://bugs.openjdk.java.net/browse/JDK-7092821). Modification: Clone a prototype instance of MessageDigest instead of creating a new one from scratch, thus avoiding lock contention. See https://github.com/google/guava/issues/1197. Result: RPTokenFactory.md5() has an improved performance as no more lock contentions arrive. --- changelog/README.md | 1 + .../java/com/datastax/driver/core/Token.java | 38 ++++++++++++++++--- .../driver/core/RPTokenFactoryTest.java | 12 ++++++ 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/changelog/README.md b/changelog/README.md index b51c4515f4c..b399499f07a 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -10,6 +10,7 @@ - [improvement] JAVA-1308: CodecRegistry performance improvements. - [improvement] JAVA-1241: Upgrade Netty to 4.1.x. - [improvement] JAVA-1287: Add CDC to TableOptionsMetadata and Schema Builder. +- [improvement] JAVA-1392: Reduce lock contention in RPTokenFactory. Merged from 3.1.x branch: diff --git a/driver-core/src/main/java/com/datastax/driver/core/Token.java b/driver-core/src/main/java/com/datastax/driver/core/Token.java index e1218587cb3..4686f51c093 100644 --- a/driver-core/src/main/java/com/datastax/driver/core/Token.java +++ b/driver-core/src/main/java/com/datastax/driver/core/Token.java @@ -230,7 +230,6 @@ private long murmur(ByteBuffer data) { k1 *= c2; h1 ^= k1; } - ; //---------- // finalization @@ -569,16 +568,45 @@ private static class RPTokenFactory extends Factory { private static final Token MIN_TOKEN = new RPToken(MIN_VALUE); private static final Token MAX_TOKEN = new RPToken(MAX_VALUE); - private BigInteger md5(ByteBuffer data) { + private final MessageDigest prototype; + private final boolean supportsClone; + + private RPTokenFactory() { + prototype = createMessageDigest(); + boolean supportsClone; try { - MessageDigest digest = MessageDigest.getInstance("MD5"); - digest.update(data.duplicate()); - return new BigInteger(digest.digest()).abs(); + prototype.clone(); + supportsClone = true; + } catch (CloneNotSupportedException e) { + supportsClone = false; + } + this.supportsClone = supportsClone; + } + + private static MessageDigest createMessageDigest() { + try { + return MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("MD5 doesn't seem to be available on this JVM", e); } } + private MessageDigest newMessageDigest() { + if (supportsClone) { + try { + return (MessageDigest) prototype.clone(); + } catch (CloneNotSupportedException ignored) { + } + } + return createMessageDigest(); + } + + private BigInteger md5(ByteBuffer data) { + MessageDigest digest = newMessageDigest(); + digest.update(data.duplicate()); + return new BigInteger(digest.digest()).abs(); + } + @Override RPToken fromString(String tokenStr) { return new RPToken(new BigInteger(tokenStr)); diff --git a/driver-core/src/test/java/com/datastax/driver/core/RPTokenFactoryTest.java b/driver-core/src/test/java/com/datastax/driver/core/RPTokenFactoryTest.java index 70cfde3f737..6c017a1cc69 100644 --- a/driver-core/src/test/java/com/datastax/driver/core/RPTokenFactoryTest.java +++ b/driver-core/src/test/java/com/datastax/driver/core/RPTokenFactoryTest.java @@ -15,8 +15,10 @@ */ package com.datastax.driver.core; +import com.datastax.driver.core.utils.Bytes; import org.testng.annotations.Test; +import java.nio.ByteBuffer; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -24,6 +26,16 @@ public class RPTokenFactoryTest { Token.Factory factory = Token.RPToken.FACTORY; + @Test(groups = "unit") + public void should_hash_consistently() { + ByteBuffer byteBuffer = Bytes.fromHexString("0xCAFEBABE"); + Token tokenA = factory.hash(byteBuffer); + Token tokenB = factory.hash(byteBuffer); + assertThat(tokenA) + .isEqualTo(factory.fromString("59959303159920881837560881824507314222")) + .isEqualTo(tokenB); + } + @Test(groups = "unit") public void should_split_range() { List splits = factory.split(factory.fromString("0"), factory.fromString("127605887595351923798765477786913079296"), 3);