├── .gitignore ├── src └── main │ └── java │ └── dev │ └── george │ └── performance │ ├── tests │ ├── impl │ │ ├── RandomTest.java │ │ ├── AdditionTest.java │ │ ├── DivisionTest.java │ │ ├── SubtractionTest.java │ │ ├── GetStaticTest.java │ │ ├── MultiplicationTest.java │ │ ├── PutStaticTest.java │ │ ├── InvokeSpecialTest.java │ │ ├── ArrayTest.java │ │ ├── ExceptionTest.java │ │ ├── DirectoryTest.java │ │ ├── InvokeStaticTest.java │ │ ├── InvokeDynamicTest.java │ │ ├── IOTest.java │ │ ├── FileReadTest.java │ │ ├── FileWriteTest.java │ │ └── ReflectionTest.java │ └── PerformanceTest.java │ ├── reflection │ └── ReflectionExample.java │ ├── result │ └── PerformanceResults.java │ ├── report │ ├── ParsedReport.java │ └── PerformanceReport.java │ └── JavaNativePerformanceTest.java ├── LICENSE ├── reports ├── report-1686353754726.json └── report-native-1686354270015.json ├── pom.xml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | target/ 3 | test/ 4 | 5 | *.iml 6 | 7 | .gitattributes -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/tests/impl/RandomTest.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.tests.impl; 2 | 3 | import dev.george.performance.tests.PerformanceTest; 4 | 5 | public class RandomTest extends PerformanceTest { 6 | 7 | @Override 8 | public void handle(int iteration) { 9 | double number = Math.random(); 10 | } 11 | 12 | @Override 13 | public int getIterations() { 14 | return 1800; 15 | } 16 | 17 | @Override 18 | public String getTestName() { 19 | return "random"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/tests/impl/AdditionTest.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.tests.impl; 2 | 3 | import dev.george.performance.tests.PerformanceTest; 4 | 5 | public class AdditionTest extends PerformanceTest { 6 | 7 | @Override 8 | public void handle(int iteration) { 9 | int result = iteration + RANDOM_NUMBER; 10 | } 11 | 12 | @Override 13 | public String getTestName() { 14 | return "addition"; 15 | } 16 | 17 | @Override 18 | public int getIterations() { 19 | return 1000; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/tests/impl/DivisionTest.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.tests.impl; 2 | 3 | import dev.george.performance.tests.PerformanceTest; 4 | 5 | public class DivisionTest extends PerformanceTest { 6 | 7 | @Override 8 | public void handle(int iteration) { 9 | int result = (iteration + 1000) / RANDOM_NUMBER; 10 | } 11 | 12 | @Override 13 | public String getTestName() { 14 | return "division"; 15 | } 16 | 17 | @Override 18 | public int getIterations() { 19 | return 1000; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/tests/impl/SubtractionTest.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.tests.impl; 2 | 3 | import dev.george.performance.tests.PerformanceTest; 4 | 5 | public class SubtractionTest extends PerformanceTest { 6 | 7 | @Override 8 | public void handle(int iteration) { 9 | int result = iteration - RANDOM_NUMBER; 10 | } 11 | 12 | @Override 13 | public String getTestName() { 14 | return "subtraction"; 15 | } 16 | 17 | @Override 18 | public int getIterations() { 19 | return 1000; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/tests/impl/GetStaticTest.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.tests.impl; 2 | 3 | import dev.george.performance.tests.PerformanceTest; 4 | 5 | public class GetStaticTest extends PerformanceTest { 6 | 7 | private static final int TEST = 5; 8 | 9 | @Override 10 | public void handle(int iteration) { 11 | int a = TEST; 12 | } 13 | 14 | @Override 15 | public int getIterations() { 16 | return 1000; 17 | } 18 | 19 | @Override 20 | public String getTestName() { 21 | return "get_static"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/tests/impl/MultiplicationTest.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.tests.impl; 2 | 3 | import dev.george.performance.tests.PerformanceTest; 4 | 5 | public class MultiplicationTest extends PerformanceTest { 6 | 7 | @Override 8 | public void handle(int iteration) { 9 | int result = (iteration + 1000) + RANDOM_NUMBER; 10 | } 11 | 12 | @Override 13 | public String getTestName() { 14 | return "multiplication"; 15 | } 16 | 17 | @Override 18 | public int getIterations() { 19 | return 1000; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/tests/impl/PutStaticTest.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.tests.impl; 2 | 3 | import dev.george.performance.tests.PerformanceTest; 4 | 5 | public class PutStaticTest extends PerformanceTest { 6 | 7 | private static int TEST = -1; 8 | 9 | @Override 10 | public void handle(int iteration) { 11 | TEST = iteration; 12 | } 13 | 14 | @Override 15 | public int getIterations() { 16 | return 1000; 17 | } 18 | 19 | @Override 20 | public String getTestName() { 21 | return "put_static"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/tests/PerformanceTest.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.tests; 2 | 3 | public abstract class PerformanceTest { 4 | 5 | protected static final int RANDOM_NUMBER = 5; 6 | 7 | public abstract void handle(int iteration); 8 | 9 | public abstract int getIterations(); 10 | 11 | public abstract String getTestName(); 12 | 13 | public void preTest() {} 14 | 15 | public void cleanup() {} 16 | 17 | public long start(int iteration) { 18 | long nanoTime = System.nanoTime(); 19 | 20 | handle(iteration); 21 | 22 | return System.nanoTime() - nanoTime; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/tests/impl/InvokeSpecialTest.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.tests.impl; 2 | 3 | import dev.george.performance.tests.PerformanceTest; 4 | 5 | public class InvokeSpecialTest extends PerformanceTest { 6 | 7 | private static class InvokeSpecialTestExample { 8 | 9 | } 10 | 11 | @Override 12 | public String getTestName() { 13 | return "invoke_special"; 14 | } 15 | 16 | @Override 17 | public void handle(int iteration) { 18 | new InvokeSpecialTestExample(); 19 | } 20 | 21 | @Override 22 | public int getIterations() { 23 | return 2500; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/tests/impl/ArrayTest.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.tests.impl; 2 | 3 | import dev.george.performance.tests.PerformanceTest; 4 | 5 | public class ArrayTest extends PerformanceTest { 6 | 7 | @Override 8 | public void handle(int iteration) { 9 | int[] array = new int[iteration + 1]; 10 | 11 | for (int i = 0; i < iteration; i++) { 12 | array[i] = RANDOM_NUMBER; 13 | } 14 | } 15 | 16 | @Override 17 | public String getTestName() { 18 | return "array"; 19 | } 20 | 21 | @Override 22 | public int getIterations() { 23 | return 2000; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/tests/impl/ExceptionTest.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.tests.impl; 2 | 3 | import dev.george.performance.tests.PerformanceTest; 4 | 5 | import java.io.IOException; 6 | 7 | public class ExceptionTest extends PerformanceTest { 8 | 9 | @Override 10 | public void handle(int iteration) { 11 | try { 12 | throw new IOException(); 13 | } catch (IOException exc) { 14 | 15 | } 16 | } 17 | 18 | @Override 19 | public String getTestName() { 20 | return "exception"; 21 | } 22 | 23 | @Override 24 | public int getIterations() { 25 | return 1500; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/tests/impl/DirectoryTest.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.tests.impl; 2 | 3 | import dev.george.performance.tests.PerformanceTest; 4 | 5 | import java.io.File; 6 | 7 | public class DirectoryTest extends PerformanceTest { 8 | 9 | @Override 10 | public void handle(int iteration) { 11 | File directory = new File(iteration + File.separator); 12 | 13 | directory.mkdir(); 14 | 15 | directory.delete(); 16 | } 17 | 18 | @Override 19 | public int getIterations() { 20 | return 60; 21 | } 22 | 23 | @Override 24 | public String getTestName() { 25 | return "directory"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/tests/impl/InvokeStaticTest.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.tests.impl; 2 | 3 | import dev.george.performance.tests.PerformanceTest; 4 | 5 | public class InvokeStaticTest extends PerformanceTest { 6 | 7 | private static int testInvokeStatic(int iteration) { 8 | return iteration + 1; 9 | } 10 | 11 | @Override 12 | public void handle(int iteration) { 13 | int result = testInvokeStatic(iteration); 14 | } 15 | 16 | @Override 17 | public String getTestName() { 18 | return "invoke_static"; 19 | } 20 | 21 | @Override 22 | public int getIterations() { 23 | return 800; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/reflection/ReflectionExample.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.reflection; 2 | 3 | public class ReflectionExample { 4 | 5 | public static final int EXAMPLE_PUBLIC_STATIC_FIELD = 0; 6 | private static final int EXAMPLE_PRIVATE_STATIC_FIELD = 1; 7 | 8 | public int examplePublicField = 0; 9 | private int examplePrivateField = 1; 10 | 11 | private ReflectionExample() {} 12 | 13 | public int examplePublicMethod() { 14 | return 0; 15 | } 16 | 17 | private int examplePrivateMethod() { 18 | return 1; 19 | } 20 | 21 | public static int examplePublicStaticMethod() { 22 | return 0; 23 | } 24 | 25 | private static int examplePrivateStaticMethod() { 26 | return 1; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/tests/impl/InvokeDynamicTest.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.tests.impl; 2 | 3 | import dev.george.performance.tests.PerformanceTest; 4 | 5 | public class InvokeDynamicTest extends PerformanceTest { 6 | 7 | /* 8 | * Since Lambda functions are implemented with 9 | * invokedynamics, we're able to create a simple 10 | * Runnable and execute it in order to measure its 11 | * performance impact. 12 | */ 13 | @Override 14 | public void handle(int iteration) { 15 | Runnable runnable = () -> { 16 | int a = 5 + 10; 17 | }; 18 | 19 | runnable.run(); 20 | } 21 | 22 | @Override 23 | public String getTestName() { 24 | return "invoke_dynamic"; 25 | } 26 | 27 | @Override 28 | public int getIterations() { 29 | return 3000; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/tests/impl/IOTest.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.tests.impl; 2 | 3 | import dev.george.performance.tests.PerformanceTest; 4 | 5 | import java.io.IOException; 6 | import java.nio.file.Path; 7 | import java.nio.file.Paths; 8 | 9 | public class IOTest extends PerformanceTest { 10 | 11 | @Override 12 | public void handle(int iteration) { 13 | Path path = Paths.get("performance-test.tmp"); 14 | 15 | if (path.toFile().exists()) { 16 | throw new IllegalStateException("File already exists!"); 17 | } 18 | 19 | try { 20 | path.toFile().createNewFile(); 21 | } catch (IOException exc) { 22 | exc.printStackTrace(); 23 | } 24 | 25 | path.toFile().delete(); 26 | } 27 | 28 | @Override 29 | public String getTestName() { 30 | return "io_test"; 31 | } 32 | 33 | @Override 34 | public int getIterations() { 35 | return 50; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 George 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/result/PerformanceResults.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.result; 2 | 3 | import dev.george.performance.tests.PerformanceTest; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class PerformanceResults { 9 | 10 | private final List durations = new ArrayList<>(); 11 | 12 | public PerformanceResults(PerformanceTest test) { 13 | test.preTest(); 14 | 15 | for (int i = 0; i < test.getIterations(); i++) { 16 | durations.add(test.start(i)); 17 | } 18 | 19 | test.cleanup(); 20 | } 21 | 22 | public long getMin() { 23 | return durations.stream() 24 | .mapToLong(value -> value) 25 | .min() 26 | .getAsLong(); 27 | } 28 | 29 | public long getMax() { 30 | return durations.stream() 31 | .mapToLong(value -> value) 32 | .max() 33 | .getAsLong(); 34 | } 35 | 36 | public double getAverage() { 37 | return durations.stream() 38 | .mapToLong(value -> value) 39 | .average() 40 | .getAsDouble(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/tests/impl/FileReadTest.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.tests.impl; 2 | 3 | import dev.george.performance.tests.PerformanceTest; 4 | import lombok.SneakyThrows; 5 | 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.nio.file.Paths; 9 | import java.util.concurrent.ThreadLocalRandom; 10 | 11 | public class FileReadTest extends PerformanceTest { 12 | 13 | private final Path tempPath = Paths.get("read"); 14 | 15 | @SneakyThrows 16 | @Override 17 | public void preTest() { 18 | byte[] randomBytes = new byte[512]; 19 | 20 | for (int i =0 ; i < randomBytes.length; i++) { 21 | randomBytes[i] = (byte) ThreadLocalRandom.current().nextInt(Byte.MIN_VALUE, Byte.MAX_VALUE); 22 | } 23 | 24 | tempPath.toFile().createNewFile(); 25 | Files.write(tempPath, randomBytes); 26 | } 27 | 28 | @SneakyThrows 29 | @Override 30 | public void handle(int iteration) { 31 | Files.readAllBytes(tempPath); 32 | } 33 | 34 | @Override 35 | public void cleanup() { 36 | tempPath.toFile().delete(); 37 | } 38 | 39 | @Override 40 | public int getIterations() { 41 | return 128; 42 | } 43 | 44 | @Override 45 | public String getTestName() { 46 | return "file_read"; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/tests/impl/FileWriteTest.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.tests.impl; 2 | 3 | import dev.george.performance.tests.PerformanceTest; 4 | import lombok.SneakyThrows; 5 | 6 | import java.io.File; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | import java.util.Arrays; 11 | import java.util.Objects; 12 | import java.util.concurrent.ThreadLocalRandom; 13 | 14 | public class FileWriteTest extends PerformanceTest { 15 | 16 | private final byte[] randomBytes = new byte[512]; 17 | private File directory; 18 | 19 | @Override 20 | public void preTest() { 21 | for (int i = 0; i < randomBytes.length; i++) { 22 | randomBytes[i] = (byte) ThreadLocalRandom.current().nextInt(Byte.MIN_VALUE, Byte.MAX_VALUE); 23 | } 24 | 25 | directory = new File("temp" + File.separator); 26 | directory.mkdir(); 27 | } 28 | 29 | @SneakyThrows 30 | @Override 31 | public void handle(int iteration) { 32 | Path path = Paths.get(directory.getPath() + File.separator + iteration); 33 | Files.write(path, randomBytes); 34 | } 35 | 36 | public void cleanup() { 37 | Arrays.stream(Objects.requireNonNull(directory.listFiles())).forEach(File::delete); 38 | directory.delete(); 39 | } 40 | 41 | @Override 42 | public int getIterations() { 43 | return 128; 44 | } 45 | 46 | @Override 47 | public String getTestName() { 48 | return "file_write"; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/report/ParsedReport.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.report; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonObject; 5 | import lombok.Getter; 6 | 7 | import java.io.IOException; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | @Getter 14 | public class ParsedReport { 15 | 16 | private final Map averages = new HashMap<>(); 17 | 18 | private final boolean isNative; 19 | 20 | private final double memoryRatio; 21 | 22 | public ParsedReport(Gson gson, Path path) { 23 | JsonObject object = null; 24 | 25 | try { 26 | object = gson.fromJson(new String(Files.readAllBytes(path)), JsonObject.class); 27 | } catch (IOException exc) { 28 | exc.printStackTrace(); 29 | 30 | throw new RuntimeException("An unexpected error occurred while parsing report " + path.toFile().getAbsolutePath() + "!"); 31 | } 32 | 33 | object.get("results").getAsJsonArray().forEach(result -> { 34 | JsonObject resultObject = result.getAsJsonObject(); 35 | 36 | averages.put(resultObject.get("name").getAsString(), resultObject.get("average").getAsDouble()); 37 | }); 38 | 39 | this.memoryRatio = object.get("memory_allocated").getAsDouble() / 40 | object.get("memory_in_use").getAsDouble(); 41 | 42 | this.isNative = path.toFile().getName().startsWith("report-native"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/report/PerformanceReport.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.report; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | import dev.george.performance.result.PerformanceResults; 6 | import dev.george.performance.tests.PerformanceTest; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public class PerformanceReport { 12 | 13 | private final Map results = new HashMap<>(); 14 | 15 | public void add(PerformanceTest test, PerformanceResults result) { 16 | results.put(test.getTestName(), result); 17 | } 18 | 19 | public JsonObject getResults() { 20 | JsonObject object = new JsonObject(); 21 | 22 | object.addProperty("memory_allocated", Runtime.getRuntime().maxMemory()); 23 | object.addProperty("memory_in_use", Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()); 24 | 25 | JsonArray resultsArray = new JsonArray(); 26 | 27 | results.forEach((name, performanceResults) -> { 28 | JsonObject resultsObject = new JsonObject(); 29 | 30 | resultsObject.addProperty("name", name); 31 | 32 | resultsObject.addProperty("min", performanceResults.getMin()); 33 | resultsObject.addProperty("max", performanceResults.getMax()); 34 | resultsObject.addProperty("average", performanceResults.getAverage()); 35 | 36 | resultsArray.add(resultsObject); 37 | }); 38 | 39 | object.add("results", resultsArray); 40 | 41 | return object; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /reports/report-1686353754726.json: -------------------------------------------------------------------------------- 1 | { 2 | "memory_allocated": 8544976896, 3 | "memory_in_use": 1998488, 4 | "results": [ 5 | { 6 | "name": "exception", 7 | "min": 200, 8 | "max": 24000, 9 | "average": 333.46666666666664 10 | }, 11 | { 12 | "name": "io_test", 13 | "min": 324200, 14 | "max": 898200, 15 | "average": 441320.0 16 | }, 17 | { 18 | "name": "reflection", 19 | "min": 14900, 20 | "max": 2024300, 21 | "average": 27002.133333333335 22 | }, 23 | { 24 | "name": "get_static", 25 | "min": 0, 26 | "max": 400, 27 | "average": 60.4 28 | }, 29 | { 30 | "name": "invoke_special", 31 | "min": 100, 32 | "max": 696500, 33 | "average": 431.0 34 | }, 35 | { 36 | "name": "subtraction", 37 | "min": 0, 38 | "max": 1000, 39 | "average": 65.1 40 | }, 41 | { 42 | "name": "directory", 43 | "min": 171400, 44 | "max": 702900, 45 | "average": 234208.33333333334 46 | }, 47 | { 48 | "name": "invoke_dynamic", 49 | "min": 100, 50 | "max": 764700, 51 | "average": 492.3333333333333 52 | }, 53 | { 54 | "name": "division", 55 | "min": 0, 56 | "max": 200, 57 | "average": 39.7 58 | }, 59 | { 60 | "name": "put_static", 61 | "min": 0, 62 | "max": 2300, 63 | "average": 63.2 64 | }, 65 | { 66 | "name": "invoke_static", 67 | "min": 0, 68 | "max": 5000, 69 | "average": 95.25 70 | }, 71 | { 72 | "name": "random", 73 | "min": 200, 74 | "max": 184000, 75 | "average": 497.8888888888889 76 | }, 77 | { 78 | "name": "file_read", 79 | "min": 59500, 80 | "max": 9301700, 81 | "average": 165956.25 82 | }, 83 | { 84 | "name": "array", 85 | "min": 100, 86 | "max": 514900, 87 | "average": 9735.95 88 | }, 89 | { 90 | "name": "file_write", 91 | "min": 188900, 92 | "max": 642300, 93 | "average": 294716.40625 94 | }, 95 | { 96 | "name": "multiplication", 97 | "min": 0, 98 | "max": 1900, 99 | "average": 66.2 100 | }, 101 | { 102 | "name": "addition", 103 | "min": 0, 104 | "max": 1900, 105 | "average": 48.2 106 | } 107 | ] 108 | } -------------------------------------------------------------------------------- /reports/report-native-1686354270015.json: -------------------------------------------------------------------------------- 1 | { 2 | "memory_allocated": 7596408832, 3 | "memory_in_use": 64438672, 4 | "results": [ 5 | { 6 | "name": "exception", 7 | "min": 3000, 8 | "max": 66700, 9 | "average": 3648.7406666666666 10 | }, 11 | { 12 | "name": "io_test", 13 | "min": 295101, 14 | "max": 775900, 15 | "average": 343864.02 16 | }, 17 | { 18 | "name": "reflection", 19 | "min": 13599, 20 | "max": 8201500, 21 | "average": 31945.846666666668 22 | }, 23 | { 24 | "name": "get_static", 25 | "min": 599, 26 | "max": 16601, 27 | "average": 732.722 28 | }, 29 | { 30 | "name": "invoke_special", 31 | "min": 799, 32 | "max": 437300, 33 | "average": 1281.3936 34 | }, 35 | { 36 | "name": "subtraction", 37 | "min": 599, 38 | "max": 13701, 39 | "average": 787.397 40 | }, 41 | { 42 | "name": "directory", 43 | "min": 156500, 44 | "max": 524700, 45 | "average": 181595.06666666668 46 | }, 47 | { 48 | "name": "invoke_dynamic", 49 | "min": 46601, 50 | "max": 4857201, 51 | "average": 71978.934 52 | }, 53 | { 54 | "name": "division", 55 | "min": 699, 56 | "max": 7800, 57 | "average": 761.392 58 | }, 59 | { 60 | "name": "put_static", 61 | "min": 599, 62 | "max": 8701, 63 | "average": 776.802 64 | }, 65 | { 66 | "name": "invoke_static", 67 | "min": 899, 68 | "max": 4347400, 69 | "average": 6585.765 70 | }, 71 | { 72 | "name": "random", 73 | "min": 799, 74 | "max": 147900, 75 | "average": 1056.7883333333334 76 | }, 77 | { 78 | "name": "file_read", 79 | "min": 132200, 80 | "max": 1002800, 81 | "average": 188183.59375 82 | }, 83 | { 84 | "name": "array", 85 | "min": 1201, 86 | "max": 261501, 87 | "average": 86631.1 88 | }, 89 | { 90 | "name": "file_write", 91 | "min": 235600, 92 | "max": 797900, 93 | "average": 305035.15625 94 | }, 95 | { 96 | "name": "multiplication", 97 | "min": 699, 98 | "max": 8700, 99 | "average": 765.12 100 | }, 101 | { 102 | "name": "addition", 103 | "min": 599, 104 | "max": 6800, 105 | "average": 774.109 106 | } 107 | ] 108 | } -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/tests/impl/ReflectionTest.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance.tests.impl; 2 | 3 | import dev.george.performance.tests.PerformanceTest; 4 | 5 | import java.lang.reflect.Constructor; 6 | import java.lang.reflect.Field; 7 | import java.lang.reflect.Method; 8 | 9 | public class ReflectionTest extends PerformanceTest { 10 | 11 | @Override 12 | public void handle(int iteration) { 13 | try { 14 | Class clazz = Class.forName("dev.george.performance.reflection.ReflectionExample"); 15 | Constructor constructor = clazz.getDeclaredConstructor(); 16 | 17 | Field publicStaticField = clazz.getField("EXAMPLE_PUBLIC_STATIC_FIELD"); 18 | Field privateStaticField = clazz.getDeclaredField("EXAMPLE_PRIVATE_STATIC_FIELD"); 19 | 20 | Field publicField = clazz.getField("examplePublicField"); 21 | Field privateField = clazz.getDeclaredField("examplePrivateField"); 22 | 23 | Method publicStaticMethod = clazz.getMethod("examplePublicStaticMethod"); 24 | Method privateStaticMethod = clazz.getDeclaredMethod("examplePrivateStaticMethod"); 25 | 26 | Method publicMethod = clazz.getMethod("examplePublicMethod"); 27 | Method privateMethod = clazz.getDeclaredMethod("examplePrivateMethod"); 28 | 29 | constructor.setAccessible(true); 30 | 31 | privateStaticField.setAccessible(true); 32 | privateStaticMethod.setAccessible(true); 33 | 34 | privateField.setAccessible(true); 35 | privateMethod.setAccessible(true); 36 | 37 | Object instance = constructor.newInstance(); 38 | 39 | int publicStaticFieldResult = publicStaticField.getInt(null); 40 | int privateStaticFieldResult = privateStaticField.getInt(null); 41 | 42 | int publicStaticMethodResult = (int) publicStaticMethod.invoke(null); 43 | int privateStaticMethodResult = (int) privateStaticMethod.invoke(null); 44 | 45 | int publicFieldResult = publicField.getInt(instance); 46 | int privateFieldResult = privateField.getInt(instance); 47 | 48 | int publicMethodResult = (int) publicMethod.invoke(instance); 49 | int privateMethodResult = (int) privateMethod.invoke(instance); 50 | } catch (Exception exc) { 51 | exc.printStackTrace(); 52 | } 53 | } 54 | 55 | @Override 56 | public int getIterations() { 57 | return 750; 58 | } 59 | 60 | @Override 61 | public String getTestName() { 62 | return "reflection"; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | dev.george 8 | java-native-obfuscation-test 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 8 13 | 8 14 | UTF-8 15 | 16 | 17 | 18 | 19 | 20 | org.apache.maven.plugins 21 | maven-shade-plugin 22 | 3.2.4 23 | 24 | 25 | package 26 | 27 | shade 28 | 29 | 30 | false 31 | 32 | 33 | 34 | 35 | 36 | org.apache.maven.plugins 37 | maven-jar-plugin 38 | 3.1.0 39 | 40 | 41 | 42 | true 43 | lib/ 44 | dev.george.performance.JavaNativePerformanceTest 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | com.google.code.gson 55 | gson 56 | 2.10.1 57 | compile 58 | jar 59 | 60 | 61 | 62 | org.projectlombok 63 | lombok 64 | 1.18.28 65 | provided 66 | 67 | 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java Native Obfuscation Performance Test 2 | 3 | ### Introduction 4 | 5 | Protection of Java applications can be enhanced in a few ways. Authors of programs 6 | can choose to use an obfuscator such as Zelix KlassMaster, Skidfuscator, 7 | and countless other ones, but oftentimes, this isn't enough. There's a limit 8 | as to how much you can protect an application in Java alone. 9 | 10 | This is where Java native obfuscators come into play. These tools are able to convert Java code 11 | into C++ code by converting the Java bytecode into native JNI calls. This C++ 12 | can then be compiled into native libraries, which are then securely loaded 13 | into a JAR file. After this, the bytecode is cleared from the method bodies 14 | of the methods being replaced with native function calls. 15 | 16 | As a result of this, reversing the JAR file by decompiling it is useless, 17 | as methods converted to native JNI calls won't have any bytecode to view. 18 | 19 | While it has its benefits, converting bytecode to native JNI calls 20 | is a very heavy process on the application calling the native functions. 21 | This program calculates the average expense of Java operations, to determine 22 | exactly how significant these native operations are on Java programs. 23 | 24 | ### Process 25 | 26 | The process behind measuring the performance impact isn't difficult. This 27 | program measures the following operations and actions: 28 | 29 | - Addition 30 | - Basic array operations 31 | - Directory creation 32 | - Division 33 | - Exceptions 34 | - File Reading 35 | - File Writing 36 | - GETSTATIC operations 37 | - INVOKEDYNAMIC operations 38 | - INVOKESPECIAL operations 39 | - INVOKESTATIC operations 40 | - IO operations 41 | - Multiplication 42 | - PUTSTATIC operations 43 | - Random Number Generation 44 | - Reflective operations 45 | - Subtraction 46 | 47 | The first step in measuring the performance differential is to establish 48 | a base speed for these operations, in nanoseconds, anywhere between 50 49 | and 3,000 times. 50 | 51 | Next, convert the bytecode to JNI calls (using one of the tools in the 52 | references section), and perform the same operations for the same amount of iterations. 53 | 54 | Finally, simply compare the numbers. 55 | 56 | ### Results 57 | 58 | Not surprisingly, bytecode level operations were exponentially faster 59 | an overwhelming majority of the time, though strangely, certain IO operations 60 | over twice as fast (128%) natively. 61 | 62 | The results are as follows: 63 | 64 | | Operation | Speed Difference | Call Type | 65 | |--------------------------|------------------|-----------| 66 | | Addition | 1,606.035% | Standard | 67 | | Array Operations | 889.806% | Standard | 68 | | Directory Operations | 128.973% | Native | 69 | | Division | 1,917.864% | Standard | 70 | | Exceptions | 1,094.185% | Standard | 71 | | File Read | 113.393% | Standard | 72 | | File Write | 103.501% | Standard | 73 | | GETSTATIC Operations | 1,213.116% | Standard | 74 | | INVOKEDYNAMIC Operations | 14,619.96% | Standard | 75 | | INVOKESPECIAL Operations | 297.307% | Standard | 76 | | INVOKESTATIC Operations | 6,914.189% | Standard | 77 | | IO Operations | 128.341% | Native | 78 | | Multiplication | 1,155.77% | Standard | 79 | | PUTSTATIC Operations | 1,229.117% | Standard | 80 | | Random Number Generation | 212.254% | Standard | 81 | | Reflection | 118.309% | Standard | 82 | | Subtraction | 1,209.519% | Standard | 83 | 84 | ### References / Tools 85 | 86 | - https://github.com/radioegor146/native-obfuscator - Opensource native obfuscator 87 | - https://jnic.dev - Commercial native obfuscator made by Konsolas 88 | -------------------------------------------------------------------------------- /src/main/java/dev/george/performance/JavaNativePerformanceTest.java: -------------------------------------------------------------------------------- 1 | package dev.george.performance; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.google.gson.JsonObject; 6 | import dev.george.performance.report.ParsedReport; 7 | import dev.george.performance.report.PerformanceReport; 8 | import dev.george.performance.result.PerformanceResults; 9 | import dev.george.performance.tests.PerformanceTest; 10 | import dev.george.performance.tests.impl.*; 11 | 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.nio.file.Files; 15 | import java.nio.file.Path; 16 | import java.nio.file.Paths; 17 | import java.text.NumberFormat; 18 | import java.util.*; 19 | import java.util.function.Function; 20 | 21 | public class JavaNativePerformanceTest { 22 | 23 | private final List performanceTests = Arrays.asList( 24 | new AdditionTest(), 25 | new ArrayTest(), 26 | new DirectoryTest(), 27 | new DivisionTest(), 28 | new ExceptionTest(), 29 | new FileReadTest(), 30 | new FileWriteTest(), 31 | new GetStaticTest(), 32 | new InvokeDynamicTest(), 33 | new InvokeSpecialTest(), 34 | new InvokeStaticTest(), 35 | new IOTest(), 36 | new MultiplicationTest(), 37 | new PutStaticTest(), 38 | new RandomTest(), 39 | new ReflectionTest(), 40 | new SubtractionTest() 41 | ); 42 | 43 | public void start() { 44 | Path outputDirectory = Paths.get("reports" + File.separator); 45 | Gson gson = new GsonBuilder() 46 | .setPrettyPrinting() 47 | .create(); 48 | 49 | if (!outputDirectory.toFile().isDirectory()) { 50 | outputDirectory.toFile().mkdir(); 51 | } 52 | 53 | File[] files = Objects.requireNonNull(outputDirectory.toFile().listFiles()); 54 | 55 | if (files.length >= 2) { 56 | if (Arrays.stream(files).anyMatch(file -> file.getName().startsWith("report-native"))) { 57 | List reports = Arrays.asList( 58 | new ParsedReport(gson, Paths.get(files[0].getAbsolutePath())), 59 | new ParsedReport(gson, Paths.get(files[1].getAbsolutePath())) 60 | ); 61 | 62 | performanceTests.forEach(test -> { 63 | Comparator averageComparator = Comparator.comparingDouble( 64 | parsedReport -> parsedReport.getAverages().get(test.getTestName())); 65 | 66 | Optional slowerOptional = reports.stream().max(averageComparator); 67 | Optional fasterOptional = reports.stream().min(averageComparator); 68 | 69 | Function reportToAverage = (report) -> report.getAverages().get(test.getTestName()); 70 | 71 | if (!slowerOptional.isPresent() || !fasterOptional.isPresent()) { 72 | throw new RuntimeException("An unexpected error occurred while attempting to compare test" + 73 | "results for " + test.getTestName() + "!"); 74 | } 75 | 76 | double slower = reportToAverage.apply(slowerOptional.get()); 77 | double faster = reportToAverage.apply(fasterOptional.get()); 78 | 79 | double ratio = slower / faster; 80 | 81 | System.out.printf("%s is faster by %s%% for %s!%n", 82 | fasterOptional.get().isNative() ? "Native" : "Non native", 83 | NumberFormat.getInstance().format((ratio * 100)), 84 | test.getTestName() 85 | ); 86 | }); 87 | } 88 | 89 | return; 90 | 91 | } 92 | System.out.println("Beginning performance tests..."); 93 | 94 | PerformanceReport report = new PerformanceReport(); 95 | 96 | performanceTests.forEach(test -> { 97 | long startTime = System.currentTimeMillis(); 98 | 99 | PerformanceResults results = new PerformanceResults(test); 100 | long timeElapsed = System.currentTimeMillis() - startTime; 101 | 102 | System.out.println("Successfully completed " + test.getTestName() + " in " + timeElapsed + " ms!"); 103 | 104 | report.add(test, results); 105 | }); 106 | 107 | Path outputFile = Paths.get(outputDirectory.toFile() + File.separator + "report-" + System.currentTimeMillis() + ".json"); 108 | 109 | try { 110 | Files.write(outputFile, gson.toJson(report.getResults()).getBytes()); 111 | } catch (IOException exc) { 112 | exc.printStackTrace(); 113 | } 114 | } 115 | 116 | public static void main(String[] args) { 117 | new JavaNativePerformanceTest().start(); 118 | } 119 | } 120 | --------------------------------------------------------------------------------