Small optimizations

Optimized methods:
- UlidUtil.fromUuidToUlid()
- UlidUtil.fromUlidToUuid()

Added tests cases:
- UlidUtilTest.testFromUuidToBytes()
- UlidUtilTest.testFromBytesToUuid()
- UlidUtilTest.testToAndFromBytes()

Created classes:
- Benchmarks.java (need to uncomment)
This commit is contained in:
Fabio Lima 2020-02-25 15:40:35 -03:00
parent 1fc0952067
commit c01a914319
5 changed files with 209 additions and 53 deletions

View File

@ -12,36 +12,18 @@ Create a ULID:
String ulid = UlidCreator.getUlid(); String ulid = UlidCreator.getUlid();
``` ```
Create a fast ULID:
```java
String ulid = UlidCreator.getFastUlid();
```
Create a ULID as GUID object: Create a ULID as GUID object:
```java ```java
UUID ulid = UlidCreator.getGuid(); UUID ulid = UlidCreator.getGuid();
``` ```
Create a fast ULID as GUID object:
```java
UUID ulid = UlidCreator.getFastGuid();
```
Create a ULID as byte sequence: Create a ULID as byte sequence:
```java ```java
byte[] ulid = UlidCreator.getBytes(); byte[] ulid = UlidCreator.getBytes();
``` ```
Create a fast ULID as byte sequence:
```java
byte[] ulid = UlidCreator.getFastBytes();
```
### Maven dependency ### Maven dependency
Add these lines to your `pom.xml`. Add these lines to your `pom.xml`.
@ -51,7 +33,7 @@ Add these lines to your `pom.xml`.
<dependency> <dependency>
<groupId>com.github.f4b6a3</groupId> <groupId>com.github.f4b6a3</groupId>
<artifactId>ulid-creator</artifactId> <artifactId>ulid-creator</artifactId>
<version>1.0.1</version> <version>1.0.2</version>
</dependency> </dependency>
``` ```
See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator) and [mvnrepository.com](https://mvnrepository.com/artifact/com.github.f4b6a3/ulid-creator). See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator) and [mvnrepository.com](https://mvnrepository.com/artifact/com.github.f4b6a3/ulid-creator).
@ -139,17 +121,19 @@ These are some examples of using the `GuidCreator` to create ULIDs:
```java ```java
// with the default random generator (java.security.SecureRandom) // with fixed timestamp strategy (for test cases)
String ulid = UlidCreator.getGuidCreator().createUlid();
// with java random generator (java.util.Random)
String ulid = UlidCreator.getGuidCreator() String ulid = UlidCreator.getGuidCreator()
.withRandomGenerator(new Random()) .withTimestampStrategy(new FixedTimestampStretegy())
.createUlid(); .createUlid();
// with your custom timestamp strategy
String ulid = UlidCreator.getGuidCreator()
.withTimestampStrategy(new MyCustomTimestampStrategy())
.createUlid();
// with fast random generator (Xorshift128Plus) // with your custom random number generator
String ulid = UlidCreator.getGuidCreator() String ulid = UlidCreator.getGuidCreator()
.withFastRandomGenerator() .withRandomGenerator(new MyCustomRandom())
.createUlid(); .createUlid();
// with fast random generator (Xorshift128Plus with salt) // with fast random generator (Xorshift128Plus with salt)
@ -158,5 +142,11 @@ Random random = new Xorshift128PlusRandom(salt);
String ulid = UlidCreator.getGuidCreator() String ulid = UlidCreator.getGuidCreator()
.withRandomGenerator(random) .withRandomGenerator(random)
.createUlid(); .createUlid();
// with fast random generator (the same as above)
String ulid = UlidCreator.getGuidCreator()
.withFastRandomGenerator()
.createUlid();
``` ```

View File

@ -46,8 +46,23 @@ public class UlidUtil {
* @return a ULID * @return a ULID
*/ */
public static String fromUuidToUlid(UUID uuid) { public static String fromUuidToUlid(UUID uuid) {
byte[] bytes = fromUuidToBytes(uuid);
return fromBytesToUlid(bytes); final long msb = uuid.getMostSignificantBits();
final long lsb = uuid.getLeastSignificantBits();
// Extract timestamp component
final long timeNumber = (msb >>> 16);
String timestampComponent = leftPad(Base32Util.toBase32Crockford(timeNumber));
// Extract randomness component
byte[] randBytes = new byte[10];
randBytes[0] = (byte) (msb >>> 8);
randBytes[1] = (byte) (msb);
byte[] lsbBytes = ByteUtil.toBytes(lsb);
System.arraycopy(lsbBytes, 0, randBytes, 2, 8);
String randomnessComponent = Base32Util.toBase32Crockford(randBytes);
return timestampComponent + randomnessComponent;
} }
/** /**
@ -59,9 +74,24 @@ public class UlidUtil {
* a ULID * a ULID
* @return a UUID if valid * @return a UUID if valid
*/ */
public static UUID fromUlidToUuid(String ulid) { public static UUID fromUlidToUuid(final String ulid) {
byte[] bytes = fromUlidToBytes(ulid);
return fromBytesToUuid(bytes); UlidUtil.validate(ulid);
// Extract timestamp component
final String timestampComponent = ulid.substring(0, 10);
final long timeNumber = Base32Util.fromBase32CrockfordAsLong(timestampComponent);
// Extract randomness component
final String randomnessComponent = ulid.substring(10, 26);
byte[] randBytes = Base32Util.fromBase32Crockford(randomnessComponent);
byte[] lsbBytes = new byte[8];
System.arraycopy(randBytes, 2, lsbBytes, 0, 8);
final long msb = (timeNumber << 16) | ((randBytes[0] << 8) & 0x0000ff00L) | ((randBytes[1]) & 0x000000ffL);
final long lsb = ByteUtil.toNumber(lsbBytes);
return new UUID(msb, lsb);
} }
/** /**
@ -71,11 +101,11 @@ public class UlidUtil {
* a UUID * a UUID
* @return an array of bytes * @return an array of bytes
*/ */
public static byte[] fromUuidToBytes(UUID uuid) { public static byte[] fromUuidToBytes(final UUID uuid) {
long msb = uuid.getMostSignificantBits(); final long msb = uuid.getMostSignificantBits();
long lsb = uuid.getLeastSignificantBits(); final long lsb = uuid.getLeastSignificantBits();
byte[] msbBytes = ByteUtil.toBytes(msb); final byte[] msbBytes = ByteUtil.toBytes(msb);
byte[] lsbBytes = ByteUtil.toBytes(lsb); final byte[] lsbBytes = ByteUtil.toBytes(lsb);
return ByteUtil.concat(msbBytes, lsbBytes); return ByteUtil.concat(msbBytes, lsbBytes);
} }
@ -87,10 +117,12 @@ public class UlidUtil {
* @return a UUID * @return a UUID
*/ */
public static UUID fromBytesToUuid(byte[] bytes) { public static UUID fromBytesToUuid(byte[] bytes) {
byte[] msbBytes = ByteUtil.copy(bytes, 0, 8); byte[] msbBytes = new byte[8];
byte[] lsbBytes = ByteUtil.copy(bytes, 8, 16); System.arraycopy(bytes, 0, msbBytes, 0, 8);
long msb = ByteUtil.toNumber(msbBytes); byte[] lsbBytes = new byte[8];
long lsb = ByteUtil.toNumber(lsbBytes); System.arraycopy(bytes, 8, lsbBytes, 0, 8);
final long msb = ByteUtil.toNumber(msbBytes);
final long lsb = ByteUtil.toNumber(lsbBytes);
return new UUID(msb, lsb); return new UUID(msb, lsb);
} }
@ -105,12 +137,12 @@ public class UlidUtil {
byte[] timeBytes = new byte[6]; byte[] timeBytes = new byte[6];
System.arraycopy(bytes, 0, timeBytes, 0, 6); System.arraycopy(bytes, 0, timeBytes, 0, 6);
long timeNumber = ByteUtil.toNumber(timeBytes); final long timeNumber = ByteUtil.toNumber(timeBytes);
String timestampComponent = leftPad(Base32Util.toBase32Crockford(timeNumber)); final String timestampComponent = leftPad(Base32Util.toBase32Crockford(timeNumber));
byte[] randBytes = new byte[10]; byte[] randBytes = new byte[10];
System.arraycopy(bytes, 6, randBytes, 0, 10); System.arraycopy(bytes, 6, randBytes, 0, 10);
String randomnessComponent = Base32Util.toBase32Crockford(randBytes); final String randomnessComponent = Base32Util.toBase32Crockford(randBytes);
return timestampComponent + randomnessComponent; return timestampComponent + randomnessComponent;
} }
@ -122,16 +154,16 @@ public class UlidUtil {
* a ULID string * a ULID string
* @return an array of bytes * @return an array of bytes
*/ */
public static byte[] fromUlidToBytes(String ulid) { public static byte[] fromUlidToBytes(final String ulid) {
UlidUtil.validate(ulid); UlidUtil.validate(ulid);
byte[] bytes = new byte[16]; byte[] bytes = new byte[16];
String timestampComponent = ulid.substring(0, 10); final String timestampComponent = ulid.substring(0, 10);
long timeNumber = Base32Util.fromBase32CrockfordAsLong(timestampComponent); final long timeNumber = Base32Util.fromBase32CrockfordAsLong(timestampComponent);
byte[] timeBytes = ByteUtil.toBytes(timeNumber); byte[] timeBytes = ByteUtil.toBytes(timeNumber);
System.arraycopy(timeBytes, 2, bytes, 0, 6); System.arraycopy(timeBytes, 2, bytes, 0, 6);
String randomnessComponent = ulid.substring(10, 26); final String randomnessComponent = ulid.substring(10, 26);
byte[] randBytes = Base32Util.fromBase32Crockford(randomnessComponent); byte[] randBytes = Base32Util.fromBase32Crockford(randomnessComponent);
System.arraycopy(randBytes, 0, bytes, 6, 10); System.arraycopy(randBytes, 0, bytes, 6, 10);

View File

@ -0,0 +1,89 @@
package com.github.f4b6a3;
// Add theese dependencies to pom.xml:
//
// <dependency>
// <groupId>org.openjdk.jmh</groupId>
// <artifactId>jmh-core</artifactId>
// <version>1.23</version>
// </dependency>
// <dependency>
// <groupId>org.openjdk.jmh</groupId>
// <artifactId>jmh-generator-annprocess</artifactId>
// <version>1.23</version>
// </dependency>
/*
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import com.github.f4b6a3.ulid.UlidCreator;
@Threads(1)
@State(Scope.Thread)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
public class Benchmarks {
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public String getUlidThroughput() {
return UlidCreator.getUlid();
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public String getUlidAverage() {
return UlidCreator.getUlid();
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public UUID getGuidThroughput() {
return UlidCreator.getGuid();
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public UUID getGuidAverage() {
return UlidCreator.getGuid();
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public UUID getRandomUUIDThroughput() {
return UUID.randomUUID();
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public UUID getRandomUUIDAverage() {
return UUID.randomUUID();
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder().include(MyBenchmark.class.getSimpleName()).forks(1).build();
new Runner(opt).run();
}
}
*/

View File

@ -7,7 +7,7 @@ public class DemoTest {
private static final String HORIZONTAL_LINE = "----------------------------------------"; private static final String HORIZONTAL_LINE = "----------------------------------------";
public static void printList() { public static void printList() {
int max = 1_000; int max = 100;
System.out.println(HORIZONTAL_LINE); System.out.println(HORIZONTAL_LINE);
System.out.println("### ULID"); System.out.println("### ULID");

View File

@ -182,8 +182,8 @@ public class UlidUtilTest {
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
UUID uuid = UlidCreator.getFastGuid(); UUID uuid1 = UlidCreator.getGuid();
String ulid = UlidUtil.fromUuidToUlid(uuid); String ulid = UlidUtil.fromUuidToUlid(uuid1);
assertTrue("ULID is null", ulid != null); assertTrue("ULID is null", ulid != null);
assertTrue("ULID is empty", !ulid.isEmpty()); assertTrue("ULID is empty", !ulid.isEmpty());
@ -191,12 +191,57 @@ public class UlidUtilTest {
assertTrue("ULID is not valid", UlidUtil.isValid(ulid, /* strict */ assertTrue("ULID is not valid", UlidUtil.isValid(ulid, /* strict */
true)); true));
UUID result = UlidUtil.fromUlidToUuid(ulid); UUID uuid2 = UlidUtil.fromUlidToUuid(ulid);
assertEquals("Result ULID is different from original ULID", uuid, result); assertEquals("Result ULID is different from original ULID", uuid1, uuid2);
} }
} }
@Test
public void testToAndFromBytes() {
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
String ulid1 = UlidCreator.getUlid();
byte[] bytes = UlidUtil.fromUlidToBytes(ulid1);
String ulid2 = UlidUtil.fromBytesToUlid(bytes);
// Check ULID 1
assertTrue(ulid1 != null);
assertTrue(!ulid1.isEmpty());
assertTrue(ulid1.length() == ULID_LENGTH);
assertTrue(UlidUtil.isValid(ulid1, /* strict */ true));
// Check ULID 2
assertTrue(ulid2 != null);
assertTrue(!ulid2.isEmpty());
assertTrue(ulid2.length() == ULID_LENGTH);
assertTrue(UlidUtil.isValid(ulid2, /* strict */ true));
assertEquals(ulid1, ulid2);
}
}
@Test
public void testFromUuidToBytes() {
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
UUID uuid1 = UlidCreator.getGuid();
byte[] bytes = UlidUtil.fromUuidToBytes(uuid1);
long msb = ByteUtil.toNumber(ByteUtil.copy(bytes, 0, 8));
long lsb = ByteUtil.toNumber(ByteUtil.copy(bytes, 8, 16));
UUID uuid2 = new UUID(msb, lsb);
assertEquals(uuid1, uuid2);
}
}
@Test
public void testFromBytesToUuid() {
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
UUID uuid1 = UlidCreator.getGuid();
byte[] bytes = UlidUtil.fromUuidToBytes(uuid1);
UUID uuid2 = UlidUtil.fromBytesToUuid(bytes);
assertEquals(uuid1, uuid2);
}
}
private String leftPad(String unpadded) { private String leftPad(String unpadded) {
return "0000000000".substring(unpadded.length()) + unpadded; return "0000000000".substring(unpadded.length()) + unpadded;
} }