From 09d8264a9eb7b3ed57ded6d7f8fd995ad612245d Mon Sep 17 00:00:00 2001 From: MASES Public Developers Team <94312179+masesdevelopers@users.noreply.github.com> Date: Tue, 14 May 2024 02:22:43 +0200 Subject: [PATCH] Updated `ByteBuffer` to optimize data exchange at JVM-CLR boundary (#394) * Update to JCOBridge 2.5.12 * Added management of data transfer based on ByteBuffer * Added documentation related to performances, with some info on improvements based on ByteBuffer * Update and align test program --- .github/workflows/build.yaml | 17 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/generateclasses.yaml | 2 +- .github/workflows/pullrequest.yaml | 4 +- .github/workflows/release.yaml | 4 +- README.md | 3 + src/documentation/articles/performancetips.md | 198 +++++++++++++++ src/documentation/articles/toc.yml | 6 +- src/documentation/articles/usage.md | 14 +- src/jvm/jnet/pom.xml | 2 +- .../main/java/org/mases/jnet/JNetHelper.java | 170 +++++++++++++ src/net/Common/Common.props | 2 +- src/net/JNet/Developed/Java/Nio/ByteBuffer.cs | 165 +++++++++++- src/net/JNet/Developed/Java/Nio/CharBuffer.cs | 73 ++++++ .../JNet/Developed/Java/Nio/DoubleBuffer.cs | 73 ++++++ .../JNet/Developed/Java/Nio/FloatBuffer.cs | 73 ++++++ src/net/JNet/Developed/Java/Nio/IntBuffer.cs | 73 ++++++ src/net/JNet/Developed/Java/Nio/LongBuffer.cs | 73 ++++++ .../JNet/Developed/Java/Nio/ShortBuffer.cs | 73 ++++++ src/net/JNet/JNet.csproj | 43 ++-- .../Specific/Extensions/JNetCoreExtensions.cs | 36 +++ src/net/JNet/Specific/JNetHelper.cs | 160 ++++++++++++ src/net/JNetReflector/JNetReflector.csproj | 2 +- .../jcobridgeConsoleApp.csproj | 4 +- .../templates/jnetAWTApp/jnetAWTApp.csproj | 2 +- .../templates/jnetApp/jnetApp.csproj | 2 +- tests/jvm/testclass/pom.xml | 236 +++++++++++++++++- .../mases/jnet/TestArrayAndByteBuffer.java | 51 ++++ tests/net/Common/Initializer.cs | 18 +- .../JNetByteBufferTest.csproj | 13 + tests/net/JNetByteBufferTest/Program.cs | 176 +++++++++++++ tests/net/JNetTest.sln | 7 + tests/net/JNetTest/Program.cs | 174 ++++++++++--- 33 files changed, 1872 insertions(+), 79 deletions(-) create mode 100644 src/documentation/articles/performancetips.md create mode 100644 src/jvm/jnet/src/main/java/org/mases/jnet/JNetHelper.java create mode 100644 src/net/JNet/Specific/JNetHelper.cs create mode 100644 tests/jvm/testclass/src/main/java/org/mases/jnet/TestArrayAndByteBuffer.java create mode 100644 tests/net/JNetByteBufferTest/JNetByteBufferTest.csproj create mode 100644 tests/net/JNetByteBufferTest/Program.cs diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d93299fb70..16f8e038d3 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -83,7 +83,7 @@ jobs: gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase - name: Install local file to be used within Javadoc plugin of generated POM - run: mvn install:install-file -DgroupId=JCOBridge -DartifactId=JCOBridge -Dversion=2.5.11 -Dpackaging=jar -Dfile=../../../bin/net8.0/JCOBridge.jar -f ./src/jvm/jnet/pom.xml + run: mvn install:install-file -DgroupId=JCOBridge -DartifactId=JCOBridge -Dversion=2.5.12 -Dpackaging=jar -Dfile=../../../bin/net8.0/JCOBridge.jar -f ./src/jvm/jnet/pom.xml shell: bash - name: Create Jars @@ -146,16 +146,23 @@ jobs: gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase - name: Install local file to be used within Javadoc plugin of generated POM - run: mvn install:install-file -DgroupId=JCOBridge -DartifactId=JCOBridge -Dversion=2.5.11 -Dpackaging=jar -Dfile=../../../bin/net8.0/JCOBridge.jar -f ./src/jvm/jnet/pom.xml + run: mvn install:install-file -DgroupId=JCOBridge -DartifactId=JCOBridge -Dversion=2.5.12 -Dpackaging=jar -Dfile=../../../bin/net8.0/JCOBridge.jar -f ./src/jvm/jnet/pom.xml shell: bash - - name: Create Jars + - name: Create Jars for JNet run: mvn --file ./src/jvm/jnet/pom.xml --no-transfer-progress package env: MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} + - name: Create Jars for tests + run: mvn --file ./tests/jvm/testclass/pom.xml --no-transfer-progress package + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} + - name: Compile JNetCLI run: dotnet build --no-incremental --configuration Release /p:Platform="Any CPU" /p:NoWarn="0108%3B1030%3B0618" src\net\JNetCLI\JNetCLI.csproj @@ -190,6 +197,8 @@ jobs: - name: Recompile to create nuget packages run: dotnet build --no-incremental --configuration Release /p:Platform="Any CPU" /p:NoWarn="0108%3B1030%3B0618" src\net\JNet.sln + env: + GITHUB_TEST_PREPARATION: true - uses: actions/upload-artifact@v4 with: @@ -209,7 +218,7 @@ jobs: fail-fast: false matrix: os: [ 'ubuntu-latest', 'windows-latest' ] - framework: [ 'net462', 'net6.0', 'net7.0', 'net8.0' ] + framework: [ 'net462', 'net6.0', 'net8.0' ] jdk_vendor: [ 'temurin', 'zulu', 'microsoft', 'corretto', 'oracle'] jdk_version: [ '11', '17', '21' ] # only LTS versions exclude: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 51b0a44865..ac171add75 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -118,7 +118,7 @@ jobs: - name: Maven preparation (step 2) if: matrix.language == 'java' - run: mvn install:install-file -DgroupId=JCOBridge -DartifactId=JCOBridge -Dversion=2.5.11 -Dpackaging=jar -Dfile=../../../bin/net6.0/JCOBridge.jar -f ./src/jvm/jnet/pom.xml + run: mvn install:install-file -DgroupId=JCOBridge -DartifactId=JCOBridge -Dversion=2.5.12 -Dpackaging=jar -Dfile=../../../bin/net6.0/JCOBridge.jar -f ./src/jvm/jnet/pom.xml shell: bash - if: matrix.language == 'java' diff --git a/.github/workflows/generateclasses.yaml b/.github/workflows/generateclasses.yaml index e64c57e028..fe15cce20c 100644 --- a/.github/workflows/generateclasses.yaml +++ b/.github/workflows/generateclasses.yaml @@ -48,7 +48,7 @@ jobs: gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase - name: Install local file to be used within Javadoc plugin of generated POM - run: mvn install:install-file -DgroupId=JCOBridge -DartifactId=JCOBridge -Dversion=2.5.11 -Dpackaging=jar -Dfile=../../../binReflector/net6.0/JCOBridge.jar -f ./src/jvm/jnet/pom.xml + run: mvn install:install-file -DgroupId=JCOBridge -DartifactId=JCOBridge -Dversion=2.5.12 -Dpackaging=jar -Dfile=../../../binReflector/net6.0/JCOBridge.jar -f ./src/jvm/jnet/pom.xml shell: bash - name: Create Jars diff --git a/.github/workflows/pullrequest.yaml b/.github/workflows/pullrequest.yaml index eb802dab49..929caab053 100644 --- a/.github/workflows/pullrequest.yaml +++ b/.github/workflows/pullrequest.yaml @@ -81,7 +81,7 @@ jobs: gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase - name: Install local file to be used within Javadoc plugin of generated POM - run: mvn install:install-file -DgroupId=JCOBridge -DartifactId=JCOBridge -Dversion=2.5.11 -Dpackaging=jar -Dfile=../../../bin/net8.0/JCOBridge.jar -f ./src/jvm/jnet/pom.xml + run: mvn install:install-file -DgroupId=JCOBridge -DartifactId=JCOBridge -Dversion=2.5.12 -Dpackaging=jar -Dfile=../../../bin/net8.0/JCOBridge.jar -f ./src/jvm/jnet/pom.xml shell: bash - name: Create Jars @@ -132,7 +132,7 @@ jobs: gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase - name: Install local file to be used within Javadoc plugin of generated POM - run: mvn install:install-file -DgroupId=JCOBridge -DartifactId=JCOBridge -Dversion=2.5.11 -Dpackaging=jar -Dfile=../../../bin/net8.0/JCOBridge.jar -f ./src/jvm/jnet/pom.xml + run: mvn install:install-file -DgroupId=JCOBridge -DartifactId=JCOBridge -Dversion=2.5.12 -Dpackaging=jar -Dfile=../../../bin/net8.0/JCOBridge.jar -f ./src/jvm/jnet/pom.xml shell: bash - name: Create Jars diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 62a4244960..ec70acbc70 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -10,7 +10,7 @@ on: - published # - edited -jobs: +jobs: # This workflow contains a single job called "build_release" build_release: # The type of runner that the job will run on @@ -44,7 +44,7 @@ jobs: gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase - name: Install local file to be used within Javadoc plugin of generated POM - run: mvn install:install-file -DgroupId=JCOBridge -DartifactId=JCOBridge -Dversion=2.5.11 -Dpackaging=jar -Dfile=../../../bin/net8.0/JCOBridge.jar -f ./src/jvm/jnet/pom.xml + run: mvn install:install-file -DgroupId=JCOBridge -DartifactId=JCOBridge -Dversion=2.5.12 -Dpackaging=jar -Dfile=../../../bin/net8.0/JCOBridge.jar -f ./src/jvm/jnet/pom.xml shell: bash - name: Create Jars diff --git a/README.md b/README.md index 880410d65f..3873ac7c7d 100644 --- a/README.md +++ b/README.md @@ -60,11 +60,14 @@ This project adheres to the Contributor [Covenant code of conduct](CODE_OF_CONDU * [Roadmap](src/documentation/articles/roadmap.md) * [Current state](src/documentation/articles/currentstate.md) * [JNet usage](src/documentation/articles/usage.md) +* [JNet performance tips](src/documentation/articles/performancetips.md) * [JNet APIs extensibility](src/documentation/articles/API_extensibility.md) +* [JNet JVM callbacks](src/documentation/articles/jvm_callbacks.md) * [JNet CLI usage](src/documentation/articles/usageCLI.md) * [JNet Docker usage](src/documentation/articles/docker.md) * [JNet Reflector usage](src/documentation/articles/usageReflector.md) * [JNet PowerShell usage](src/documentation/articles/usagePS.md) +* [JNet Command-line switches](src/documentation/articles/commandlineswitch.md) ### News diff --git a/src/documentation/articles/performancetips.md b/src/documentation/articles/performancetips.md new file mode 100644 index 0000000000..265a5b258c --- /dev/null +++ b/src/documentation/articles/performancetips.md @@ -0,0 +1,198 @@ +--- +title: Performance tips of Java/JVM suite for .NET +_description: Describes some tips about performance of Java/JVM suite for .NET +--- + +# JNet performance tips + +This article tries to report some tips can be used when the user faces with the performances, meanwhile explains the reasons. + +## Reduce CLR-JVM boundary invocations + +The library tries to optimize the usage of invocations at CLR-JVM boundary, however the user shall avoid an expansive usage of methods if not strictly needed. +Consider the following code (available in the __tests__ folder projects): + +```c# +const int execution = 10000; +Java.Util.ArrayList alist = new Java.Util.ArrayList(); +for (int i = 0; i < execution; i++) +{ + alist.Add(i); +} +``` + +The previous code creates a `Java.Util.ArrayList` and fills it, within the loop, invoking `Add` method: the fill operation is very expensive because on every loop the CLR-JVM boundary shall be traversed. +The previous is only an example of the impact of CLR-JVM boundary when performance is a key element. + +### JNet helper class + +The specific previous example can be optimized with some features of JNet available to build `java.util.List` of primitive types starting from array of primitive types of the CLR. + +#### JNet helper class based on array transfer + +An optimized way to allocate a `Java.Util.ArrayList` uses the `JNetHelper` class available in JNet: + +```c# +const int execution = 10000; +int[] tmpArray = new int[execution]; +var tmpJList = JNetHelper.ListFrom(tmpArray); +Java.Util.ArrayList alist = new Java.Util.ArrayList(tmpJList); +``` + +the previous code (also available in the __tests__ folder projects) move the primitive array using few invocations at CLR-JVM boundary, the final job is done in the JVM where the [`java.util.List`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/util/List.html) is created and then it is returned to the CLR. + +#### JNet helper class based on java.nio.*Buffer + +The previous code can uses a [`java.nio.ByteBuffer`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/nio/ByteBuffer.html) to transfrer data using other overloads of `JNetHelper.ListFrom`: + +```c# +const int execution = 10000; +int[] tmpArray = new int[execution]; +var intBuffer = IntBuffer.From(tmpArray, false, false); +var tmpJList = JNetHelper.ListFrom(intBuffer); +Java.Util.ArrayList alist = new Java.Util.ArrayList(tmpJList); +``` + +or + +```c# +const int execution = 10000; +int[] tmpArray = new int[execution]; +var tmpJList = JNetHelper.ListFrom(tmpArray, true); +Java.Util.ArrayList alist = new Java.Util.ArrayList(tmpJList); +``` + +Both examples uses a shared memory to move memory from CLR to JVM, the difference is mainly: +- in the first example the user allocates the `IntBuffer` and can reuse it for other reasons, e.g. refill it with other data to be sent to the JVM removing the need to create a new one +- in the second example an `IntBuffer` is allocated behind the scene each time the `JNetHelper.ListFrom` is invoked. + +### Performance comparison and tips + +Considering all examples it is possible to highligh how invocations at CLR-JVM boundary impacts on performance. +The JNetTest project (available in the __tests__ folder projects) executes a timing comparison and the synthesis of the results is the following: +- First example is always the most impacted; using it as reference... +- ...when the JNet helper, based on array transfer, is used the speed is more or less 100 times higher +- ...when the JNet helper, based on java.nio.*Buffer, is used the speed can be 140 times higher +- Building a `System.Collections.Generic.List` directly in the CLR the speed can be 1000 times higher + +> [!TIP] +> From the previous it is possible to report three things: +> - when it is possible, the user shall avoid the invocation of methods in the JVM from the CLR to reduce their impact on performances; +> - if it possible the invocations at CLR-JVM boundary shall be collapsed just like JNetHelper does; +> - if something can be made entirely in CLR or JVM try to do it in that environment until invocation at CLR-JVM boundary is really needed. + +## Memory transfer at CLR-JVM boundary + +Latest version of JNet comes with many new APIs to manage data exchange at CLR-JVM boundary using [`java.nio.ByteBuffer`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/nio/ByteBuffer.html). + +The [chapter](#JNet-helper-class-based-on-java.nio.*Buffer) introduces one of the usage of the new APIs in a specific context behind an helper class, however [`java.nio.ByteBuffer`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/nio/ByteBuffer.html) can be used starting from many types of CLR inputs: +- from an `IntPtr` +- from a `System.IO.MemoryStream` +- from an array of primitive types (`byte`, `short`, `int`, `long`, and so on) + +All previous variants are available because there many possible needs, here some examples: +- An `IntPtr`, representing a generic native memory pointer, can be the address of a memory allocated from COM (Component Object Model), a pointer to a unmanaged memory or other cases +- A `System.IO.MemoryStream` can be the output of some operations (e.g. a JSON serialization before convert it to an array of bytes) +- An array of `byte`s can be a file read from the disk, information from a socket and so on + +JNI (CLR-JVM boundary) comes with many methods to transfer data using array of primitivess, but in some conditions both CLR and JVM can make multiple copies of the memory reducing the performances. +An example can be a `System.IO.MemoryStream` holding information to be transferred in the JVM: +1. the data within the `System.IO.MemoryStream` shall be extracted and converted into an array of `byte`s; +2. the array of `byte`s shall be allocated from the CLR then the content of `System.IO.MemoryStream` shall be copied in it; +3. the array of `byte`s shall be transferred to the JVM and this operation needs to: + a. create a new array in the JVM with length equals to the CLR array + b. execute a memory copy from CLR memory to the JVM memory +4. the operation 3.b can be impacted, depending on the JVM, from a further copy becuase the JVM itself can decide to use a temporary array reference at JNI (CLR-JVM boundary) level +5. then the JVM can use the data sent from the CLR. + +With the new APIs available in JNet the previous steps becomes: +1. A [`java.nio.ByteBuffer`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/nio/ByteBuffer.html) is created diretly from `System.IO.MemoryStream`; +2. the reference to [`java.nio.ByteBuffer`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/nio/ByteBuffer.html) is sent to the JVM, but the memory isn't moved; +3. then the JVM can directly access the CLR memory using [`public abstract byte get()`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/nio/ByteBuffer.html#get()) or [`public abstract byte get(int index)`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/nio/ByteBuffer.html#get(int)) methods. + +> [!IMPORTANT] +> Other methods can be used to retrieve an array of `byte`s if the code needs that kind of type, however it is important to notice that an user shall avoid the [`public ByteBuffer get(byte[] dst)`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/nio/ByteBuffer.html#get(byte%5B%5D)) method because the underlying implementation executes a byte-to-byte copy, while it is preferable the [`public ByteBuffer get(byte[] dst, int offset, int length)`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/nio/ByteBuffer.html#get(byte%5B%5D,int,int)) method because it executes a copy based on block of bytes. + +### The impact of array creation + +In the previous chapter was reported that an user can obtain an array of `byte`s (or any other primitive) by method parameter or reading data transferred using a [`java.nio.ByteBuffer`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/nio/ByteBuffer.html). +If an array of primitives is needed the JVM (or the CLR) is heavely impacted because the JVM (or the CLR) must execute some work to obtain it. + +> [!NOTE] +> The work mentioned before does not change if the array comes from JNI (CLR-JVM boundary) or from [`java.nio.ByteBuffer`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/nio/ByteBuffer.html). + +The JNetByteBufferTest project (available in the __tests__ folder projects) executes a set of different tests comparing some cases: +1. executes a transfer using array of `byte`s, and it is used as reference +2. then executes memory transfer using [`java.nio.ByteBuffer`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/nio/ByteBuffer.html) and reads the data allocating, on each execution, a new array of `byte`s +3. then executes memory transfer using [`java.nio.ByteBuffer`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/nio/ByteBuffer.html) and reads the data reusing, on each execution, a previously allocated array of `byte`s +4. then executes memory transfer using [`java.nio.ByteBuffer`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/nio/ByteBuffer.html) without reads the data + +The previous steps are done both from CLR to the JVM, then from JVM to the CLR, repeating them many times and using different array lengths; from the tests executed it is possible to highlight that: + +- test cases 1 and 2: + - array of `byte`s performs better than [`java.nio.ByteBuffer`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/nio/ByteBuffer.html) mainly for smallest arrays in both directions + - [`java.nio.ByteBuffer`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/nio/ByteBuffer.html) not always performs better than array of `byte`s and the reason can be found in the allocation of array of `byte`s requested from JVM, and CLR, on each execution + +- test case 3: + - in general, avoiding array allocation gives an enhancement of 4/5 times than transfer made with array of `byte`s + - currently, transferring few data (smallest length) from JVM to CLR, array of `byte`s performs better than [`java.nio.ByteBuffer`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/nio/ByteBuffer.html) + +- test case 4: + - uncomparable with the other tests, it is useful to measure the transfer of the [`java.nio.ByteBuffer`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/nio/ByteBuffer.html) which is acting as a pointer to the JVM (or CLR) memory + +> [!TIP] +> For test case 4: if the user implements a sparse access to the memory transferred using a [`java.nio.ByteBuffer`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/nio/ByteBuffer.html), the code does not need to move memory (or make a copy) with array of `byte`s, gaining in performance. + +### Performance and tips + +From the previous chapter it is clear that memory allocation is a key to be considered in performances because the memory shall be found from the JVM, or CLR, then it shall be released. + +> [!TIP] +> When it is possible, try to reuse the previously allocated arrays to avoid an expansive load on JVM, or CLR, both during creation and when the arrays are garbage collected. + +## Execute iterations in parallel + +The chapter [Performance comparison and tips](#Performance-comparison-and-tips) recommends to reduce the number of invocations at CLR-JVM boundary to the minimum possible, JNetHelper is a great helper when the user is facing with primitives and [Memory transfer at CLR-JVM boundary](#Memory-transfer-at-CLR-JVM-boundary) explain how to optimize memory transfer. +However in many conditions the user shall executes iterations on objects which cannot be managed like primitive types. To explain better, consider the following snippet: + +```c# +ArrayList alist = GetAnArrayListOfString(); +foreach (Java.Lang.String item in alist) +{ + // EXPENSIVE OPERATION OVER item +} +``` + +the code requests a `Java.Lang.String` using the `Java.Lang.Iterable` interface, then executes an EXPENSIVE operation over it: when the CLR is executing the operation the JVM does not do anything with the `Java.Lang.Iterable` interface, the next time the CLR code requests a new item the `Java.Lang.Iterable` interface is impacted. +In general this means that: +1. the next **EXPENSIVE OPERATION OVER item** shall wait that the item is received from the JVM +2. while **EXPENSIVE OPERATION OVER item** is executed the JVM is idle + +To speed up the iteration it is possible to use the `WithPrefetch` extension method like in the following sinppet: + +```c# +ArrayList alist = GetAnArrayListOfString(); +foreach (Java.Lang.String item in alist.WithPrefetch()) +{ + // EXPENSIVE OPERATION OVER item +} +``` + +`WithPrefetch` returns a new iterator that retrieves data from JVM in parallel: while **EXPENSIVE OPERATION OVER item** is executed, a new item is received from the JVM. + +A further improvement can be obtained using `WithThread` extension method like in: + +```c# +ArrayList alist = GetAnArrayListOfString(); +foreach (Java.Lang.String item in alist.WithPrefetch().WithThread()) +{ + // EXPENSIVE OPERATION OVER item +} +``` + +`WithThread` creates an external native thread which executes the retrieve operation. + +> [!TIP] +> It is preferable to use `WithPrefetch` and `WithThread` when the number of items are high and the operation over item is expensive; otherwise the time spent to allocate the external native thread and manage the second iterator is more than the operation executed with classic iterator. + + diff --git a/src/documentation/articles/toc.yml b/src/documentation/articles/toc.yml index 17bb4be4f2..8ec040f8ad 100644 --- a/src/documentation/articles/toc.yml +++ b/src/documentation/articles/toc.yml @@ -4,12 +4,14 @@ href: roadmap.md - name: Current state href: currentstate.md +- name: JNet usage + href: usage.md +- name: JNet performance tips + href: performancetips.md - name: APIs extendibility href: API_extensibility.md - name: JVM callbacks href: jvm_callbacks.md -- name: JNet usage - href: usage.md - name: JNet CLI usage href: usageCLI.md - name: JNet Docker images diff --git a/src/documentation/articles/usage.md b/src/documentation/articles/usage.md index 936741d5cb..b835804de6 100644 --- a/src/documentation/articles/usage.md +++ b/src/documentation/articles/usage.md @@ -18,17 +18,17 @@ One of the most important command-line switch is **JVMPath** and it is available If a developer is using JNet within its own product it is possible to override the **JVMPath** property with a snippet like the following one: ```c# - class MyJNetCore : JNetCore +class MyJNetCore : JNetCore +{ + public override string JVMPath { - public override string JVMPath + get { - get - { - string pathToJVM = "Set here the path to JVM library or use your own search method"; - return pathToJVM; - } + string pathToJVM = "Set here the path to JVM library or use your own search method"; + return pathToJVM; } } +} ``` **IMPORTANT NOTE**: `pathToJVM` shall be escaped diff --git a/src/jvm/jnet/pom.xml b/src/jvm/jnet/pom.xml index aab5356015..0d831094e2 100644 --- a/src/jvm/jnet/pom.xml +++ b/src/jvm/jnet/pom.xml @@ -41,7 +41,7 @@ 11 ${basedir}/classpathfile.classpath 2.4.0.0 - 2.5.11 + 2.5.12 ../../../bin/net6.0/JCOBridge.jar diff --git a/src/jvm/jnet/src/main/java/org/mases/jnet/JNetHelper.java b/src/jvm/jnet/src/main/java/org/mases/jnet/JNetHelper.java new file mode 100644 index 0000000000..0865506c38 --- /dev/null +++ b/src/jvm/jnet/src/main/java/org/mases/jnet/JNetHelper.java @@ -0,0 +1,170 @@ +/* + * Copyright 2024 MASES s.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Refer to LICENSE for more information. + */ + +package org.mases.jnet; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.LongBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class JNetHelper { + public static List listFromPrimitiveArray(Object input) { + if (input == null) throw new IllegalArgumentException("Input parameter is null"); + + if (input instanceof boolean[]) { + final List l = new ArrayList(); + for (final boolean s : (boolean[])input) { + l.add(s); + } + return l; + } + else if (input instanceof byte[]) { + final List l = new ArrayList(); + for (final byte s : (byte[])input) { + l.add(s); + } + return l; + } + else if (input instanceof char[]) { + final List l = new ArrayList(); + for (final char s : (char[])input) { + l.add(s); + } + return l; + } + else if (input instanceof short[]) { + final List l = new ArrayList(); + for (final short s : (short[])input) { + l.add(s); + } + return l; + } + else if (input instanceof int[]) { + Integer[] array = Arrays.stream((int[])input).boxed().toArray(Integer[]::new); + return Arrays.asList(array); + } + else if (input instanceof long[]) { + Long[] array = Arrays.stream((long[])input).boxed().toArray(Long[]::new); + return Arrays.asList(array); + } + else if (input instanceof float[]) { + final List l = new ArrayList(); + for (final float s : (float[])input) { + l.add(s); + } + return l; + } + else if (input instanceof double[]) { + Double[] array = Arrays.stream((double[])input).boxed().toArray(Double[]::new); + return Arrays.asList(array); + } + + if(!input.getClass().isArray()) throw new IllegalArgumentException("Input parameter is not an array"); + + throw new ClassCastException(input.getClass().getName() + " cannot be converted with this function"); + } + + public static List listFromByteBuffer(ByteBuffer input) { + if (input == null) throw new IllegalArgumentException("Input parameter is null"); + + input.rewind(); + byte[] array = new byte[input.remaining()]; + input.get(array, 0, array.length); + final List l = new ArrayList(); + for (final byte s : array) { + l.add(s); + } + return l; + } + + public static List listFromCharBuffer(CharBuffer input) { + if (input == null) throw new IllegalArgumentException("Input parameter is null"); + + input.rewind(); + char[] array = new char[input.remaining()]; + input.get(array, 0, array.length); + final List l = new ArrayList(); + for (final char s : array) { + l.add(s); + } + return l; + } + + public static List listFromDoubleBuffer(DoubleBuffer input) { + if (input == null) throw new IllegalArgumentException("Input parameter is null"); + + input.rewind(); + double[] array = new double[input.remaining()]; + input.get(array, 0, array.length); + Double[] array2 = Arrays.stream(array).boxed().toArray(Double[]::new); + return Arrays.asList(array2); + } + + public static List listFromFloatBuffer(FloatBuffer input) { + if (input == null) throw new IllegalArgumentException("Input parameter is null"); + + input.rewind(); + float[] array = new float[input.remaining()]; + input.get(array, 0, array.length); + final List l = new ArrayList(); + for (final float s : array) { + l.add(s); + } + return l; + } + + public static List listFromIntBuffer(IntBuffer input) { + if (input == null) throw new IllegalArgumentException("Input parameter is null"); + + input.rewind(); + int[] array = new int[input.remaining()]; + input.get(array, 0, array.length); + Integer[] array2 = Arrays.stream(array).boxed().toArray(Integer[]::new); + return Arrays.asList(array2); + } + + public static List listFromLongBuffer(LongBuffer input) { + if (input == null) throw new IllegalArgumentException("Input parameter is null"); + + input.rewind(); + long[] array = new long[input.remaining()]; + input.get(array, 0, array.length); + Long[] array2 = Arrays.stream(array).boxed().toArray(Long[]::new); + return Arrays.asList(array2); + } + + public static List listFromShortBuffer(ShortBuffer input) { + if (input == null) throw new IllegalArgumentException("Input parameter is null"); + + input.rewind(); + short[] array = new short[input.remaining()]; + input.get(array, 0, array.length); + final List l = new ArrayList(); + for (final short s : array) { + l.add(s); + } + return l; + } +} diff --git a/src/net/Common/Common.props b/src/net/Common/Common.props index 33ecfe39eb..6b739c5d71 100644 --- a/src/net/Common/Common.props +++ b/src/net/Common/Common.props @@ -22,7 +22,7 @@ true - net462;net6.0;net7.0;net8.0 + net462;net6.0;net8.0 $(DefineConstants);JNET_DOCKER_BUILD_ACTIONS diff --git a/src/net/JNet/Developed/Java/Nio/ByteBuffer.cs b/src/net/JNet/Developed/Java/Nio/ByteBuffer.cs index be73a9fc6c..20d4a6a7c6 100644 --- a/src/net/JNet/Developed/Java/Nio/ByteBuffer.cs +++ b/src/net/JNet/Developed/Java/Nio/ByteBuffer.cs @@ -17,17 +17,180 @@ */ using Java.Lang; +using Java.Util.Function; using MASES.JCOBridge.C2JBridge; +using MASES.JNet.Specific.Extensions; +using System.IO; +using System; namespace Java.Nio { - public partial class ByteBuffer + public partial class ByteBuffer : IDisposable { // can be extended with methods not reflected or not available in Java; + #region Operators + /// /// Converter from to /// public static implicit operator Comparable(ByteBuffer buffer) => buffer.Cast>(); + /// + /// Converts an instance of into + /// + public static implicit operator JCOBridgeDirectBuffer(ByteBuffer t) => t.ToDirectBuffer(); + /// + /// Converts an instance of array into using the default parameters of + /// + /// If the JVM supports direct access the function will share with the JVM the memory without JNI, otherwise fallback to the standard memory copy. + public static implicit operator ByteBuffer(byte[] t) => From(t); + /// + /// Converts an instance of into array + /// + /// If the supports direct access the function tries to move data from JVM memory without JNI, otherwise fallback to the standard memory copy. + public static implicit operator byte[](ByteBuffer t) => t.ToArray(); + /// + /// Converts an instance of into a using the default parameters of + /// + /// See remarks of + public static implicit operator ByteBuffer(System.IO.MemoryStream stream) => From(stream); + /// + /// Converts an instance of into + /// + /// The returned can be used to directly access and manages JVM memory without any memory move + public static implicit operator System.IO.Stream(ByteBuffer t) => t.ToStream(); + + #endregion + + #region Methods + + /// + /// Returns the array managed from this + /// + /// to bypass the conversion using direct buffer + /// The array managed from this + public byte[] ToArray(bool bypassDirectConvert = false) + { + if (!bypassDirectConvert) + { + try + { + return ToDirectBuffer().ToArray(); + } + catch (UnsupportedOperationException) { } + catch (System.NotSupportedException) { } + } + return IExecuteWithSignatureArray("array", "()Ljava/lang/Object;"); + } + /// + /// Fills the with data managed from this + /// + /// The array to be filled with the content of the + /// Resize to contain all data available in the + public void ToArray(ref byte[] array, bool resizeToFill = true) + { + try + { + ToDirectBuffer().ToArray(ref array, resizeToFill); + } + catch (UnsupportedOperationException) { } + catch (System.NotSupportedException) { } + } + /// + /// Creates a new in the JVM which belongs to . + /// + /// The pointer where data is stored + /// Declares the memory available, in , associated to + /// An optional can be used to be informed when the can be safely retired becuase the JVM is no moore using the pointer of . + /// The data will be associated to , by default the value will be + /// The time to live, expressed in milliseconds, the underlying memory shall remain available; if the time to live expires the pinned memory is retired leaving potentially the JVM under the possibility of an access violation. + /// A new instance of holding the memory of + /// + /// The memory associated to shall be available until the JVM reference of the newly created is garbage collected to avoid access violation within the JVM. + /// Under heavy pressure the memory footprint can raise up and generate an , use the functionality with caution or take into account the option which can help to recover the memory in advance before the Garbage Collector of the JVM retires the + /// If the user of is pretty sure that the memory is no more needed from the JVM, e.g. the invoked method does not queue the and its lifetime ends when the method returns, invoke to immediately release unmanaged resources and free the memory + /// + public static ByteBuffer From(IntPtr rawAddr, long capacity, EventHandler disposeEvent = null, object disposeEventState = null, int timeToLive = System.Threading.Timeout.Infinite) + { + var buf = JCOBridge.Global.JVM.NewDirectBuffer(rawAddr, capacity, disposeEvent, disposeEventState, timeToLive); + return JVMBridgeBase.WrapsDirect(buf.JavaObject); + } + /// + /// Creates a new in the JVM which belongs to + /// + /// The data to be shared + /// Appends to the end of the a memory block will be used to controls and arbitrates memory between CLR and JVM + /// If the array in will be resized to the next power of 2, + /// so capacity will be memory aligned and the limit of will be current size of + /// + /// The time to live, expressed in milliseconds, the underlying memory shall remain available; if the time to live expires the pinned memory is retired leaving potentially the JVM under the possibility of an access violation. + /// A new instance of holding the memory of + /// + /// The memory associated to will be pinned until the JVM reference of the newly created is garbage collected to avoid access violation within the JVM. + /// Under heavy pressure the memory footprint can raise up and generate an , use the functionality with caution or take into account the option which can help to recover the memory in advance before the Garbage Collector of the JVM retires the + /// If the user of is pretty sure that the pinned memory is no more needed from the JVM, e.g. the invoked method does not queue the and its lifetime ends when the method returns, invoke to immediately release unmanaged resources and free the memory + /// + public static ByteBuffer From(byte[] data, bool useMemoryControlBlock = true, bool arrangeCapacity = true, int timeToLive = System.Threading.Timeout.Infinite) + { + try + { + return data.DirectBufferWithWrap(useMemoryControlBlock, arrangeCapacity, timeToLive); + } + catch (UnsupportedOperationException) { } + catch (System.NotSupportedException) { } + + return ByteBuffer.Wrap(data); + } + /// + /// Creates a new in the JVM which shares the . The method helps to avoid too many array copies from CLR to JVM + /// + /// The non disposed to be used directly within the JVM from a , see remarks + /// Appends to the end of the a memory block will be used to controls and arbitrates memory between CLR and JVM + /// The time to live, expressed in milliseconds, the underlying memory shall remain available; if the time to live expires the pinned memory is retired leaving potentially the JVM under the possibility of an access violation. + /// An optional can be used to be informed when the can be safely disposed (the dispose action shall be in the user code), if the underlying system will automatically dispose the . + /// A new instance of holding the memory of shared with the + /// + /// The memory associated to will be pinned until the JVM reference of the newly created is garbage collected to avoid access violation within the JVM. + /// Under heavy pressure the memory footprint can raise up and generate an , use the functionality with caution or take into account the option which can help to recover the memory in advance before the Garbage Collector of the JVM retires the + /// + /// The cannot be disposed otherwise the underlying system is not able to access the memory. The can be written, or read, and changes are visible to both CLR and JVM, + /// however, if the grows, the underlying system cannot resize too and capacity still remains the one when was invoked the first time. + /// + public static ByteBuffer From(System.IO.MemoryStream stream, bool useMemoryControlBlock = true, EventHandler disposeEvent = null, int timeToLive = System.Threading.Timeout.Infinite) + { + var buf = JCOBridge.Global.JVM.NewDirectBuffer(stream, useMemoryControlBlock, disposeEvent, timeToLive); + return JVMBridgeBase.WrapsDirect(buf.JavaObject); + } + /// + /// Returns an instance of associated to this instance + /// + /// The associated to this instance + /// The returned can be used to directly access and manages JVM memory without any memory move + public System.IO.Stream ToStream() + { + return ToDirectBuffer().ToStream(); + } + /// + /// Returns an instance of + /// + /// The associated to this instance + /// The returned can be used to directly access and manages JVM memory without any memory move + public JCOBridgeDirectBuffer ToDirectBuffer() + { + Rewind(); + return JVM.GetDirectBuffer(BridgeInstance); + } + + #endregion + + #region IDisposable + /// + public override void Dispose() + { + ToDirectBuffer().Dispose(); + base.Dispose(); + } + + #endregion } } diff --git a/src/net/JNet/Developed/Java/Nio/CharBuffer.cs b/src/net/JNet/Developed/Java/Nio/CharBuffer.cs index 1b2a7eabb8..7c02e4e9f0 100644 --- a/src/net/JNet/Developed/Java/Nio/CharBuffer.cs +++ b/src/net/JNet/Developed/Java/Nio/CharBuffer.cs @@ -18,6 +18,7 @@ using Java.Lang; using MASES.JCOBridge.C2JBridge; +using MASES.JNet.Specific.Extensions; namespace Java.Nio { @@ -25,9 +26,81 @@ public partial class CharBuffer { // can be extended with methods not reflected or not available in Java; + #region Operators + /// /// Converter from to /// public static implicit operator Comparable(CharBuffer buffer) => buffer.Cast>(); + /// + /// Converts an instance of into + /// + public static implicit operator JCOBridgeDirectBuffer(CharBuffer t) => t.ToDirectBuffer(); + /// + /// Converts an instance of array into using the default parameters of + /// + /// If the JVM supports direct access the function will share with the JVM the memory without JNI, otherwise fallback to the standard memory copy. + public static implicit operator CharBuffer(char[] t) => From(t); + /// + /// Converts an instance of into array + /// + /// If the supports direct access the function tries to move data from JVM memory without JNI, otherwise fallback to the standard memory copy. + public static implicit operator char[](CharBuffer t) => t.ToArray(); + + #endregion + + #region Methods + + /// + /// Returns the array managed from this + /// + /// to bypass the conversion using direct buffer + /// The array managed from this + public char[] ToArray(bool bypassDirectConvert = false) + { + if (!bypassDirectConvert) + { + try + { + return ToDirectBuffer().ToArray(); + } + catch (UnsupportedOperationException) { } + catch (System.NotSupportedException) { } + } + return IExecuteWithSignatureArray("array", "()Ljava/lang/Object;"); + } + /// + /// Creates a new in the JVM which belongs to + /// + /// The data to be shared + /// Appends to the end of the a memory block will be used to controls and arbitrates memory between CLR and JVM + /// If the array in will be resized to the next power of 2, + /// so capacity will be memory aligned and the limit of java.nio.ByteBuffer will be current size of + /// + /// The time to live, expressed in milliseconds, the underlying memory shall remain available; if the time to live expires the pinned memory is retired leaving potentially the JVM under the possibility of an access violation. + /// A new instance of + public static CharBuffer From(char[] data, bool useMemoryControlBlock = true, bool arrangeCapacity = true, int timeToLive = System.Threading.Timeout.Infinite) + { + try + { + return data.DirectBufferWithWrap(useMemoryControlBlock, arrangeCapacity, timeToLive); + } + catch (UnsupportedOperationException) { } + catch (System.NotSupportedException) { } + + return CharBuffer.Wrap(data); + } + /// + /// Returns an instance of + /// + /// The associated to this instance + /// The returned can be used to directly access and manages JVM memory without any memory move + public JCOBridgeDirectBuffer ToDirectBuffer() + { + Rewind(); + return JVM.GetDirectBuffer(BridgeInstance); + } + + #endregion } } diff --git a/src/net/JNet/Developed/Java/Nio/DoubleBuffer.cs b/src/net/JNet/Developed/Java/Nio/DoubleBuffer.cs index 7c37c4f55f..0ee31567bc 100644 --- a/src/net/JNet/Developed/Java/Nio/DoubleBuffer.cs +++ b/src/net/JNet/Developed/Java/Nio/DoubleBuffer.cs @@ -18,6 +18,7 @@ using Java.Lang; using MASES.JCOBridge.C2JBridge; +using MASES.JNet.Specific.Extensions; namespace Java.Nio { @@ -25,9 +26,81 @@ public partial class DoubleBuffer { // can be extended with methods not reflected or not available in Java; + #region Operators + /// /// Converter from to /// public static implicit operator Comparable(DoubleBuffer buffer) => buffer.Cast>(); + /// + /// Converts an instance of into + /// + public static implicit operator JCOBridgeDirectBuffer(DoubleBuffer t) => t.ToDirectBuffer(); + /// + /// Converts an instance of array into using the default parameters of + /// + /// If the JVM supports direct access the function will share with the JVM the memory without JNI, otherwise fallback to the standard memory copy. + public static implicit operator DoubleBuffer(double[] t) => From(t); + /// + /// Converts an instance of into array + /// + /// If the supports direct access the function tries to move data from JVM memory without JNI, otherwise fallback to the standard memory copy. + public static implicit operator double[](DoubleBuffer t) => t.ToArray(); + + #endregion + + #region Methods + + /// + /// Returns the array managed from this + /// + /// to bypass the conversion using direct buffer + /// The array managed from this + public double[] ToArray(bool bypassDirectConvert = false) + { + if (!bypassDirectConvert) + { + try + { + return ToDirectBuffer().ToArray(); + } + catch (UnsupportedOperationException) { } + catch (System.NotSupportedException) { } + } + return IExecuteWithSignatureArray("array", "()Ljava/lang/Object;"); + } + /// + /// Creates a new in the JVM which belongs to + /// + /// The data to be shared + /// Appends to the end of the a memory block will be used to controls and arbitrates memory between CLR and JVM + /// If the array in will be resized to the next power of 2, + /// so capacity will be memory aligned and the limit of java.nio.ByteBuffer will be current size of + /// + /// The time to live, expressed in milliseconds, the underlying memory shall remain available; if the time to live expires the pinned memory is retired leaving potentially the JVM under the possibility of an access violation. + /// A new instance of + public static DoubleBuffer From(double[] data, bool useMemoryControlBlock = true, bool arrangeCapacity = true, int timeToLive = System.Threading.Timeout.Infinite) + { + try + { + return data.DirectBufferWithWrap(useMemoryControlBlock, arrangeCapacity, timeToLive); + } + catch (UnsupportedOperationException) { } + catch (System.NotSupportedException) { } + + return DoubleBuffer.Wrap(data); + } + /// + /// Returns an instance of + /// + /// The associated to this instance + /// The returned can be used to directly access and manages JVM memory without any memory move + public JCOBridgeDirectBuffer ToDirectBuffer() + { + Rewind(); + return JVM.GetDirectBuffer(BridgeInstance); + } + + #endregion } } diff --git a/src/net/JNet/Developed/Java/Nio/FloatBuffer.cs b/src/net/JNet/Developed/Java/Nio/FloatBuffer.cs index 145e755527..47dde7a177 100644 --- a/src/net/JNet/Developed/Java/Nio/FloatBuffer.cs +++ b/src/net/JNet/Developed/Java/Nio/FloatBuffer.cs @@ -18,6 +18,7 @@ using Java.Lang; using MASES.JCOBridge.C2JBridge; +using MASES.JNet.Specific.Extensions; namespace Java.Nio { @@ -25,9 +26,81 @@ public partial class FloatBuffer { // can be extended with methods not reflected or not available in Java; + #region Operators + /// /// Converter from to /// public static implicit operator Comparable(FloatBuffer buffer) => buffer.Cast>(); + /// + /// Converts an instance of into + /// + public static implicit operator JCOBridgeDirectBuffer(FloatBuffer t) => t.ToDirectBuffer(); + /// + /// Converts an instance of array into using the default parameters of + /// + /// If the JVM supports direct access the function will share with the JVM the memory without JNI, otherwise fallback to the standard memory copy. + public static implicit operator FloatBuffer(float[] t) => From(t); + /// + /// Converts an instance of into array + /// + /// If the supports direct access the function tries to move data from JVM memory without JNI, otherwise fallback to the standard memory copy. + public static implicit operator float[](FloatBuffer t) => t.ToArray(); + + #endregion + + #region Methods + + /// + /// Returns the array managed from this + /// + /// to bypass the conversion using direct buffer + /// The array managed from this + public float[] ToArray(bool bypassDirectConvert = false) + { + if (!bypassDirectConvert) + { + try + { + return ToDirectBuffer().ToArray(); + } + catch (UnsupportedOperationException) { } + catch (System.NotSupportedException) { } + } + return IExecuteWithSignatureArray("array", "()Ljava/lang/Object;"); + } + /// + /// Creates a new in the JVM which belongs to + /// + /// The data to be shared + /// Appends to the end of the a memory block will be used to controls and arbitrates memory between CLR and JVM + /// If the array in will be resized to the next power of 2, + /// so capacity will be memory aligned and the limit of java.nio.ByteBuffer will be current size of + /// + /// The time to live, expressed in milliseconds, the underlying memory shall remain available; if the time to live expires the pinned memory is retired leaving potentially the JVM under the possibility of an access violation. + /// A new instance of + public static FloatBuffer From(float[] data, bool useMemoryControlBlock = true, bool arrangeCapacity = true, int timeToLive = System.Threading.Timeout.Infinite) + { + try + { + return data.DirectBufferWithWrap(useMemoryControlBlock, arrangeCapacity, timeToLive); + } + catch (UnsupportedOperationException) { } + catch (System.NotSupportedException) { } + + return FloatBuffer.Wrap(data); + } + /// + /// Returns an instance of + /// + /// The associated to this instance + /// The returned can be used to directly access and manages JVM memory without any memory move + public JCOBridgeDirectBuffer ToDirectBuffer() + { + Rewind(); + return JVM.GetDirectBuffer(BridgeInstance); + } + + #endregion } } diff --git a/src/net/JNet/Developed/Java/Nio/IntBuffer.cs b/src/net/JNet/Developed/Java/Nio/IntBuffer.cs index ce0f03e452..8ffc6f323b 100644 --- a/src/net/JNet/Developed/Java/Nio/IntBuffer.cs +++ b/src/net/JNet/Developed/Java/Nio/IntBuffer.cs @@ -18,6 +18,7 @@ using Java.Lang; using MASES.JCOBridge.C2JBridge; +using MASES.JNet.Specific.Extensions; namespace Java.Nio { @@ -25,9 +26,81 @@ public partial class IntBuffer { // can be extended with methods not reflected or not available in Java; + #region Operators + /// /// Converter from to /// public static implicit operator Comparable(IntBuffer buffer) => buffer.Cast>(); + /// + /// Converts an instance of into + /// + public static implicit operator JCOBridgeDirectBuffer(IntBuffer t) => t.ToDirectBuffer(); + /// + /// Converts an instance of array into using the default parameters of + /// + /// If the JVM supports direct access the function will share with the JVM the memory without JNI, otherwise fallback to the standard memory copy. + public static implicit operator IntBuffer(int[] t) => From(t); + /// + /// Converts an instance of into array + /// + /// If the supports direct access the function tries to move data from JVM memory without JNI, otherwise fallback to the standard memory copy. + public static implicit operator int[](IntBuffer t) => t.ToArray(); + + #endregion + + #region Methods + + /// + /// Returns the array managed from this + /// + /// to bypass the conversion using direct buffer + /// The array managed from this + public int[] ToArray(bool bypassDirectConvert = false) + { + if (!bypassDirectConvert) + { + try + { + return ToDirectBuffer().ToArray(); + } + catch (UnsupportedOperationException) { } + catch (System.NotSupportedException) { } + } + return IExecuteWithSignatureArray("array", "()Ljava/lang/Object;"); + } + /// + /// Creates a new in the JVM which belongs to + /// + /// The data to be shared + /// Appends to the end of the a memory block will be used to controls and arbitrates memory between CLR and JVM + /// If the array in will be resized to the next power of 2, + /// so capacity will be memory aligned and the limit of java.nio.ByteBuffer will be current size of + /// + /// The time to live, expressed in milliseconds, the underlying memory shall remain available; if the time to live expires the pinned memory is retired leaving potentially the JVM under the possibility of an access violation. + /// A new instance of + public static IntBuffer From(int[] data, bool useMemoryControlBlock = true, bool arrangeCapacity = true, int timeToLive = System.Threading.Timeout.Infinite) + { + try + { + return data.DirectBufferWithWrap(useMemoryControlBlock, arrangeCapacity, timeToLive); + } + catch (UnsupportedOperationException) { } + catch (System.NotSupportedException) { } + + return IntBuffer.Wrap(data); + } + /// + /// Returns an instance of + /// + /// The associated to this instance + /// The returned can be used to directly access and manages JVM memory without any memory move + public JCOBridgeDirectBuffer ToDirectBuffer() + { + Rewind(); + return JVM.GetDirectBuffer(BridgeInstance); + } + + #endregion } } diff --git a/src/net/JNet/Developed/Java/Nio/LongBuffer.cs b/src/net/JNet/Developed/Java/Nio/LongBuffer.cs index d026e281d7..dcfc27b87b 100644 --- a/src/net/JNet/Developed/Java/Nio/LongBuffer.cs +++ b/src/net/JNet/Developed/Java/Nio/LongBuffer.cs @@ -18,6 +18,7 @@ using Java.Lang; using MASES.JCOBridge.C2JBridge; +using MASES.JNet.Specific.Extensions; namespace Java.Nio { @@ -25,9 +26,81 @@ public partial class LongBuffer { // can be extended with methods not reflected or not available in Java; + #region Operators + /// /// Converter from to /// public static implicit operator Comparable(LongBuffer buffer) => buffer.Cast>(); + /// + /// Converts an instance of into + /// + public static implicit operator JCOBridgeDirectBuffer(LongBuffer t) => t.ToDirectBuffer(); + /// + /// Converts an instance of array into using the default parameters of + /// + /// If the JVM supports direct access the function will share with the JVM the memory without JNI, otherwise fallback to the standard memory copy. + public static implicit operator LongBuffer(long[] t) => From(t); + /// + /// Converts an instance of into array + /// + /// If the supports direct access the function tries to move data from JVM memory without JNI, otherwise fallback to the standard memory copy. + public static implicit operator long[](LongBuffer t) => t.ToArray(); + + #endregion + + #region Methods + + /// + /// Returns the array managed from this + /// + /// to bypass the conversion using direct buffer + /// The array managed from this + public long[] ToArray(bool bypassDirectConvert = false) + { + if (!bypassDirectConvert) + { + try + { + return ToDirectBuffer().ToArray(); + } + catch (UnsupportedOperationException) { } + catch (System.NotSupportedException) { } + } + return IExecuteWithSignatureArray("array", "()Ljava/lang/Object;"); + } + /// + /// Creates a new in the JVM which belongs to + /// + /// The data to be shared + /// Appends to the end of the a memory block will be used to controls and arbitrates memory between CLR and JVM + /// If the array in will be resized to the next power of 2, + /// so capacity will be memory aligned and the limit of java.nio.ByteBuffer will be current size of + /// + /// The time to live, expressed in milliseconds, the underlying memory shall remain available; if the time to live expires the pinned memory is retired leaving potentially the JVM under the possibility of an access violation. + /// A new instance of + public static LongBuffer From(long[] data, bool useMemoryControlBlock = true, bool arrangeCapacity = true, int timeToLive = System.Threading.Timeout.Infinite) + { + try + { + return data.DirectBufferWithWrap(useMemoryControlBlock, arrangeCapacity, timeToLive); + } + catch (UnsupportedOperationException) { } + catch (System.NotSupportedException) { } + + return LongBuffer.Wrap(data); + } + /// + /// Returns an instance of + /// + /// The associated to this instance + /// The returned can be used to directly access and manages JVM memory without any memory move + public JCOBridgeDirectBuffer ToDirectBuffer() + { + Rewind(); + return JVM.GetDirectBuffer(BridgeInstance); + } + + #endregion } } diff --git a/src/net/JNet/Developed/Java/Nio/ShortBuffer.cs b/src/net/JNet/Developed/Java/Nio/ShortBuffer.cs index 09a3202a3a..12dd78817b 100644 --- a/src/net/JNet/Developed/Java/Nio/ShortBuffer.cs +++ b/src/net/JNet/Developed/Java/Nio/ShortBuffer.cs @@ -18,6 +18,7 @@ using Java.Lang; using MASES.JCOBridge.C2JBridge; +using MASES.JNet.Specific.Extensions; namespace Java.Nio { @@ -25,9 +26,81 @@ public partial class ShortBuffer { // can be extended with methods not reflected or not available in Java; + #region Operators + /// /// Converter from to /// public static implicit operator Comparable(ShortBuffer buffer) => buffer.Cast>(); + /// + /// Converts an instance of into + /// + public static implicit operator JCOBridgeDirectBuffer(ShortBuffer t) => t.ToDirectBuffer(); + /// + /// Converts an instance of array into using the default parameters of + /// + /// If the JVM supports direct access the function will share with the JVM the memory without JNI, otherwise fallback to the standard memory copy. + public static implicit operator ShortBuffer(short[] t) => From(t); + /// + /// Converts an instance of into array + /// + /// If the supports direct access the function tries to move data from JVM memory without JNI, otherwise fallback to the standard memory copy. + public static implicit operator short[](ShortBuffer t) => t.ToArray(); + + #endregion + + #region Methods + + /// + /// Returns the array managed from this + /// + /// to bypass the conversion using direct buffer + /// The array managed from this + public short[] ToArray(bool bypassDirectConvert = false) + { + if (!bypassDirectConvert) + { + try + { + return ToDirectBuffer().ToArray(); + } + catch (UnsupportedOperationException) { } + catch (System.NotSupportedException) { } + } + return IExecuteWithSignatureArray("array", "()Ljava/lang/Object;"); + } + /// + /// Creates a new in the JVM which belongs to + /// + /// The data to be shared + /// Appends to the end of the a memory block will be used to controls and arbitrates memory between CLR and JVM + /// If the array in will be resized to the next power of 2, + /// so capacity will be memory aligned and the limit of java.nio.ByteBuffer will be current size of + /// + /// The time to live, expressed in milliseconds, the underlying memory shall remain available; if the time to live expires the pinned memory is retired leaving potentially the JVM under the possibility of an access violation. + /// A new instance of + public static ShortBuffer From(short[] data, bool useMemoryControlBlock = true, bool arrangeCapacity = true, int timeToLive = System.Threading.Timeout.Infinite) + { + try + { + return data.DirectBufferWithWrap(useMemoryControlBlock, arrangeCapacity, timeToLive); + } + catch (UnsupportedOperationException) { } + catch (System.NotSupportedException) { } + + return ShortBuffer.Wrap(data); + } + /// + /// Returns an instance of + /// + /// The associated to this instance + /// The returned can be used to directly access and manages JVM memory without any memory move + public JCOBridgeDirectBuffer ToDirectBuffer() + { + Rewind(); + return JVM.GetDirectBuffer(BridgeInstance); + } + + #endregion } } diff --git a/src/net/JNet/JNet.csproj b/src/net/JNet/JNet.csproj index a54f53c5c7..ae3965830e 100644 --- a/src/net/JNet/JNet.csproj +++ b/src/net/JNet/JNet.csproj @@ -14,21 +14,30 @@ False False - - - - - - - - - - - - - All - None - - - + + + + + + + + + + + + + + + + + + + + + + All + None + + + diff --git a/src/net/JNet/Specific/Extensions/JNetCoreExtensions.cs b/src/net/JNet/Specific/Extensions/JNetCoreExtensions.cs index e5053df9cf..7a1bedfc92 100644 --- a/src/net/JNet/Specific/Extensions/JNetCoreExtensions.cs +++ b/src/net/JNet/Specific/Extensions/JNetCoreExtensions.cs @@ -16,7 +16,9 @@ * Refer to LICENSE for more information. */ +using Java.Nio; using MASES.JCOBridge.C2JBridge; +using System; namespace MASES.JNet.Specific.Extensions { @@ -34,5 +36,39 @@ public static class JNetCoreExtensions { return Java.Lang.Class.Of(); } + + /// + /// The method creates a and wrap it into + /// + /// The data to be wrapped + /// The wrapping class + /// The array of to be wrapped + /// Appends to the end of the a memory block will be used to controls and arbitrates memory between CLR and JVM + /// If the array in will be resized to the next power of 2, + /// so capacity will be memory aligned and the limit of java.nio.ByteBuffer will be current size of + /// + /// The time to live, expressed in milliseconds, the underlying memory shall remain available; if the time to live expires the pinned memory is retired leaving potentially the JVM under the possibility of an access violation. + /// A that receives the prepared and shall return + /// The instance + public static TWrap DirectBufferWithWrap(this TData[] data, bool useMemoryControlBlock = true, bool arrangeCapacity = true, int timeToLive = System.Threading.Timeout.Infinite, Func converter = null) where TWrap : IJVMBridgeBase, new() + { + var buf = JCOBridge.C2JBridge.JCOBridge.Global.JVM.NewDirectBuffer(data, useMemoryControlBlock, arrangeCapacity, timeToLive); + if (data is byte[]) return JVMBridgeBase.WrapsDirect(buf.JavaObject); + else + { + IJVMBridgeBase ibb; + ByteBuffer bb = JVMBridgeBase.WrapsDirect(buf.JavaObject); + bb.Order(BitConverter.IsLittleEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); + if (data is char[]) ibb = bb.AsCharBuffer(); + else if (data is double[]) ibb = bb.AsDoubleBuffer(); + else if (data is float[]) ibb = bb.AsFloatBuffer(); + else if (data is int[]) ibb = bb.AsIntBuffer(); + else if (data is long[]) ibb = bb.AsLongBuffer(); + else if (data is short[]) ibb = bb.AsShortBuffer(); + else if (converter != null) ibb = converter(bb); + else throw new System.InvalidCastException($"{typeof(TData)} does not have a ready made counter part, try use the converter to return {typeof(TWrap)}"); + return JVMBridgeBase.WrapsDirect(ibb.BridgeInstance); + } + } } } diff --git a/src/net/JNet/Specific/JNetHelper.cs b/src/net/JNet/Specific/JNetHelper.cs new file mode 100644 index 0000000000..1d864f4f9a --- /dev/null +++ b/src/net/JNet/Specific/JNetHelper.cs @@ -0,0 +1,160 @@ +/* +* Copyright 2024 MASES s.r.l. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* Refer to LICENSE for more information. +*/ + +using Java.Nio; +using Java.Util; +using MASES.JCOBridge.C2JBridge; +using MASES.JCOBridge.C2JBridge.JVMInterop; +using MASES.JNet.Specific.Extensions; +using System; + +namespace MASES.JNet.Specific +{ + /// + /// Helper class of JNet for some specific operations + /// + public sealed class JNetHelper : MASES.JCOBridge.C2JBridge.JVMBridgeBase + { + const string _bridgeClassName = "org.mases.jnet.JNetHelper"; + + private static readonly IJavaType LocalBridgeClazz = ClazzOf(_bridgeClassName); + + /// + /// + /// + public override string BridgeClassName => _bridgeClassName; + /// + /// + /// + public override bool IsBridgeAbstract => false; + /// + /// + /// + public override bool IsBridgeCloseable => false; + /// + /// + /// + public override bool IsBridgeInterface => false; + /// + /// + /// + public override bool IsBridgeStatic => false; + + /// + /// Executes the over primitive types + /// + /// Array of primitive types + /// Set to to use , , and so on to transfer data to JVM + /// A can be used as input of + public static List ListFrom(T[] data, bool buffered = false) + { + if (data == null) throw new ArgumentNullException(nameof(data)); + if (!buffered) + { + return SExecute(LocalBridgeClazz, "listFromPrimitiveArray", data); + } + else + { + var buf = JCOBridge.C2JBridge.JCOBridge.Global.JVM.NewDirectBuffer(data, false, false); + ByteBuffer bb = JVMBridgeBase.WrapsDirect(buf.JavaObject); + if (data is byte[]) return ListFrom(bb); + else + { + bb.Order(BitConverter.IsLittleEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); + if (data is char[]) return ListFrom(bb.AsCharBuffer()); + else if (data is double[]) return ListFrom(bb.AsDoubleBuffer()); + else if (data is float[]) return ListFrom(bb.AsFloatBuffer()); + else if (data is int[]) return ListFrom(bb.AsIntBuffer()); + else if (data is long[]) return ListFrom(bb.AsLongBuffer()); + else if (data is short[]) return ListFrom(bb.AsShortBuffer()); + else throw new System.InvalidCastException($"{typeof(T)} does not have a ready made counter part"); + } + } + } + + /// + /// Executes the using a in + /// + /// to be used + /// A can be used as input of + public static List ListFrom(ByteBuffer arg0) + { + return SExecute(LocalBridgeClazz, "listFromByteBuffer", arg0); + } + + /// + /// Executes the using a in + /// + /// to be used + /// A can be used as input of + public static List ListFrom(CharBuffer arg0) + { + return SExecute(LocalBridgeClazz, "listFromCharBuffer", arg0); + } + + /// + /// Executes the using a in + /// + /// to be used + /// A can be used as input of + public static List ListFrom(DoubleBuffer arg0) + { + return SExecute(LocalBridgeClazz, "listFromDoubleBuffer", arg0); + } + + /// + /// Executes the using a in + /// + /// to be used + /// A can be used as input of + public static List ListFrom(FloatBuffer arg0) + { + return SExecute(LocalBridgeClazz, "listFromFloatBuffer", arg0); + } + + /// + /// Executes the using a in + /// + /// to be used + /// A can be used as input of + public static List ListFrom(IntBuffer arg0) + { + return SExecute(LocalBridgeClazz, "listFromIntBuffer", arg0); + } + + /// + /// Executes the using a in + /// + /// to be used + /// A can be used as input of + public static List ListFrom(LongBuffer arg0) + { + return SExecute(LocalBridgeClazz, "listFromLongBuffer", arg0); + } + + /// + /// Executes the using a in + /// + /// to be used + /// A can be used as input of + public static List ListFrom(ShortBuffer arg0) + { + return SExecute(LocalBridgeClazz, "listFromShortBuffer", arg0); + } + } +} diff --git a/src/net/JNetReflector/JNetReflector.csproj b/src/net/JNetReflector/JNetReflector.csproj index c2245e8e79..9f1afe38af 100644 --- a/src/net/JNetReflector/JNetReflector.csproj +++ b/src/net/JNetReflector/JNetReflector.csproj @@ -152,7 +152,7 @@ - + All None diff --git a/src/net/templates/templates/jcobridgeConsoleApp/jcobridgeConsoleApp.csproj b/src/net/templates/templates/jcobridgeConsoleApp/jcobridgeConsoleApp.csproj index 64d22175cd..2b3129d130 100644 --- a/src/net/templates/templates/jcobridgeConsoleApp/jcobridgeConsoleApp.csproj +++ b/src/net/templates/templates/jcobridgeConsoleApp/jcobridgeConsoleApp.csproj @@ -1,12 +1,12 @@ Exe - net462;net6.0;net7.0 + net462;net6.0;net8.0 - + diff --git a/src/net/templates/templates/jnetAWTApp/jnetAWTApp.csproj b/src/net/templates/templates/jnetAWTApp/jnetAWTApp.csproj index ea35fe84d7..75bc8ce324 100644 --- a/src/net/templates/templates/jnetAWTApp/jnetAWTApp.csproj +++ b/src/net/templates/templates/jnetAWTApp/jnetAWTApp.csproj @@ -1,7 +1,7 @@ Exe - net462;net6.0;net7.0;net8.0 + net462;net6.0;net8.0 diff --git a/src/net/templates/templates/jnetApp/jnetApp.csproj b/src/net/templates/templates/jnetApp/jnetApp.csproj index 4cdf7bf9d5..3d2a6fdd5c 100644 --- a/src/net/templates/templates/jnetApp/jnetApp.csproj +++ b/src/net/templates/templates/jnetApp/jnetApp.csproj @@ -1,7 +1,7 @@  Exe - net462;net6.0;net7.0;net8.0 + net462;net6.0;net8.0 diff --git a/tests/jvm/testclass/pom.xml b/tests/jvm/testclass/pom.xml index 3de9151e75..e7ac8a1ace 100644 --- a/tests/jvm/testclass/pom.xml +++ b/tests/jvm/testclass/pom.xml @@ -1,13 +1,239 @@ 4.0.0 - org.mases.jnet - testclass - 1.0-SNAPSHOT + + com.masesgroup + jnet-test + mases.jnet-test + JNet-Java test library + https://github.com/masesgroup/JNet + ${jnettestversion} + + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + A business-friendly OSS license + + + + https://github.com/masesgroup/JNet/issues + GitHub Issues + + + https://github.com/masesgroup/JNet + scm:git:git://github.com/masesgroup/JNet.git + scm:git:git@github.com:masesgroup/JNet.git + + + + info@masesgroup.com + MASES Group + https://github.com/masesgroup + masesgroup + + 11 11 + ${basedir}/classpathfile.classpath + 1.0.0.0 + 2.5.12 + ../../../bin/net6.0/JCOBridge.jar - Archetype - testclass - http://maven.apache.org + + + ossrh + https://s01.oss.sonatype.org/content/repositories/snapshots + + + ossrh + Central Repository OSSRH + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.6.1 + + + build-classpath + generate-sources + + build-classpath + + + ${classpathfile} + + + + copy-dependencies + package + + copy-dependencies + + + ${basedir}/../../../jars/ + false + false + true + + + + copy-installed + package + + copy + + + + + ${project.groupId} + ${project.artifactId} + ${project.version} + ${project.packaging} + + + ${basedir}/../../../jars/ + true + + + + + + org.codehaus.gmaven + gmaven-plugin + 1.5 + + + generate-resources + + execute + + + + def file = new File(project.properties.classpathfile) + project.properties.originalClassPath = file.getText() + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + -cp + ${originalClassPath}${path.separator}${basedir}/${jcobridgepath} + + 11 + 11 + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + org.apache.maven.plugins + maven-source-plugin + 3.3.0 + + + attach-sources + + jar-no-fork + + + + **/target/ + **/*.xml + **/*.zip + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.6.0 + + + https://www.jcobridge.com/api-java_${jcobridgeversion} + + true + 8 + + + JCOBridge + JCOBridge + ${jcobridgeversion} + + + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.1.0 + + + --pinentry-mode + loopback + + + + + sign-artifacts + verify + + sign + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.13 + true + + ossrh + https://s01.oss.sonatype.org/ + true + + + + + + + junit + junit + 4.13.2 + test + + + org.burningwave + core + 12.64.2 + + diff --git a/tests/jvm/testclass/src/main/java/org/mases/jnet/TestArrayAndByteBuffer.java b/tests/jvm/testclass/src/main/java/org/mases/jnet/TestArrayAndByteBuffer.java new file mode 100644 index 0000000000..52a9b6f40f --- /dev/null +++ b/tests/jvm/testclass/src/main/java/org/mases/jnet/TestArrayAndByteBuffer.java @@ -0,0 +1,51 @@ +package org.mases.jnet; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.mases.jcobridge.JCSharedBuffer; + +public class TestArrayAndByteBuffer { + int m_length; + byte[] _array; + ByteBuffer _buffer; + + public TestArrayAndByteBuffer(int length) throws IOException { + _array = new byte[length]; + for (int i = 0; i < length; i++) + { + _array[i] = (byte) (i % Byte.MAX_VALUE); + } + _buffer = JCSharedBuffer.Create(_array); + } + + public void insertArray(byte[] array) { + m_length = array.length; + } + + public void insertByteBuffer(ByteBuffer buf) { + buf.rewind(); + byte[] array = new byte[buf.remaining()]; + buf.get(array, 0, array.length); + m_length = array.length; + } + + public void insertByteBufferNoNew(ByteBuffer buf) { + buf.rewind(); + buf.get(_array, 0, _array.length); + m_length = _array.length; + } + + public void insertByteBufferNoGet(ByteBuffer buf) { + buf.rewind(); + m_length = buf.remaining(); + } + + public byte[] getArray() { + return _array; + } + + public ByteBuffer getByteBuffer() { + return _buffer; + } +} diff --git a/tests/net/Common/Initializer.cs b/tests/net/Common/Initializer.cs index 9e3c290617..703cac1df1 100644 --- a/tests/net/Common/Initializer.cs +++ b/tests/net/Common/Initializer.cs @@ -17,10 +17,26 @@ */ using MASES.JNet; +using System.Collections.Generic; namespace MASES.JNetTest.Common { class JNetTestCore : JNetCore - { + { +#if DEBUG + public override bool EnableDebug => true; +#endif + protected override IList PathToParse + { + get + { + var lst = base.PathToParse; + var assembly = typeof(JNetTestCore).Assembly; + var path = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(assembly.Location), JARsSubFolder, $"jnet-test-1.0.0.0.jar"); + if (!System.IO.File.Exists(path)) throw new System.IO.FileNotFoundException("JAR file for test not available, run Maven first", path); + lst.Add(path); + return lst; + } + } } } \ No newline at end of file diff --git a/tests/net/JNetByteBufferTest/JNetByteBufferTest.csproj b/tests/net/JNetByteBufferTest/JNetByteBufferTest.csproj new file mode 100644 index 0000000000..d5be5d38f4 --- /dev/null +++ b/tests/net/JNetByteBufferTest/JNetByteBufferTest.csproj @@ -0,0 +1,13 @@ + + + + JNetByteBufferTest + Exe + MASES.JNetByteBufferTest + JNetByteBufferTest - a test tool for JNet + JNetByteBufferTest - a test tool for JNet + + + + + diff --git a/tests/net/JNetByteBufferTest/Program.cs b/tests/net/JNetByteBufferTest/Program.cs new file mode 100644 index 0000000000..bdbb0e2535 --- /dev/null +++ b/tests/net/JNetByteBufferTest/Program.cs @@ -0,0 +1,176 @@ +/* +* Copyright 2024 MASES s.r.l. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* Refer to LICENSE for more information. +*/ + +using Java.Nio; +using MASES.JCOBridge.C2JBridge.JVMInterop; +using MASES.JNetTest.Common; +using System; +using System.Diagnostics; + +namespace MASES.JNetByteBufferTest +{ + class Program + { + const int MinValue = 10; + const int MaxValue = 1000000000; + const int Padding = 10; + const int iterations = 100; + static void Main(string[] args) + { + JNetTestCore.ApplicationHeapSize = "4G"; + JNetTestCore.CreateGlobalInstance(); + var appArgs = JNetTestCore.FilteredArgs; + + Console.WriteLine("Start insert from CLR to JVM"); + + for (int i = MinValue; i < MaxValue; i *= 10) + { + TestInsertByteBuffers(iterations, i); + } + + Console.WriteLine("Start get from JVM to CLR"); + + for (int i = MinValue; i < MaxValue; i *= 10) + { + TestGetByteBuffers(iterations, i); + } + } + + static void TestInsertByteBuffers(int iteration, int length) + { + byte[] bytes = new byte[length]; + for (int i = 0; i < bytes.Length; i++) + { + bytes[i] = (byte)(i % byte.MaxValue); + } + + var bbCast = Java.Nio.ByteBuffer.From(bytes, false, false); + var jClass = JNetTestCore.GlobalInstance.JVM.New("org.mases.jnet.TestArrayAndByteBuffer", length) as IJavaObject; + + System.GC.Collect(); + Java.Lang.System.Gc(); + + Stopwatch watcher1 = Stopwatch.StartNew(); + for (int i = 0; i < iteration; i++) + { + jClass.Invoke("insertArray", bytes); + } + watcher1.Stop(); + + System.GC.Collect(); + Java.Lang.System.Gc(); + + Stopwatch watcher2 = Stopwatch.StartNew(); + for (int i = 0; i < iteration; i++) + { + jClass.Invoke("insertByteBuffer", bbCast.BridgeInstance); + } + watcher2.Stop(); + + System.GC.Collect(); + Java.Lang.System.Gc(); + + Stopwatch watcher3 = Stopwatch.StartNew(); + for (int i = 0; i < iteration; i++) + { + jClass.Invoke("insertByteBufferNoNew", bbCast.BridgeInstance); + } + watcher3.Stop(); + + System.GC.Collect(); + Java.Lang.System.Gc(); + + Stopwatch watcher4 = Stopwatch.StartNew(); + for (int i = 0; i < iteration; i++) + { + jClass.Invoke("insertByteBufferNoGet", bbCast.BridgeInstance); + } + watcher4.Stop(); + + Console.WriteLine($"{length,Padding} - Array {TimeSpan.FromTicks(watcher1.ElapsedTicks / iteration)} - ByteBuffer {TimeSpan.FromTicks(watcher2.ElapsedTicks / iteration)} ({((double)watcher1.ElapsedTicks / watcher2.ElapsedTicks) * 100:0.##}%) - ByteBufferNoNew {TimeSpan.FromTicks(watcher3.ElapsedTicks / iteration)} ({((double)watcher1.ElapsedTicks / watcher3.ElapsedTicks) * 100:0.##}%) - ByteBufferNoGet {TimeSpan.FromTicks(watcher4.ElapsedTicks / iteration)} ({((double)watcher1.ElapsedTicks / watcher4.ElapsedTicks) * 100:0.##}%)"); + } + + static void TestGetByteBuffers(int iteration, int length) + { + byte[] bytes = new byte[length]; + var jClass = JNetTestCore.GlobalInstance.JVM.New("org.mases.jnet.TestArrayAndByteBuffer", length) as IJavaObject; + + System.GC.Collect(); + Java.Lang.System.Gc(); + + Stopwatch watcher1 = Stopwatch.StartNew(); + for (int i = 0; i < iteration; i++) + { + var res = jClass.Invoke("getArray"); + if (res.Length != length) { throw new System.Exception(); } + } + watcher1.Stop(); + + System.GC.Collect(); + Java.Lang.System.Gc(); + + Stopwatch watcher2 = Stopwatch.StartNew(); + for (int i = 0; i < iteration; i++) + { + var res = jClass.Invoke("getByteBuffer"); + var array = res.ToArray(); + if (array.Length != length) { throw new System.Exception(); } + } + watcher2.Stop(); + + System.GC.Collect(); + Java.Lang.System.Gc(); + + Stopwatch watcher3 = Stopwatch.StartNew(); + for (int i = 0; i < iteration; i++) + { + var res = jClass.Invoke("getByteBuffer"); + res.ToArray(ref bytes, false); + if (bytes.Length != length) { throw new System.Exception(); } + } + watcher3.Stop(); + + System.GC.Collect(); + Java.Lang.System.Gc(); + + System.Runtime.InteropServices.GCHandle handle = System.Runtime.InteropServices.GCHandle.Alloc(bytes, System.Runtime.InteropServices.GCHandleType.Pinned); + + Stopwatch watcher4 = Stopwatch.StartNew(); + for (int i = 0; i < iteration; i++) + { + var res = jClass.Invoke("getByteBuffer"); + res.ToDirectBuffer().CopyTo(handle.AddrOfPinnedObject(), bytes.Length, 0, bytes.Length); + if (bytes.Length != length) { throw new System.Exception(); } + } + watcher4.Stop(); + + System.GC.Collect(); + Java.Lang.System.Gc(); + + Stopwatch watcher5 = Stopwatch.StartNew(); + for (int i = 0; i < iteration; i++) + { + var res = jClass.Invoke("getByteBuffer"); + if (res.Remaining() != length) { throw new System.Exception(); } + } + watcher5.Stop(); + + Console.WriteLine($"{length,Padding} - Array {TimeSpan.FromTicks(watcher1.ElapsedTicks / iteration)} - ByteBuffer {TimeSpan.FromTicks(watcher2.ElapsedTicks / iteration)} ({((double)watcher1.ElapsedTicks / watcher2.ElapsedTicks) * 100:0.##}%) - ByteBufferNoNew {TimeSpan.FromTicks(watcher3.ElapsedTicks / iteration)} ({((double)watcher1.ElapsedTicks / watcher3.ElapsedTicks) * 100:0.##}%) - ByteBufferNoAlloc {TimeSpan.FromTicks(watcher4.ElapsedTicks / iteration)} ({((double)watcher1.ElapsedTicks / watcher4.ElapsedTicks) * 100:0.##}%) - ByteBufferNoGet {TimeSpan.FromTicks(watcher5.ElapsedTicks / iteration)} ({((double)watcher1.ElapsedTicks / watcher5.ElapsedTicks) * 100:0.##}%)"); + } + } +} diff --git a/tests/net/JNetTest.sln b/tests/net/JNetTest.sln index 38b1186800..e0685ed4ff 100644 --- a/tests/net/JNetTest.sln +++ b/tests/net/JNetTest.sln @@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "jnetAWTApp", "..\..\src\net EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{360FAD84-7744-4B18-8624-1B5AE07534F6}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JNetByteBufferTest", "JNetByteBufferTest\JNetByteBufferTest.csproj", "{96FDB00B-58C0-4DA5-B254-B5970706DD70}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -63,6 +65,10 @@ Global {BD61B857-1919-4546-8B87-3F525BF269F1}.Debug|Any CPU.Build.0 = Debug|Any CPU {BD61B857-1919-4546-8B87-3F525BF269F1}.Release|Any CPU.ActiveCfg = Release|Any CPU {BD61B857-1919-4546-8B87-3F525BF269F1}.Release|Any CPU.Build.0 = Release|Any CPU + {96FDB00B-58C0-4DA5-B254-B5970706DD70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {96FDB00B-58C0-4DA5-B254-B5970706DD70}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96FDB00B-58C0-4DA5-B254-B5970706DD70}.Release|Any CPU.ActiveCfg = Release|Any CPU + {96FDB00B-58C0-4DA5-B254-B5970706DD70}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -76,6 +82,7 @@ Global {6F8234A7-1336-49D9-A68A-3327DFBDFCDD} = {D622CE4D-8CE5-486A-9AAE-97AC093122C7} {17DC32BC-9C75-4846-B08D-D90241B71436} = {D622CE4D-8CE5-486A-9AAE-97AC093122C7} {BD61B857-1919-4546-8B87-3F525BF269F1} = {D622CE4D-8CE5-486A-9AAE-97AC093122C7} + {96FDB00B-58C0-4DA5-B254-B5970706DD70} = {360FAD84-7744-4B18-8624-1B5AE07534F6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0A7C16DC-1BAA-44BC-AA1C-D40B7B61878E} diff --git a/tests/net/JNetTest/Program.cs b/tests/net/JNetTest/Program.cs index dc88d21fe9..29acbb1b8d 100644 --- a/tests/net/JNetTest/Program.cs +++ b/tests/net/JNetTest/Program.cs @@ -24,6 +24,9 @@ using MASES.JCOBridge.C2JBridge; using System.Threading.Tasks; using System.Linq; +using Java.Nio; +using MASES.JCOBridge.C2JBridge.JVMInterop; +using MASES.JNet.Specific; namespace MASES.JNetTest { @@ -34,34 +37,121 @@ static void Main(string[] args) JNetTestCore.CreateGlobalInstance(); var appArgs = JNetTestCore.FilteredArgs; - var cls = Java.Lang.Class.Of>(); - var cls2 = JNetTestCore.Class>(); + TestExtensions(); - var res = cls.Equals(cls2); - System.Console.WriteLine($"Class are equals: {res}"); + TestMemoryStream(); + + TestByteBuffers(); + + TestEquality(); + + TestSingleton(); + + TestSimpleOperatorsExtension("a", "b", "c"); + + TestArrays(); + + TestOperators(); + + TestIterator(); + + TestAsyncIterator().Wait(); + } + + static void TestSingleton() + { + System.Console.WriteLine("TestSingleton"); try { var set = Collections.Singleton((Java.Lang.String)"test"); - if (appArgs.Length != 0) set.Add(appArgs[0]); + set.Add("testData"); } catch (UnsupportedOperationException) { System.Console.WriteLine("Add Operation not supported as expected"); } catch (System.Exception ex) { System.Console.WriteLine(ex.Message); } + } - TestArrays(); + static void TestEquality() + { + System.Console.WriteLine("TestEquality"); - TestOperators(); + var cls = Java.Lang.Class.Of>(); + var cls2 = JNetTestCore.Class>(); - TestIterator(); + var res = cls.Equals(cls2); + System.Console.WriteLine($"Class are equals: {res}"); + } - TestExtensions(); + static void TestMemoryStream() + { + System.Console.WriteLine("TestMemoryStream"); + + System.IO.MemoryStream ms = new(); + for (int i = 0; i < 100000; i++) + { + ms.WriteByte((byte)(i % byte.MaxValue)); + } - TestSimpleOperatorsExtension(); + ByteBuffer bb = (ByteBuffer) ms; + } - TestAsyncIterator().Wait(); + static void TestByteBuffers() + { + System.Console.WriteLine("TestByteBuffers"); + + byte[] bytes = new byte[100000]; + for (int i = 0; i < bytes.Length; i++) + { + bytes[i] = (byte)(i % byte.MaxValue); + } + + var bbCast = (Java.Nio.ByteBuffer)bytes; + + /* + var jClass = JNetTestCore.GlobalInstance.JVM.GetClass("org.mases.jcobridge.JCOBridgeSharedBuffer"); + var jj = jClass.Invoke("Create", bytes); + var backObj = jj.Invoke("getBackingIndex"); + */ + byte[] newArray; + //JVMBridgeSharedBuffer.TryGetValue(backObj, out newArray); + + var direct = JNetTestCore.GlobalInstance.JVM.NewDirectBuffer(bytes); + + var getSharedBuffer = JNetTestCore.GlobalInstance.JVM.GetDirectBuffer(direct.JavaObject); + var getSharedBufferInt = JNetTestCore.GlobalInstance.JVM.GetDirectBuffer(direct.JavaObject); + + direct[10] = 4; + + if (direct[10] != getSharedBuffer[10]) throw new System.InvalidOperationException(); + + var bb = Java.Nio.ByteBuffer.Wrap(bytes); + var hasArray = bb.HasArray(); + var isDirect = bb.IsDirect(); + var obj = bb.Array(); + + var bb2 = Java.Nio.ByteBuffer.AllocateDirect(bytes.Length); + bb2.Put(bytes, 0, bytes.Length); + + var intBuffer = bb2.AsIntBuffer(); + var newCapacity = intBuffer.Capacity(); + + var newBuffer = JNetTestCore.GlobalInstance.JVM.GetDirectBuffer(bb2.BridgeInstance); + + bb2.IsDirect(); + bb2.Rewind(); + bb2.Get(); + + var bb3 = Java.Nio.ByteBuffer.AllocateDirect(bytes.Length); + bb3.Put(bytes, 0, bytes.Length); + + JCOBridgeDirectBuffer db = (JCOBridgeDirectBuffer)bb3; + bb3.Rewind(); + JCOBridgeDirectBuffer db2 = (JCOBridgeDirectBuffer)bb3.AsIntBuffer(); + + newBuffer = JNetTestCore.GlobalInstance.JVM.GetDirectBuffer(bb3.BridgeInstance); } static void TestArrays() @@ -194,26 +284,52 @@ static void TestExtensions() var newDict = map.ToNetDictiony(); const int execution = 10000; - Stopwatch w = Stopwatch.StartNew(); - Java.Util.ArrayList alist = new Java.Util.ArrayList(); - for (int i = 0; i < execution; i++) - { - alist.Add(i); - } - w.Stop(); - System.Console.WriteLine($"ArrayList Elapsed ticks: {w.ElapsedTicks}"); - w.Restart(); - System.Collections.Generic.List nlist = new System.Collections.Generic.List(); - for (int i = 0; i < execution; i++) + + for (int index = 0; index < 5; index++) { - nlist.Add(i); - } - w.Stop(); - System.Console.WriteLine($"System.Collections.Generic.List Elapsed ticks: {w.ElapsedTicks}"); + Stopwatch w = Stopwatch.StartNew(); + Java.Util.ArrayList alist = new Java.Util.ArrayList(); + for (int i = 0; i < execution; i++) + { + alist.Add(i); + } + w.Stop(); + System.Console.WriteLine($"ArrayList Elapsed ticks: {w.ElapsedTicks}"); - //var collection = newDict.Values.ToJCollection(); - //var intermediate = collection.ToList>(); - var list = ((List)alist).ToList(); + w.Restart(); + System.Collections.Generic.List nlist = new System.Collections.Generic.List(); + for (int i = 0; i < execution; i++) + { + nlist.Add(i); + } + w.Stop(); + System.Console.WriteLine($"System.Collections.Generic.List Elapsed ticks: {w.ElapsedTicks}"); + + var tmpArray = nlist.ToArray(); + + w.Restart(); + var tmpJList = JNetHelper.ListFrom(tmpArray); + alist = new Java.Util.ArrayList(tmpJList); + w.Stop(); + System.Console.WriteLine($"Java.Util.ArrayList from array Elapsed ticks: {w.ElapsedTicks}"); + + var intBuffer = IntBuffer.From(tmpArray, false, false); + + w.Restart(); + tmpJList = JNetHelper.ListFrom(intBuffer); + alist = new Java.Util.ArrayList(tmpJList); + w.Stop(); + System.Console.WriteLine($"Java.Util.ArrayList from array premade buffer Elapsed ticks: {w.ElapsedTicks}"); + + w.Restart(); + tmpJList = JNetHelper.ListFrom(tmpArray, true); + alist = new Java.Util.ArrayList(tmpJList); + w.Stop(); + System.Console.WriteLine($"Java.Util.ArrayList from array buffered Elapsed ticks: {w.ElapsedTicks}"); + //var collection = newDict.Values.ToJCollection(); + //var intermediate = collection.ToList>(); + var list = ((List)alist).ToList(); + } } } }