From 3f56210b5336d43af0da024ffaf85d5824055335 Mon Sep 17 00:00:00 2001 From: Fabio Lima Date: Sun, 21 Aug 2022 07:24:10 -0300 Subject: [PATCH] Optimize comparison and hash #20 --- .../src/main/java/benchmark/Throughput.java | 4 +- .../java/com/github/f4b6a3/ulid/Ulid.java | 46 ++++++------ .../java/com/github/f4b6a3/ulid/UlidTest.java | 74 ++++++++++++++++++- 3 files changed, 97 insertions(+), 27 deletions(-) diff --git a/benchmark/src/main/java/benchmark/Throughput.java b/benchmark/src/main/java/benchmark/Throughput.java index 01ba668..23ae6ea 100644 --- a/benchmark/src/main/java/benchmark/Throughput.java +++ b/benchmark/src/main/java/benchmark/Throughput.java @@ -21,8 +21,8 @@ import com.github.f4b6a3.ulid.UlidCreator; @Fork(1) @Threads(1) @State(Scope.Benchmark) -@Warmup(iterations = 3) -@Measurement(iterations = 5) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 3) @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.MILLISECONDS) public class Throughput { diff --git a/src/main/java/com/github/f4b6a3/ulid/Ulid.java b/src/main/java/com/github/f4b6a3/ulid/Ulid.java index da89da1..8b6f63a 100644 --- a/src/main/java/com/github/f4b6a3/ulid/Ulid.java +++ b/src/main/java/com/github/f4b6a3/ulid/Ulid.java @@ -622,45 +622,45 @@ public final class Ulid implements Serializable, Comparable { @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (int) (lsb ^ (lsb >>> 32)); - result = prime * result + (int) (msb ^ (msb >>> 32)); - return result; + final long bits = msb ^ lsb; + return (int) (bits ^ (bits >>> 32)); } @Override public boolean equals(Object obj) { - if (this == obj) - return true; if (obj == null) return false; - if (getClass() != obj.getClass()) + if (obj.getClass() != Ulid.class) return false; - Ulid other = (Ulid) obj; - if (lsb != other.lsb) + Ulid that = (Ulid) obj; + if (lsb != that.lsb) return false; - if (msb != other.msb) + if (msb != that.msb) return false; return true; } @Override - public int compareTo(Ulid other) { + public int compareTo(Ulid that) { - final long mask = 0xffffffffL; + // used to compare as UNSIGNED longs + final long min = 0x8000000000000000L; - final long[] a = { this.msb >>> 32, this.msb & mask, this.lsb >>> 32, this.lsb & mask }; - final long[] b = { other.msb >>> 32, other.msb & mask, other.lsb >>> 32, other.lsb & mask }; + final long a = this.msb + min; + final long b = that.msb + min; - // compare as fields unsigned integers - for (int i = 0; i < a.length; i++) { - if (a[i] > b[i]) { - return 1; - } else if (a[i] < b[i]) { - return -1; - } - } + if (a > b) + return 1; + else if (a < b) + return -1; + + final long c = this.lsb + min; + final long d = that.lsb + min; + + if (c > d) + return 1; + else if (c < d) + return -1; return 0; } diff --git a/src/test/java/com/github/f4b6a3/ulid/UlidTest.java b/src/test/java/com/github/f4b6a3/ulid/UlidTest.java index 97a3afa..109ae71 100644 --- a/src/test/java/com/github/f4b6a3/ulid/UlidTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/UlidTest.java @@ -337,6 +337,28 @@ public class UlidTest { assertEquals(new BigInteger(random2).add(increment), new BigInteger(ulid2.getRandom())); } + @Test + public void testHashCode() { + + Random random = new Random(); + byte[] bytes = new byte[Ulid.ULID_BYTES]; + + // invoked on the same object + for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { + random.nextBytes(bytes); + Ulid ulid1 = Ulid.from(bytes); + assertEquals(ulid1.hashCode(), ulid1.hashCode()); + } + + // invoked on two equal objects + for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { + random.nextBytes(bytes); + Ulid ulid1 = Ulid.from(bytes); + Ulid ulid2 = Ulid.from(bytes); + assertEquals(ulid1.hashCode(), ulid2.hashCode()); + } + } + @Test public void testEquals() { @@ -366,15 +388,63 @@ public class UlidTest { @Test public void testCompareTo() { + final long zero = 0L; Random random = new Random(); byte[] bytes = new byte[Ulid.ULID_BYTES]; for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { - random.nextBytes(bytes); + + bytes = ByteBuffer.allocate(16).putLong(random.nextLong()).putLong(random.nextLong()).array(); Ulid ulid1 = Ulid.from(bytes); BigInteger number1 = new BigInteger(1, bytes); - random.nextBytes(bytes); + bytes = ByteBuffer.allocate(16).putLong(random.nextLong()).putLong(random.nextLong()).array(); + Ulid ulid2 = Ulid.from(bytes); + Ulid ulid3 = Ulid.from(bytes); + BigInteger number2 = new BigInteger(1, bytes); + BigInteger number3 = new BigInteger(1, bytes); + + // compare numerically + assertEquals(number1.compareTo(number2) > 0, ulid1.compareTo(ulid2) > 0); + assertEquals(number1.compareTo(number2) < 0, ulid1.compareTo(ulid2) < 0); + assertEquals(number2.compareTo(number3) == 0, ulid2.compareTo(ulid3) == 0); + + // compare lexicographically + assertEquals(number1.compareTo(number2) > 0, ulid1.toString().compareTo(ulid2.toString()) > 0); + assertEquals(number1.compareTo(number2) < 0, ulid1.toString().compareTo(ulid2.toString()) < 0); + assertEquals(number2.compareTo(number3) == 0, ulid2.toString().compareTo(ulid3.toString()) == 0); + } + + for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { + + bytes = ByteBuffer.allocate(16).putLong(zero).putLong(random.nextLong()).array(); + Ulid ulid1 = Ulid.from(bytes); + BigInteger number1 = new BigInteger(1, bytes); + + bytes = ByteBuffer.allocate(16).putLong(zero).putLong(random.nextLong()).array(); + Ulid ulid2 = Ulid.from(bytes); + Ulid ulid3 = Ulid.from(bytes); + BigInteger number2 = new BigInteger(1, bytes); + BigInteger number3 = new BigInteger(1, bytes); + + // compare numerically + assertEquals(number1.compareTo(number2) > 0, ulid1.compareTo(ulid2) > 0); + assertEquals(number1.compareTo(number2) < 0, ulid1.compareTo(ulid2) < 0); + assertEquals(number2.compareTo(number3) == 0, ulid2.compareTo(ulid3) == 0); + + // compare lexicographically + assertEquals(number1.compareTo(number2) > 0, ulid1.toString().compareTo(ulid2.toString()) > 0); + assertEquals(number1.compareTo(number2) < 0, ulid1.toString().compareTo(ulid2.toString()) < 0); + assertEquals(number2.compareTo(number3) == 0, ulid2.toString().compareTo(ulid3.toString()) == 0); + } + + for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { + + bytes = ByteBuffer.allocate(16).putLong(random.nextLong()).putLong(zero).array(); + Ulid ulid1 = Ulid.from(bytes); + BigInteger number1 = new BigInteger(1, bytes); + + bytes = ByteBuffer.allocate(16).putLong(random.nextLong()).putLong(zero).array(); Ulid ulid2 = Ulid.from(bytes); Ulid ulid3 = Ulid.from(bytes); BigInteger number2 = new BigInteger(1, bytes);