Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 20 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ To include `xdagj-native-randomx` in your project, add the following dependency
<dependency>
<groupId>io.xdag</groupId>
<artifactId>xdagj-native-randomx</artifactId>
<version>0.2.0</version>
<version>0.2.2</version>
</dependency>
```

Expand All @@ -138,27 +138,27 @@ public class Example {
byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);

// Get supported RandomX flags for the current CPU
Set<RandomXFlag> flags = RandomXUtils.getFlagsSet();
Set<RandomXFlag> flags = RandomXUtils.getRecommendedFlags();
System.out.println("Supported flags: " + flags);
int combinedFlags = RandomXFlag.toValue(flags);
System.out.println("Combined flags value: " + combinedFlags);

// Initialize RandomX cache with the supported flags
// Initialize RandomX cache (key will be set via template)
RandomXCache cache = new RandomXCache(flags);
cache.init(keyBytes);

// Create and configure RandomXTemplate using builder pattern
byte[] hash;
try (RandomXTemplate template = RandomXTemplate.builder()
.cache(cache)
.miningMode(false) // Set to false for normal hashing mode
.miningMode(false)
.flags(flags)
.build()) {

// Initialize the template with the configured settings
// Set the key for RandomX operations. This will initialize the cache.
template.changeKey(keyBytes);

// Initialize the template's VM with the configured settings
template.init();

// Calculate hash of the input key
// Calculate hash of the input
hash = template.calculateHash(keyBytes);
}

Expand All @@ -184,10 +184,10 @@ public class Example {
### Linux Performance Results
| Benchmark | Mode | Cnt | Score | Error | Units |
|:------------------------------:|:-----:|:---:|:-------:|:------:|:-----:|
| RandomXBenchmark.lightBatch | thrpt | | 328.736 | | ops/s |
| RandomXBenchmark.lightNoBatch | thrpt | | 325.383 | | ops/s |
| RandomXBenchmark.miningBatch | thrpt | | 2777.939 | | ops/s |
| RandomXBenchmark.miningNoBatch | thrpt | | 2817.811 | | ops/s |
| RandomXBenchmark.lightBatch | thrpt | | 416.114 | | ops/s |
| RandomXBenchmark.lightNoBatch | thrpt | | 424.865 | | ops/s |
| RandomXBenchmark.miningBatch | thrpt | | 1818.991 | | ops/s |
| RandomXBenchmark.miningNoBatch | thrpt | | 2191.774 | | ops/s |

---

Expand All @@ -196,17 +196,15 @@ public class Example {
- **CPU**: Apple M3 Pro
- **RAM**: 36 GB
- **thread**: 8
- **RandomX Flags**: [DEFAULT, HARD_AES, SECURE]

JIT flag will cause jvm to crash in MacOS
- **RandomX Flags**: [DEFAULT, JIT, SECURE]

### MacOS Performance Results
| Benchmark | Mode | Cnt | Score | Error | Units |
|:------------------------------:|:-----:|:---:|:-------:|:------:|:-----:|
| RandomXBenchmark.lightBatch | thrpt | | 32.864 | | ops/s |
| RandomXBenchmark.lightNoBatch | thrpt | | 33.683 | | ops/s |
| RandomXBenchmark.miningBatch | thrpt | | 554.966 | | ops/s |
| RandomXBenchmark.miningNoBatch | thrpt | | 570.060 | | ops/s |
| Benchmark | Mode | Cnt | Score | Error | Units |
|:------------------------------:|:-----:|:---:|:--------:|:------:|:-----:|
| RandomXBenchmark.lightBatch | thrpt | | 416.114 | | ops/s |
| RandomXBenchmark.lightNoBatch | thrpt | | 424.865 | | ops/s |
| RandomXBenchmark.miningBatch | thrpt | | 1818.991 | | ops/s |
| RandomXBenchmark.miningNoBatch | thrpt | | 2191.774 | | ops/s |

---

Expand Down
31 changes: 15 additions & 16 deletions src/main/java/io/xdag/crypto/randomx/Example.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,34 +40,33 @@ public class Example {
* @param args Command line arguments (not used)
*/
public static void main(String[] args) {
// Key to be hashed
// Key (or message) to be hashed
String key = "hello xdagj-native-randomx";
byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
System.out.println("Input message: " + key);

// Get supported RandomX flags for the current CPU
Set<RandomXFlag> flags = RandomXUtils.getFlagsSet();
System.out.println("Supported flags: " + flags);
int combinedFlags = RandomXFlag.toValue(flags);
System.out.println("Combined flags value: " + combinedFlags);
// Get recommended RandomX flags for the current CPU
Set<RandomXFlag> flags = RandomXUtils.getRecommendedFlags();

// Initialize RandomX cache with the supported flags
// Allocate RandomX cache with the recommended flags
// The key will be set and cache initialized via RandomXTemplate
RandomXCache cache = new RandomXCache(flags);
cache.init(keyBytes);

// Create and configure RandomXTemplate using builder pattern
// Create and configure RandomXTemplate using a builder pattern
byte[] hash;
try (RandomXTemplate template = RandomXTemplate.builder()
.cache(cache)
.miningMode(false) // Set to false for normal hashing mode
.flags(flags)
.cache(cache) // Provide the cache instance (not yet initialized with key)
.miningMode(false) // Set to false for light hashing mode (no dataset)
.flags(flags) // Provide the base flags
.build()) {

// Set the key for RandomX operations. This will initialize the cache.
template.changeKey(keyBytes);

// Initialize the template with the configured settings
// Initialize the template. This creates the VM.
template.init();

// Calculate hash of the input key
hash = template.calculateHash(keyBytes);
}
} // try-with-resources automatically calls template.close()

// Format and display the results
HexFormat hex = HexFormat.of();
Expand Down
145 changes: 67 additions & 78 deletions src/main/java/io/xdag/crypto/randomx/RandomXCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,119 +26,108 @@
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.extern.slf4j.Slf4j;

import java.io.Closeable;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;

/**
* A class that encapsulates the RandomX cache functionality.
* This class manages the allocation, initialization and release of RandomX cache memory.
* It implements AutoCloseable to ensure proper resource cleanup.
* Represents a RandomX Cache object.
* This class manages the allocation, initialization, and release of the native RandomX cache structure.
*/
@Getter
public class RandomXCache implements AutoCloseable {
private static final Logger logger = LoggerFactory.getLogger(RandomXCache.class);

/**
* Pointer to the allocated RandomX cache memory.
* -- GETTER --
* Returns the pointer to the allocated cache memory.
*/
@Getter
@Slf4j
public class RandomXCache implements Closeable {
private final Pointer cachePointer;

/**
* Pointer to the key used for cache initialization
*/
private volatile Pointer keyPointer;
private int keyLength;
private final ReentrantLock lock = new ReentrantLock();
@Getter
private final Set<RandomXFlag> flags;

/**
* Constructs a new RandomXCache with the specified flags.
* Allocates memory for the cache using the native RandomX library.
* Allocates a new RandomX cache.
*
* @param flags Set of RandomXFlag values that configure the cache behavior
* @throws IllegalArgumentException if flags is null or empty
* @throws IllegalStateException if cache allocation fails
* @param flags Flags used to initialize the cache.
* @throws RuntimeException if cache allocation fails.
*/
public RandomXCache(Set<RandomXFlag> flags) {
if (flags == null || flags.isEmpty()) {
throw new IllegalArgumentException("Flags cannot be null or empty");
}

this.flags = flags;
int combinedFlags = RandomXFlag.toValue(flags);
logger.debug("Allocating RandomX cache with flags: {}", flags);

this.cachePointer = RandomXJNALoader.getInstance().randomx_alloc_cache(combinedFlags);
log.debug("Allocating RandomX cache with flags: {} ({})", flags, combinedFlags);
// Use RandomXNative for allocation
this.cachePointer = RandomXNative.randomx_alloc_cache(combinedFlags);
if (this.cachePointer == null) {
throw new IllegalStateException("Failed to allocate RandomX cache");
String errorMsg = String.format("Failed to allocate RandomX cache with flags: %s (%d)", flags, combinedFlags);
log.error(errorMsg);
throw new RuntimeException(errorMsg);
}

logger.debug("RandomX cache allocated successfully");
log.info("RandomX cache allocated successfully at pointer: {}", Pointer.nativeValue(this.cachePointer));
}

/**
* Initializes the cache with the provided key.
* This method is thread-safe and can be called multiple times with different keys.
* Initializes the RandomX cache with the specified key.
*
* @param key byte array containing the key data
* @throws IllegalArgumentException if key is null or empty
* @param key Key (seed) used to initialize the cache.
* @throws RuntimeException if cache initialization fails.
* @throws IllegalStateException if the cache is not allocated.
*/
public void init(byte[] key) {
if (cachePointer == null) {
throw new IllegalStateException("Cache is not allocated.");
}
if (key == null || key.length == 0) {
throw new IllegalArgumentException("Key cannot be null or empty");
throw new IllegalArgumentException("Key cannot be null or empty for cache initialization.");
}

lock.lock();
// Use JNA Memory to manage native memory
Memory keyPointer = new Memory(key.length);
try {
long startTime = System.nanoTime();
logger.debug("Initializing cache with key length: {}", key.length);

// Free old key pointer if exists
if (keyPointer != null) {
keyPointer.clear(keyLength);
}

// Allocate and initialize new key
keyLength = key.length;
keyPointer = new Memory(key.length);
keyPointer.write(0, key, 0, key.length);

RandomXJNALoader.getInstance().randomx_init_cache(
this.cachePointer,
keyPointer,
key.length
log.debug("Initializing RandomX cache with key of length: {}", key.length);
// Use RandomXNative for initialization
RandomXNative.randomx_init_cache(
this.cachePointer,
keyPointer,
key.length
);

long endTime = System.nanoTime();
logger.debug("Cache initialization completed in {} ms", (endTime - startTime) / 1_000_000);
log.info("RandomX cache initialized successfully.");
} catch (Exception e) {
log.error("Failed to initialize RandomX cache", e);
// Even if initialization fails, attempt to release memory
close(); // Release cachePointer
throw new RuntimeException("Failed to initialize RandomX cache", e);
} finally {
lock.unlock();
// Memory objects do not need to be manually released; JNA's GC will handle it,
// but nullifying the reference immediately might help GC reclaim it faster.
keyPointer = null; // Help GC
}
}

/**
* Releases the allocated cache memory and key memory.
* This method is thread-safe and idempotent.
* Gets the pointer to the underlying native RandomX cache structure.
*
* @return Pointer to the native cache.
* @throws IllegalStateException if the cache is not allocated.
*/
public Pointer getCachePointer() {
if (cachePointer == null) {
throw new IllegalStateException("Cache is not allocated.");
}
return cachePointer;
}

/**
* Releases the resources occupied by the native RandomX cache.
* This method should be called after finishing with the cache to prevent memory leaks.
*/
@Override
public void close() {
lock.lock();
try {
if (keyPointer != null) {
keyPointer.clear(keyLength);
keyPointer = null;
if (cachePointer != null) {
log.debug("Releasing RandomX cache at pointer: {}", Pointer.nativeValue(cachePointer));
try {
// Use RandomXNative for release
RandomXNative.randomx_release_cache(cachePointer);
log.info("RandomX cache released successfully");
} catch (Throwable t) {
log.error("Error occurred while releasing RandomX cache. Pointer: {}", Pointer.nativeValue(cachePointer), t);
}

if (cachePointer != null) {
RandomXJNALoader.getInstance().randomx_release_cache(cachePointer);
logger.debug("RandomX cache released successfully");
}
} finally {
lock.unlock();
}
}
}
Loading
Loading