From 489a83456e8201b4344a12cf0208829bdf92bb14 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 13:42:21 +0000 Subject: [PATCH 1/3] Setting up GitHub Classroom Feedback From 99832f75d5f93b787d30dff6d789b9af3c0a48c8 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 13:42:23 +0000 Subject: [PATCH 2/3] add deadline --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6495c30..0eb4312 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/AwTYhPar) # Лабораторная работа № 1: определение достижимости параллелизма и реализация параллельных алгоритмов. Шаги выполнения: From ff9d99b2a4a2c60292b67331031dfbb85c3faf04 Mon Sep 17 00:00:00 2001 From: jbisss Date: Tue, 14 Oct 2025 16:48:03 +0300 Subject: [PATCH 3/3] [hw_1] --- build.gradle.kts | 21 +- graphs/ParallelOnDifferentThreads.png | Bin 0 -> 34098 bytes graphs/SerialAndParallel.png | Bin 0 -> 42880 bytes results/index.html | 128 ++++++++ results/org.itmo.JSStressGraphTest.html | 293 ++++++++++++++++++ src/main/java/org/itmo/Graph.java | 148 ++++++++- src/main/java/org/itmo/UnsafeCounter.java | 13 + src/test/java/org/itmo/BFSTest.java | 126 ++++++-- src/test/java/org/itmo/JSStressGraphTest.java | 59 ++++ .../java/org/itmo/RandomGraphGenerator.java | 45 ++- src/test/java/org/itmo/UnsafeCounterTest.java | 27 ++ tmp/results.txt | 32 -- tmp/results_16_threads_0.txt | 39 +++ tmp/results_1_threads_0.txt | 39 +++ tmp/results_2_threads_0.txt | 39 +++ tmp/results_32_threads_0.txt | 39 +++ tmp/results_4_threads_0.txt | 35 +++ tmp/results_64_threads_0.txt | 39 +++ tmp/results_8_threads_0.txt | 39 +++ 19 files changed, 1093 insertions(+), 68 deletions(-) create mode 100644 graphs/ParallelOnDifferentThreads.png create mode 100644 graphs/SerialAndParallel.png create mode 100644 results/index.html create mode 100644 results/org.itmo.JSStressGraphTest.html create mode 100644 src/main/java/org/itmo/UnsafeCounter.java create mode 100644 src/test/java/org/itmo/JSStressGraphTest.java create mode 100644 src/test/java/org/itmo/UnsafeCounterTest.java create mode 100644 tmp/results_16_threads_0.txt create mode 100644 tmp/results_1_threads_0.txt create mode 100644 tmp/results_2_threads_0.txt create mode 100644 tmp/results_32_threads_0.txt create mode 100644 tmp/results_4_threads_0.txt create mode 100644 tmp/results_64_threads_0.txt create mode 100644 tmp/results_8_threads_0.txt diff --git a/build.gradle.kts b/build.gradle.kts index 3341beb..5370e10 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ plugins { kotlin("jvm") version "1.9.20" + java application } @@ -12,6 +13,8 @@ repositories { dependencies { testImplementation(kotlin("test")) + testImplementation("org.openjdk.jcstress:jcstress-core:0.16") + testAnnotationProcessor("org.openjdk.jcstress:jcstress-core:0.16") } tasks.test { @@ -24,4 +27,20 @@ kotlin { application { mainClass.set("MainKt") -} \ No newline at end of file +} + +// JCStress runner task: runs JCStress tests located on the test runtime classpath +// Use: ./gradlew jcstress [-PjcstressArgs="-v -m quick"] +tasks.register("jcstress") { + group = "verification" + description = "Run JCStress stress tests" + mainClass.set("org.openjdk.jcstress.Main") + classpath = sourceSets.test.get().runtimeClasspath + dependsOn("testClasses") + + val argsProp = project.findProperty("jcstressArgs") as String? + if (!argsProp.isNullOrBlank()) { + args = argsProp.split("\\s+".toRegex()) + } +} + diff --git a/graphs/ParallelOnDifferentThreads.png b/graphs/ParallelOnDifferentThreads.png new file mode 100644 index 0000000000000000000000000000000000000000..1c3f0fa80071c26693279425db7269143d66416b GIT binary patch literal 34098 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

AcVt9fQNlms!lGB|Y8VK(@LE@2*YX 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 YH*D{0zA+sJ>zwL_vW5~;;r5gN08+5<*8l(j literal 0 HcmV?d00001 diff --git a/graphs/SerialAndParallel.png b/graphs/SerialAndParallel.png new file mode 100644 index 0000000000000000000000000000000000000000..d274bb00193d2d41df101fa9ffb0afab5008c317 GIT binary patch literal 42880 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 literal 0 HcmV?d00001 diff --git a/results/index.html b/results/index.html new file mode 100644 index 0000000..450c940 --- /dev/null +++ b/results/index.html @@ -0,0 +1,128 @@ + + + +Java Concurrency Stress test report + + + + + + + + +
+

+0% +

+ + + +
 
Overall pass rate: 0/1 
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
java.specification.nameJava Platform API Specification
java.specification.vendorOracle Corporation
java.specification.version17
java.vendorAmazon.com Inc.
java.version17.0.11
java.vm.nameOpenJDK 64-Bit Server VM
java.vm.vendorAmazon.com Inc.
java.vm.version17.0.11+9-LTS
os.archamd64
os.nameLinux
os.version6.8.0-79-generic
+
+


+

FAILED tests

+

Strong asserts were violated. Correct implementations should have no assert failures here.

+ + + + + + + +
   org.itmo.JSStressGraphTest104FAILED
+
+
+

ERROR tests

+

Tests break for some reason, other than failing the assert. Correct implementations should have none.

+ +
+None! +
+
+
+

INTERESTING tests

+

Some interesting behaviors observed. This is for the plain curiosity.

+ +
+None! +
+
+
+

All tests

+

+ + + + + + + +
   org.itmo.JSStressGraphTest104FAILED
+
+ + diff --git a/results/org.itmo.JSStressGraphTest.html b/results/org.itmo.JSStressGraphTest.html new file mode 100644 index 0000000..f4ca01f --- /dev/null +++ b/results/org.itmo.JSStressGraphTest.html @@ -0,0 +1,293 @@ + + + +Java Concurrency Stress test report + + + +

org.itmo.JSStressGraphTest

+

Description and references

+

null

+

Environment

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
java.specification.nameJava Platform API Specification
java.specification.vendorOracle Corporation
java.specification.version17
java.vendorAmazon.com Inc.
java.version17.0.11
java.vm.nameOpenJDK 64-Bit Server VM
java.vm.vendorAmazon.com Inc.
java.vm.version17.0.11+9-LTS
os.archamd64
os.nameLinux
os.version6.8.0-79-generic
+

Results

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Compilation ModeScheduling ClassJava OptionsStatusObserved States
-3
Forbidden
No default case provided, assume Forbidden
split
+    actor1: Interpreter
+
    actor1: package group 0, core group 0
+
+
[-Dfile.encoding=UTF-8, -Duser.country=US, -Duser.language=en, -Duser.variant, -XX:+UseBiasedLocking]
+
FAILED3815
split
+    actor1: Interpreter
+
    actor1: package group 0, core group 0
+
+
[-Dfile.encoding=UTF-8, -Duser.country=US, -Duser.language=en, -Duser.variant, -XX:-UseBiasedLocking]
+
FAILED3889
split
+    actor1: C1
+
    actor1: package group 0, core group 0
+
+
[-Dfile.encoding=UTF-8, -Duser.country=US, -Duser.language=en, -Duser.variant, -XX:+UseBiasedLocking]
+
FAILED3889
split
+    actor1: C1
+
    actor1: package group 0, core group 0
+
+
[-Dfile.encoding=UTF-8, -Duser.country=US, -Duser.language=en, -Duser.variant, -XX:-UseBiasedLocking]
+
FAILED3889
split
+    actor1: C2
+
    actor1: package group 0, core group 0
+
+
[-Dfile.encoding=UTF-8, -Duser.country=US, -Duser.language=en, -Duser.variant, -XX:+UseBiasedLocking]
+
FAILED3889
split
+    actor1: C2
+
    actor1: package group 0, core group 0
+
+
[-Dfile.encoding=UTF-8, -Duser.country=US, -Duser.language=en, -Duser.variant, -XX:+UseBiasedLocking, -XX:+StressLCM, -XX:+StressGCM, -XX:+StressIGVN, -XX:+StressCCP, -XX:StressSeed=1253187770]
+
FAILED3741
split
+    actor1: C2
+
    actor1: package group 0, core group 0
+
+
[-Dfile.encoding=UTF-8, -Duser.country=US, -Duser.language=en, -Duser.variant, -XX:+UseBiasedLocking, -XX:+StressLCM, -XX:+StressGCM, -XX:+StressIGVN, -XX:+StressCCP, -XX:StressSeed=1947555701]
+
FAILED3741
split
+    actor1: C2
+
    actor1: package group 0, core group 0
+
+
[-Dfile.encoding=UTF-8, -Duser.country=US, -Duser.language=en, -Duser.variant, -XX:+UseBiasedLocking, -XX:+StressLCM, -XX:+StressGCM, -XX:+StressIGVN, -XX:+StressCCP, -XX:StressSeed=1990019871]
+
FAILED3889
split
+    actor1: C2
+
    actor1: package group 0, core group 0
+
+
[-Dfile.encoding=UTF-8, -Duser.country=US, -Duser.language=en, -Duser.variant, -XX:+UseBiasedLocking, -XX:+StressLCM, -XX:+StressGCM, -XX:+StressIGVN, -XX:+StressCCP, -XX:StressSeed=2109808913]
+
FAILED3815
split
+    actor1: C2
+
    actor1: package group 0, core group 0
+
+
[-Dfile.encoding=UTF-8, -Duser.country=US, -Duser.language=en, -Duser.variant, -XX:+UseBiasedLocking, -XX:+StressLCM, -XX:+StressGCM, -XX:+StressIGVN, -XX:+StressCCP, -XX:StressSeed=349458943]
+
FAILED3889
split
+    actor1: C2
+
    actor1: package group 0, core group 0
+
+
[-Dfile.encoding=UTF-8, -Duser.country=US, -Duser.language=en, -Duser.variant, -XX:-UseBiasedLocking]
+
FAILED3889
split
+    actor1: C2
+
    actor1: package group 0, core group 0
+
+
[-Dfile.encoding=UTF-8, -Duser.country=US, -Duser.language=en, -Duser.variant, -XX:-UseBiasedLocking, -XX:+StressLCM, -XX:+StressGCM, -XX:+StressIGVN, -XX:+StressCCP, -XX:StressSeed=143529006]
+
FAILED3815
split
+    actor1: C2
+
    actor1: package group 0, core group 0
+
+
[-Dfile.encoding=UTF-8, -Duser.country=US, -Duser.language=en, -Duser.variant, -XX:-UseBiasedLocking, -XX:+StressLCM, -XX:+StressGCM, -XX:+StressIGVN, -XX:+StressCCP, -XX:StressSeed=1517871585]
+
FAILED3889
split
+    actor1: C2
+
    actor1: package group 0, core group 0
+
+
[-Dfile.encoding=UTF-8, -Duser.country=US, -Duser.language=en, -Duser.variant, -XX:-UseBiasedLocking, -XX:+StressLCM, -XX:+StressGCM, -XX:+StressIGVN, -XX:+StressCCP, -XX:StressSeed=243224436]
+
FAILED3889
split
+    actor1: C2
+
    actor1: package group 0, core group 0
+
+
[-Dfile.encoding=UTF-8, -Duser.country=US, -Duser.language=en, -Duser.variant, -XX:-UseBiasedLocking, -XX:+StressLCM, -XX:+StressGCM, -XX:+StressIGVN, -XX:+StressCCP, -XX:StressSeed=547705354]
+
FAILED3889
split
+    actor1: C2
+
    actor1: package group 0, core group 0
+
+
[-Dfile.encoding=UTF-8, -Duser.country=US, -Duser.language=en, -Duser.variant, -XX:-UseBiasedLocking, -XX:+StressLCM, -XX:+StressGCM, -XX:+StressIGVN, -XX:+StressCCP, -XX:StressSeed=570703936]
+
FAILED3815
+

Messages

+

VM Output Streams

+

VM Error Streams

+ + diff --git a/src/main/java/org/itmo/Graph.java b/src/main/java/org/itmo/Graph.java index 141a0b6..95b1eba 100644 --- a/src/main/java/org/itmo/Graph.java +++ b/src/main/java/org/itmo/Graph.java @@ -1,13 +1,27 @@ package org.itmo; -import java.util.*; -import java.util.concurrent.*; +import java.util.HashMap; +import java.util.List; +import java.util.LinkedList; +import java.util.ArrayList; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; class Graph { + + private static int THREAD_COUNT = 4; + + private static Queue[] batches; + private final int V; - private final ArrayList[] adjList; + private final List[] adjList; + + public Map visitedMap; Graph(int vertices) { this.V = vertices; @@ -23,7 +37,64 @@ void addEdge(int src, int dest) { } } - void parallelBFS(int startVertex) { + void parallelBFS(int startVertex, int threadCount) { + THREAD_COUNT = threadCount; + batches = new LinkedList[THREAD_COUNT]; + for (int i = 0; i < THREAD_COUNT; i++) { + batches[i] = new LinkedList<>(); + } + ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT); + + AtomicBoolean[] visited = new AtomicBoolean[V]; + for (int i = 0; i < visited.length; i++) { + visited[i] = new AtomicBoolean(false); + } + + Queue queue = new ConcurrentLinkedQueue<>(); + + queue.add(startVertex); + visited[startVertex].set(true); + + while (!queue.isEmpty()) { + List> tasks = getCurrentLevelTasks(queue, visited); + + try { + if (!tasks.isEmpty()) executorService.invokeAll(tasks); + } catch (InterruptedException e) { + throw new RuntimeException("Parallel BFS failed...", e); + } + } + + executorService.shutdown(); + } + + private List> getCurrentLevelTasks(Queue queue, AtomicBoolean[] visited) { + int size = queue.size(); + int batchSize = size / THREAD_COUNT > 0 ? size / THREAD_COUNT : size; + for (int i = 0; i < size; i++) { + int index = i / batchSize >= batches.length ? batches.length - 1 : i / batchSize; + batches[index].add(queue.poll()); + } + return formTasks(queue, visited); + } + + private List> formTasks(Queue queue, AtomicBoolean[] visited) { + List> tasks = new ArrayList<>(); + for (Queue batch : batches) { + tasks.add(() -> { + int startVertex; + while (!batch.isEmpty()) { + startVertex = batch.poll(); + for (int n : adjList[startVertex]) { + if (visited[n].compareAndSet(false, true)) { + queue.add(n); + } + } + } + return null; + }); + } + return tasks; } //Generated by ChatGPT @@ -47,4 +118,71 @@ void bfs(int startVertex) { } } + void parallelBfsForTest(int startVertex, int threadCount) { + THREAD_COUNT = threadCount; + batches = new LinkedList[THREAD_COUNT]; + for (int i = 0; i < THREAD_COUNT; i++) { + batches[i] = new LinkedList<>(); + } + ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT); + + visitedMap = new HashMap<>(V); + + Queue queue = new ConcurrentLinkedQueue<>(); + + queue.add(startVertex); + visitedMap.put(startVertex, 1); + + while (!queue.isEmpty()) { + List> tasks = getCurrentLevelTasksForTest(queue, visitedMap); + + try { + if (!tasks.isEmpty()) executorService.invokeAll(tasks); + } catch (InterruptedException e) { + throw new RuntimeException("Parallel BFS failed...", e); + } + } + + executorService.shutdown(); + } + + private List> getCurrentLevelTasksForTest(Queue queue, Map visitedMap) { + int size = queue.size(); + int batchSize = size / THREAD_COUNT > 0 ? size / THREAD_COUNT : size; + for (int i = 0; i < size; i++) { + int index = i / batchSize >= batches.length ? batches.length - 1 : i / batchSize; + batches[index].add(queue.poll()); + } + return formTasksForTest(queue, visitedMap); + } + + private List> formTasksForTest(Queue queue, Map visitedMap) { + List> tasks = new ArrayList<>(); + for (Queue batch : batches) { + tasks.add(() -> { + int startVertex; + while (!batch.isEmpty()) { + startVertex = batch.poll(); + + for (int n : adjList[startVertex]) { + // non-valid + if (!visitedMap.containsKey(n)) { + visitedMap.put(n, 0); + Integer counter = visitedMap.get(n); + counter++; + visitedMap.put(n, counter); + queue.add(n); + } else { + Integer counter = visitedMap.get(n); + counter++; + visitedMap.put(n, counter); + } + // non-valid + } + } + return null; + }); + } + return tasks; + } } diff --git a/src/main/java/org/itmo/UnsafeCounter.java b/src/main/java/org/itmo/UnsafeCounter.java new file mode 100644 index 0000000..1041a21 --- /dev/null +++ b/src/main/java/org/itmo/UnsafeCounter.java @@ -0,0 +1,13 @@ +package org.itmo; + +public class UnsafeCounter { + private int counter = 0; + + public void increment() { + counter++; // <-- гонка данных + } + + public int get() { + return counter; + } +} diff --git a/src/test/java/org/itmo/BFSTest.java b/src/test/java/org/itmo/BFSTest.java index 7bf9098..22ed23b 100644 --- a/src/test/java/org/itmo/BFSTest.java +++ b/src/test/java/org/itmo/BFSTest.java @@ -4,34 +4,108 @@ import java.io.FileWriter; import java.io.IOException; -import java.nio.Buffer; -import java.util.HashSet; +import java.util.ArrayList; +import java.util.List; import java.util.Random; -import java.util.function.BiFunction; -import java.util.stream.IntStream; public class BFSTest { @Test - public void bfsTest() throws IOException { - int[] sizes = new int[]{10, 100, 1000, 10_000, 10_000, 50_000, 100_000, 1_000_000, 2_000_000}; - int[] connections = new int[]{50, 500, 5000, 50_000, 100_000, 1_000_000, 1_000_000, 10_000_000, 10_000_000}; - Random r = new Random(42); - try (FileWriter fw = new FileWriter("tmp/results.txt")) { - for (int i = 0; i < sizes.length; i++) { - System.out.println("--------------------------"); - System.out.println("Generating graph of size " + sizes[i] + " ...wait"); - Graph g = new RandomGraphGenerator().generateGraph(r, sizes[i], connections[i]); - System.out.println("Generation completed!\nStarting bfs"); - long serialTime = executeSerialBfsAndGetTime(g); - long parallelTime = executeParallelBfsAndGetTime(g); - fw.append("Times for " + sizes[i] + " vertices and " + connections[i] + " connections: "); - fw.append("\nSerial: " + serialTime); - fw.append("\nParallel: " + parallelTime); - fw.append("\n--------\n"); + public void bfsNonValidTest() throws IOException { + bfsNonValidTest(1, 4); + } + + @Test + public void bfsTestSoloTest() throws IOException { + bfsTestBase(1, 4); + } + + @Test + public void bfsTestSomeTests() throws IOException { + bfsTestBase(10, 4); + } + + @Test + public void bfsTestSomeTestsOnSeveralThreads() throws IOException { + int triesCount = 1; + int[] threadAmount = new int[]{1, 2, 4, 8, 16, 32, 64}; + for (int threadsToTest : threadAmount) { + bfsTestBase(triesCount, threadsToTest); + } + } + + private void bfsTestBase(int tries, int threads) throws IOException { + List> overAllSerialStats = new ArrayList<>(); + List> overAllParallelStats = new ArrayList<>(); + for (int j = 0; j < tries; j++) { + int[] sizes = new int[]{10, 100, 1000, 10_000, 10_000, 50_000, 100_000, 1_000_000, 2_000_000}; + int[] connections = new int[]{50, 500, 5000, 50_000, 100_000, 1_000_000, 1_000_000, 10_000_000, 10_000_000}; + Random r = new Random(42); + List currentSerialTimes = new ArrayList<>(); + List currentParallelTimes = new ArrayList<>(); + overAllSerialStats.add(currentSerialTimes); + overAllParallelStats.add(currentParallelTimes); + try (FileWriter fw = new FileWriter("tmp/results_" + threads + "_threads" + "_" + j + ".txt")) { + for (int i = 0; i < sizes.length; i++) { + System.out.println("--------------------------"); + System.out.println("Try number: " + j); + System.out.println("On threads: " + threads); + System.out.println("Generating graph of size " + sizes[i] + " ...wait"); + Graph g = new RandomGraphGenerator().generateGraph(r, sizes[i], connections[i]); + System.out.println("Generation completed!\nStarting bfs"); + long serialTime = executeSerialBfsAndGetTime(g); + currentSerialTimes.add(serialTime); + long parallelTime = executeParallelBfsAndGetTime(g, threads); + currentParallelTimes.add(parallelTime); + fw.append("Times for " + sizes[i] + " vertices and " + connections[i] + " connections: "); + fw.append("\nSerial: " + serialTime); + fw.append("\nParallel: " + parallelTime); + fw.append("\n--------\n"); + } + fw.append("Overall:\nSerial: " + currentSerialTimes + "\nParallel: " + currentParallelTimes); + fw.flush(); + } + } + System.out.println(overAllSerialStats); + System.out.println("---------------------"); + System.out.println(overAllParallelStats); + } + + private void bfsNonValidTest(int tries, int threads) throws IOException { + List> overAllSerialStats = new ArrayList<>(); + List> overAllParallelStats = new ArrayList<>(); + for (int j = 0; j < tries; j++) { + int[] sizes = new int[]{10, 100, 1000, 10_000, 10_000, 50_000, 100_000, 1_000_000}; + int[] connections = new int[]{50, 500, 5000, 50_000, 100_000, 1_000_000, 1_000_000, 10_000_000}; + Random r = new Random(42); + List currentSerialTimes = new ArrayList<>(); + List currentParallelTimes = new ArrayList<>(); + overAllSerialStats.add(currentSerialTimes); + overAllParallelStats.add(currentParallelTimes); + try (FileWriter fw = new FileWriter("tmp/results_" + threads + "_threads" + "_" + j + ".txt")) { + for (int i = 0; i < sizes.length; i++) { + System.out.println("--------------------------"); + System.out.println("Try number: " + j); + System.out.println("On threads: " + threads); + System.out.println("Generating graph of size " + sizes[i] + " ...wait"); + Graph g = new RandomGraphGenerator().generateGraph(r, sizes[i], connections[i]); + System.out.println("Generation completed!\nStarting bfs"); + long serialTime = executeSerialBfsAndGetTime(g); + currentSerialTimes.add(serialTime); + long parallelTime = executeParallelBfsAndGetTimeNonValid(g, threads); + currentParallelTimes.add(parallelTime); + fw.append("Times for " + sizes[i] + " vertices and " + connections[i] + " connections: "); + fw.append("\nSerial: " + serialTime); + fw.append("\nParallel: " + parallelTime); + fw.append("\n--------\n"); + } + fw.append("Overall:\nSerial: " + currentSerialTimes + "\nParallel: " + currentParallelTimes); + fw.flush(); } - fw.flush(); } + System.out.println(overAllSerialStats); + System.out.println("---------------------"); + System.out.println(overAllParallelStats); } @@ -42,11 +116,17 @@ private long executeSerialBfsAndGetTime(Graph g) { return endTime - startTime; } - private long executeParallelBfsAndGetTime(Graph g) { + private long executeParallelBfsAndGetTime(Graph g, int threadCount) { long startTime = System.currentTimeMillis(); - g.parallelBFS(0); + g.parallelBFS(0, threadCount); long endTime = System.currentTimeMillis(); return endTime - startTime; } + private long executeParallelBfsAndGetTimeNonValid(Graph g, int threadCount) { + long startTime = System.currentTimeMillis(); + g.parallelBfsForTest(0, threadCount); + long endTime = System.currentTimeMillis(); + return endTime - startTime; + } } diff --git a/src/test/java/org/itmo/JSStressGraphTest.java b/src/test/java/org/itmo/JSStressGraphTest.java new file mode 100644 index 0000000..7d1ca6e --- /dev/null +++ b/src/test/java/org/itmo/JSStressGraphTest.java @@ -0,0 +1,59 @@ +package org.itmo; + +import org.openjdk.jcstress.annotations.Actor; +import org.openjdk.jcstress.annotations.Arbiter; +import org.openjdk.jcstress.annotations.Expect; +import org.openjdk.jcstress.annotations.JCStressTest; +import org.openjdk.jcstress.annotations.Outcome; +import org.openjdk.jcstress.annotations.State; +import org.openjdk.jcstress.infra.results.I_Result; + +import java.util.Random; + +@JCStressTest +@Outcome(id = "0", expect = Expect.ACCEPTABLE, desc = "Каждое значение было валидно обработано только 1 раз") +@Outcome(id = "1", expect = Expect.ACCEPTABLE_INTERESTING, desc = "Небольшое отклонение от нормы") +@Outcome(id = "2", expect = Expect.ACCEPTABLE_INTERESTING, desc = "Немного больше небольшого отклонения от нормы") +@Outcome(id = "3", expect = Expect.ACCEPTABLE_INTERESTING, desc = "Среднее отклонение от нормы") +@Outcome(id = "4", expect = Expect.ACCEPTABLE_INTERESTING, desc = "Большое отклонение от нормы") +@State +public class JSStressGraphTest { + + private static final int actorsCount = 4; + private final Graph graph; + + private final int size = 10; + + { + Random random = new Random(2); + int connections = 10; + graph = new RandomGraphGenerator().generateGraph(random, size, connections); + } + + @Actor public void actor1() { graph.parallelBfsForTest(0, 1); } + @Actor public void actor2() { graph.parallelBfsForTest(0, 1); } + @Actor public void actor3() { graph.parallelBfsForTest(0, 1); } + @Actor public void actor4() { graph.parallelBfsForTest(0, 1); } + + @Arbiter + public void arbiter(I_Result r) { + int visitCounter = 0; + for (Integer i : graph.visitedMap.values()) { + visitCounter += actorsCount * i; + } + int abs = Math.abs(visitCounter - size); + if (abs == size) { + r.r1 = 0; + } else { + if (abs > size && abs < size * 1.5) { + r.r1 = 1; + } else if (abs < size * 2.5) { + r.r1 = 2; + } else if (abs < size * 3.5) { + r.r1 = 3; + } else { + r.r1 = 4; + } + } + } +} diff --git a/src/test/java/org/itmo/RandomGraphGenerator.java b/src/test/java/org/itmo/RandomGraphGenerator.java index fdb888c..1a57226 100644 --- a/src/test/java/org/itmo/RandomGraphGenerator.java +++ b/src/test/java/org/itmo/RandomGraphGenerator.java @@ -1,7 +1,9 @@ package org.itmo; import java.util.Arrays; +import java.util.HashSet; import java.util.Random; +import java.util.Set; import java.util.SplittableRandom; import java.util.concurrent.ForkJoinPool; import java.util.stream.IntStream; @@ -11,22 +13,27 @@ public class RandomGraphGenerator { private long pack(int u, int v) { return (((long) u) << 32) | (v & 0xffffffffL); } + private int unpackU(long key) { return (int) (key >>> 32); } + private int unpackV(long key) { return (int) (key & 0xffffffffL); } Graph generateGraph(Random r, int size, int numEdges) { + if (size < 1) throw new IllegalArgumentException("size must be >= 1"); if (numEdges < size - 1) throw new IllegalArgumentException("We need min size-1 edges"); long maxDirected = (long) size * (size - 1); if (numEdges > maxDirected) throw new IllegalArgumentException("Too many edges for directed graph without self-loops"); - int[] perm = java.util.stream.IntStream.range(0, size).toArray(); - for (int i = size - 1; i > 1; i--) { - int j = 1 + r.nextInt(i); - int tmp = perm[i]; perm[i] = perm[j]; perm[j] = tmp; + int[] perm = IntStream.range(0, size).toArray(); + for (int i = size - 1; i > 0; i--) { + int j = r.nextInt(i + 1); + int tmp = perm[i]; + perm[i] = perm[j]; + perm[j] = tmp; } final int chainCount = size - 1; @@ -74,7 +81,7 @@ Graph generateGraph(Random r, int size, int numEdges) { while (unique < numEdges) { int missing = numEdges - unique; - int extra = Math.max(missing / 2, 10_000); // небольшой запас + int extra = Math.max(missing / 2, 10_000); int add = missing + extra; long[] more = new long[unique + add]; @@ -109,6 +116,31 @@ Graph generateGraph(Random r, int size, int numEdges) { keys = more; } + Set chainSet = new HashSet<>(chainCount * 2); + for (int i = 1; i < size; i++) { + chainSet.add(pack(perm[i - 1], perm[i])); + } + + int p = 0; + for (int i = 0; i < unique && p < chainCount; i++) { + long e = keys[i]; + if (chainSet.remove(e)) { + // swap keys[p] и keys[i] + long tmp = keys[p]; + keys[p] = keys[i]; + keys[i] = tmp; + p++; + } + } + + SplittableRandom shuf = base.split(); + for (int i = p; i < numEdges; i++) { + int j = i + shuf.nextInt(unique - i); + long tmp = keys[i]; + keys[i] = keys[j]; + keys[j] = tmp; + } + Graph g = new Graph(size); for (int i = 0; i < numEdges; i++) { long key = keys[i]; @@ -118,5 +150,4 @@ Graph generateGraph(Random r, int size, int numEdges) { } return g; } - -} +} \ No newline at end of file diff --git a/src/test/java/org/itmo/UnsafeCounterTest.java b/src/test/java/org/itmo/UnsafeCounterTest.java new file mode 100644 index 0000000..c0fa97d --- /dev/null +++ b/src/test/java/org/itmo/UnsafeCounterTest.java @@ -0,0 +1,27 @@ +package org.itmo; + +import org.openjdk.jcstress.annotations.*; +import org.openjdk.jcstress.infra.results.I_Result; + +//@JCStressTest +//@Outcome(id = "5", expect = Expect.ACCEPTABLE, desc = "Все 5 инкрементов выполнены корректно") +//@Outcome(id = "1", expect = Expect.ACCEPTABLE_INTERESTING, desc = "Гонка данных: часть инкрементов потерялась") +//@Outcome(id = "2", expect = Expect.ACCEPTABLE_INTERESTING, desc = "Гонка данных: часть инкрементов потерялась") +//@Outcome(id = "3", expect = Expect.ACCEPTABLE_INTERESTING, desc = "Гонка данных: часть инкрементов потерялась") +//@Outcome(id = "4", expect = Expect.ACCEPTABLE_INTERESTING, desc = "Гонка данных: часть инкрементов потерялась") +//@State +public class UnsafeCounterTest { + + private UnsafeCounter counter = new UnsafeCounter(); + +// @Actor public void actor1() { counter.increment(); } +// @Actor public void actor2() { counter.increment(); } +// @Actor public void actor3() { counter.increment(); } +// @Actor public void actor4() { counter.increment(); } +// @Actor public void actor5() { counter.increment(); } + +// @Arbiter + public void arbiter(I_Result r) { + r.r1 = counter.get(); + } +} diff --git a/tmp/results.txt b/tmp/results.txt index 027e7f9..e69de29 100644 --- a/tmp/results.txt +++ b/tmp/results.txt @@ -1,32 +0,0 @@ -Times for 10 vertices and 50 connections: -Serial: 0 -Parallel: 0 --------- -Times for 100 vertices and 500 connections: -Serial: 0 -Parallel: 0 --------- -Times for 1000 vertices and 5000 connections: -Serial: 1 -Parallel: 0 --------- -Times for 10000 vertices and 50000 connections: -Serial: 3 -Parallel: 0 --------- -Times for 10000 vertices and 100000 connections: -Serial: 2 -Parallel: 0 --------- -Times for 50000 vertices and 1000000 connections: -Serial: 30 -Parallel: 0 --------- -Times for 100000 vertices and 1000000 connections: -Serial: 18 -Parallel: 0 --------- -Times for 1000000 vertices and 10000000 connections: -Serial: 307 -Parallel: 0 --------- diff --git a/tmp/results_16_threads_0.txt b/tmp/results_16_threads_0.txt new file mode 100644 index 0000000..58ee756 --- /dev/null +++ b/tmp/results_16_threads_0.txt @@ -0,0 +1,39 @@ +Times for 10 vertices and 50 connections: +Serial: 0 +Parallel: 3 +-------- +Times for 100 vertices and 500 connections: +Serial: 0 +Parallel: 2 +-------- +Times for 1000 vertices and 5000 connections: +Serial: 0 +Parallel: 2 +-------- +Times for 10000 vertices and 50000 connections: +Serial: 3 +Parallel: 4 +-------- +Times for 10000 vertices and 100000 connections: +Serial: 5 +Parallel: 4 +-------- +Times for 50000 vertices and 1000000 connections: +Serial: 45 +Parallel: 18 +-------- +Times for 100000 vertices and 1000000 connections: +Serial: 31 +Parallel: 23 +-------- +Times for 1000000 vertices and 10000000 connections: +Serial: 1452 +Parallel: 319 +-------- +Times for 2000000 vertices and 10000000 connections: +Serial: 2732 +Parallel: 2444 +-------- +Overall: +Serial: [0, 0, 0, 3, 5, 45, 31, 1452, 2732] +Parallel: [3, 2, 2, 4, 4, 18, 23, 319, 2444] \ No newline at end of file diff --git a/tmp/results_1_threads_0.txt b/tmp/results_1_threads_0.txt new file mode 100644 index 0000000..b1bddcc --- /dev/null +++ b/tmp/results_1_threads_0.txt @@ -0,0 +1,39 @@ +Times for 10 vertices and 50 connections: +Serial: 0 +Parallel: 2 +-------- +Times for 100 vertices and 500 connections: +Serial: 0 +Parallel: 1 +-------- +Times for 1000 vertices and 5000 connections: +Serial: 2 +Parallel: 3 +-------- +Times for 10000 vertices and 50000 connections: +Serial: 17 +Parallel: 18 +-------- +Times for 10000 vertices and 100000 connections: +Serial: 14 +Parallel: 13 +-------- +Times for 50000 vertices and 1000000 connections: +Serial: 24 +Parallel: 33 +-------- +Times for 100000 vertices and 1000000 connections: +Serial: 83 +Parallel: 81 +-------- +Times for 1000000 vertices and 10000000 connections: +Serial: 1480 +Parallel: 755 +-------- +Times for 2000000 vertices and 10000000 connections: +Serial: 2730 +Parallel: 2786 +-------- +Overall: +Serial: [0, 0, 2, 17, 14, 24, 83, 1480, 2730] +Parallel: [2, 1, 3, 18, 13, 33, 81, 755, 2786] \ No newline at end of file diff --git a/tmp/results_2_threads_0.txt b/tmp/results_2_threads_0.txt new file mode 100644 index 0000000..ec02038 --- /dev/null +++ b/tmp/results_2_threads_0.txt @@ -0,0 +1,39 @@ +Times for 10 vertices and 50 connections: +Serial: 0 +Parallel: 1 +-------- +Times for 100 vertices and 500 connections: +Serial: 0 +Parallel: 1 +-------- +Times for 1000 vertices and 5000 connections: +Serial: 1 +Parallel: 2 +-------- +Times for 10000 vertices and 50000 connections: +Serial: 3 +Parallel: 9 +-------- +Times for 10000 vertices and 100000 connections: +Serial: 4 +Parallel: 8 +-------- +Times for 50000 vertices and 1000000 connections: +Serial: 25 +Parallel: 22 +-------- +Times for 100000 vertices and 1000000 connections: +Serial: 82 +Parallel: 63 +-------- +Times for 1000000 vertices and 10000000 connections: +Serial: 1451 +Parallel: 833 +-------- +Times for 2000000 vertices and 10000000 connections: +Serial: 2731 +Parallel: 2170 +-------- +Overall: +Serial: [0, 0, 1, 3, 4, 25, 82, 1451, 2731] +Parallel: [1, 1, 2, 9, 8, 22, 63, 833, 2170] \ No newline at end of file diff --git a/tmp/results_32_threads_0.txt b/tmp/results_32_threads_0.txt new file mode 100644 index 0000000..f40073b --- /dev/null +++ b/tmp/results_32_threads_0.txt @@ -0,0 +1,39 @@ +Times for 10 vertices and 50 connections: +Serial: 0 +Parallel: 3 +-------- +Times for 100 vertices and 500 connections: +Serial: 0 +Parallel: 4 +-------- +Times for 1000 vertices and 5000 connections: +Serial: 1 +Parallel: 4 +-------- +Times for 10000 vertices and 50000 connections: +Serial: 2 +Parallel: 5 +-------- +Times for 10000 vertices and 100000 connections: +Serial: 4 +Parallel: 5 +-------- +Times for 50000 vertices and 1000000 connections: +Serial: 58 +Parallel: 20 +-------- +Times for 100000 vertices and 1000000 connections: +Serial: 45 +Parallel: 25 +-------- +Times for 1000000 vertices and 10000000 connections: +Serial: 939 +Parallel: 789 +-------- +Times for 2000000 vertices and 10000000 connections: +Serial: 2703 +Parallel: 2483 +-------- +Overall: +Serial: [0, 0, 1, 2, 4, 58, 45, 939, 2703] +Parallel: [3, 4, 4, 5, 5, 20, 25, 789, 2483] \ No newline at end of file diff --git a/tmp/results_4_threads_0.txt b/tmp/results_4_threads_0.txt new file mode 100644 index 0000000..3ba8811 --- /dev/null +++ b/tmp/results_4_threads_0.txt @@ -0,0 +1,35 @@ +Times for 10 vertices and 50 connections: +Serial: 0 +Parallel: 7 +-------- +Times for 100 vertices and 500 connections: +Serial: 1 +Parallel: 7 +-------- +Times for 1000 vertices and 5000 connections: +Serial: 3 +Parallel: 8 +-------- +Times for 10000 vertices and 50000 connections: +Serial: 23 +Parallel: 34 +-------- +Times for 10000 vertices and 100000 connections: +Serial: 20 +Parallel: 30 +-------- +Times for 50000 vertices and 1000000 connections: +Serial: 34 +Parallel: 73 +-------- +Times for 100000 vertices and 1000000 connections: +Serial: 105 +Parallel: 132 +-------- +Times for 1000000 vertices and 10000000 connections: +Serial: 2007 +Parallel: 4652 +-------- +Overall: +Serial: [0, 1, 3, 23, 20, 34, 105, 2007] +Parallel: [7, 7, 8, 34, 30, 73, 132, 4652] \ No newline at end of file diff --git a/tmp/results_64_threads_0.txt b/tmp/results_64_threads_0.txt new file mode 100644 index 0000000..02558af --- /dev/null +++ b/tmp/results_64_threads_0.txt @@ -0,0 +1,39 @@ +Times for 10 vertices and 50 connections: +Serial: 0 +Parallel: 6 +-------- +Times for 100 vertices and 500 connections: +Serial: 0 +Parallel: 5 +-------- +Times for 1000 vertices and 5000 connections: +Serial: 0 +Parallel: 33 +-------- +Times for 10000 vertices and 50000 connections: +Serial: 3 +Parallel: 8 +-------- +Times for 10000 vertices and 100000 connections: +Serial: 5 +Parallel: 8 +-------- +Times for 50000 vertices and 1000000 connections: +Serial: 19 +Parallel: 20 +-------- +Times for 100000 vertices and 1000000 connections: +Serial: 82 +Parallel: 46 +-------- +Times for 1000000 vertices and 10000000 connections: +Serial: 1441 +Parallel: 297 +-------- +Times for 2000000 vertices and 10000000 connections: +Serial: 3032 +Parallel: 2456 +-------- +Overall: +Serial: [0, 0, 0, 3, 5, 19, 82, 1441, 3032] +Parallel: [6, 5, 33, 8, 8, 20, 46, 297, 2456] \ No newline at end of file diff --git a/tmp/results_8_threads_0.txt b/tmp/results_8_threads_0.txt new file mode 100644 index 0000000..29ee81a --- /dev/null +++ b/tmp/results_8_threads_0.txt @@ -0,0 +1,39 @@ +Times for 10 vertices and 50 connections: +Serial: 0 +Parallel: 1 +-------- +Times for 100 vertices and 500 connections: +Serial: 0 +Parallel: 1 +-------- +Times for 1000 vertices and 5000 connections: +Serial: 0 +Parallel: 2 +-------- +Times for 10000 vertices and 50000 connections: +Serial: 3 +Parallel: 4 +-------- +Times for 10000 vertices and 100000 connections: +Serial: 4 +Parallel: 4 +-------- +Times for 50000 vertices and 1000000 connections: +Serial: 46 +Parallel: 17 +-------- +Times for 100000 vertices and 1000000 connections: +Serial: 30 +Parallel: 24 +-------- +Times for 1000000 vertices and 10000000 connections: +Serial: 1465 +Parallel: 331 +-------- +Times for 2000000 vertices and 10000000 connections: +Serial: 2714 +Parallel: 1896 +-------- +Overall: +Serial: [0, 0, 0, 3, 4, 46, 30, 1465, 2714] +Parallel: [1, 1, 2, 4, 4, 17, 24, 331, 1896] \ No newline at end of file