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: определение достижимости параллелизма и реализация параллельных алгоритмов. Шаги выполнения: diff --git a/build.gradle.kts b/build.gradle.kts index 3341beb..184de05 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ plugins { kotlin("jvm") version "1.9.20" + java application } @@ -12,10 +13,14 @@ repositories { dependencies { testImplementation(kotlin("test")) + testImplementation("org.openjdk.jcstress:jcstress-core:0.16") + testAnnotationProcessor("org.openjdk.jcstress:jcstress-core:0.16") } tasks.test { useJUnitPlatform() + minHeapSize = "6G" + maxHeapSize = "10G" } kotlin { @@ -24,4 +29,19 @@ 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/docs/performance_comparison_line.png b/docs/performance_comparison_line.png new file mode 100644 index 0000000..48c0a5a Binary files /dev/null and b/docs/performance_comparison_line.png differ diff --git a/docs/sync_error.png b/docs/sync_error.png new file mode 100644 index 0000000..5e58afd Binary files /dev/null and b/docs/sync_error.png differ diff --git a/docs/thread1.txt b/docs/thread1.txt new file mode 100644 index 0000000..88434c6 --- /dev/null +++ b/docs/thread1.txt @@ -0,0 +1,38 @@ +thread: 1 + +Times for 10 vertices and 50 connections: +Serial: 1 +Parallel: 2 +-------- +Times for 100 vertices and 500 connections: +Serial: 0 +Parallel: 1 +-------- +Times for 1000 vertices and 5000 connections: +Serial: 1 +Parallel: 0 +-------- +Times for 10000 vertices and 50000 connections: +Serial: 3 +Parallel: 3 +-------- +Times for 10000 vertices and 100000 connections: +Serial: 1 +Parallel: 3 +-------- +Times for 50000 vertices and 1000000 connections: +Serial: 12 +Parallel: 23 +-------- +Times for 100000 vertices and 1000000 connections: +Serial: 15 +Parallel: 27 +-------- +Times for 1000000 vertices and 10000000 connections: +Serial: 260 +Parallel: 385 +-------- +Times for 2000000 vertices and 10000000 connections: +Serial: 268 +Parallel: 341 +-------- diff --git a/docs/thread12.txt b/docs/thread12.txt new file mode 100644 index 0000000..cc3aa6a --- /dev/null +++ b/docs/thread12.txt @@ -0,0 +1,38 @@ +thread: 12 + +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: 1 +Parallel: 4 +-------- +Times for 10000 vertices and 50000 connections: +Serial: 3 +Parallel: 8 +-------- +Times for 10000 vertices and 100000 connections: +Serial: 2 +Parallel: 14 +-------- +Times for 50000 vertices and 1000000 connections: +Serial: 12 +Parallel: 5 +-------- +Times for 100000 vertices and 1000000 connections: +Serial: 15 +Parallel: 7 +-------- +Times for 1000000 vertices and 10000000 connections: +Serial: 177 +Parallel: 54 +-------- +Times for 2000000 vertices and 10000000 connections: +Serial: 368 +Parallel: 89 +-------- diff --git a/docs/thread2.txt b/docs/thread2.txt new file mode 100644 index 0000000..3f515b2 --- /dev/null +++ b/docs/thread2.txt @@ -0,0 +1,38 @@ +thread: 2 + +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: 0 +-------- +Times for 10000 vertices and 50000 connections: +Serial: 3 +Parallel: 3 +-------- +Times for 10000 vertices and 100000 connections: +Serial: 2 +Parallel: 6 +-------- +Times for 50000 vertices and 1000000 connections: +Serial: 14 +Parallel: 24 +-------- +Times for 100000 vertices and 1000000 connections: +Serial: 16 +Parallel: 15 +-------- +Times for 1000000 vertices and 10000000 connections: +Serial: 271 +Parallel: 209 +-------- +Times for 2000000 vertices and 10000000 connections: +Serial: 314 +Parallel: 213 +-------- diff --git a/docs/thread4.txt b/docs/thread4.txt new file mode 100644 index 0000000..9ed66e8 --- /dev/null +++ b/docs/thread4.txt @@ -0,0 +1,38 @@ +thread: 4 + +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: 1 +-------- +Times for 10000 vertices and 50000 connections: +Serial: 3 +Parallel: 3 +-------- +Times for 10000 vertices and 100000 connections: +Serial: 2 +Parallel: 5 +-------- +Times for 50000 vertices and 1000000 connections: +Serial: 13 +Parallel: 22 +-------- +Times for 100000 vertices and 1000000 connections: +Serial: 16 +Parallel: 16 +-------- +Times for 1000000 vertices and 10000000 connections: +Serial: 257 +Parallel: 104 +-------- +Times for 2000000 vertices and 10000000 connections: +Serial: 373 +Parallel: 147 +-------- diff --git a/docs/thread8.txt b/docs/thread8.txt new file mode 100644 index 0000000..fda62c2 --- /dev/null +++ b/docs/thread8.txt @@ -0,0 +1,38 @@ +thread: 8 + +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: 0 +Parallel: 2 +-------- +Times for 10000 vertices and 50000 connections: +Serial: 3 +Parallel: 7 +-------- +Times for 10000 vertices and 100000 connections: +Serial: 2 +Parallel: 14 +-------- +Times for 50000 vertices and 1000000 connections: +Serial: 12 +Parallel: 4 +-------- +Times for 100000 vertices and 1000000 connections: +Serial: 17 +Parallel: 7 +-------- +Times for 1000000 vertices and 10000000 connections: +Serial: 285 +Parallel: 75 +-------- +Times for 2000000 vertices and 10000000 connections: +Serial: 390 +Parallel: 103 +-------- diff --git a/docs/thread_comparison_line.png b/docs/thread_comparison_line.png new file mode 100644 index 0000000..29cd32c Binary files /dev/null and b/docs/thread_comparison_line.png differ diff --git a/src/main/java/org/itmo/Graph.java b/src/main/java/org/itmo/Graph.java index 141a0b6..63f031e 100644 --- a/src/main/java/org/itmo/Graph.java +++ b/src/main/java/org/itmo/Graph.java @@ -1,9 +1,11 @@ package org.itmo; import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicIntegerArray; class Graph { private final int V; @@ -24,6 +26,80 @@ void addEdge(int src, int dest) { } void parallelBFS(int startVertex) { + AtomicIntegerArray visited = new AtomicIntegerArray(V); + + List currentFrontier = new ArrayList<>(); + currentFrontier.add(startVertex); + visited.set(startVertex, 1); + + int numThreads = Runtime.getRuntime().availableProcessors(); + ExecutorService executor = Executors.newFixedThreadPool(numThreads); + + try { + while (!currentFrontier.isEmpty()) { + final List frontier = currentFrontier; + final int frontierSize = frontier.size(); + + List> threadLocalBuffers = new ArrayList<>(numThreads); + for (int t = 0; t < numThreads; t++) { + threadLocalBuffers.add(new ArrayList<>()); + } + + int chunkSize = Math.max(1, (frontierSize + numThreads - 1) / numThreads); + CountDownLatch latch = new CountDownLatch(numThreads); + + for (int threadId = 0; threadId < numThreads; threadId++) { + final int start = threadId * chunkSize; + final int end = Math.min(start + chunkSize, frontierSize); + final List localBuffer = threadLocalBuffers.get(threadId); + + if (start >= frontierSize) { + latch.countDown(); + continue; + } + + executor.execute(() -> { + try { + for (int i = start; i < end; i++) { + int vertex = frontier.get(i); + + for (int neighbor : adjList[vertex]) { + if (visited.compareAndSet(neighbor, 0, 1)) { + localBuffer.add(neighbor); + } + } + } + } finally { + latch.countDown(); + } + }); + } + + try { + latch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("BFS interrupted", e); + } + + List nextFrontier = new ArrayList<>(); + for (List buffer : threadLocalBuffers) { + nextFrontier.addAll(buffer); + } + + currentFrontier = nextFrontier; + } + } finally { + executor.shutdown(); + try { + if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { + executor.shutdownNow(); + } + } catch (InterruptedException e) { + executor.shutdownNow(); + Thread.currentThread().interrupt(); + } + } } //Generated by ChatGPT diff --git a/src/main/java/org/itmo/UnsafeCounter.java b/src/main/java/org/itmo/UnsafeCounter.java new file mode 100644 index 0000000..6e99ad2 --- /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; + } +} \ No newline at end of file diff --git a/src/test/java/org/itmo/GraphBFSSimpleRaceTest.java b/src/test/java/org/itmo/GraphBFSSimpleRaceTest.java new file mode 100644 index 0000000..aad31b5 --- /dev/null +++ b/src/test/java/org/itmo/GraphBFSSimpleRaceTest.java @@ -0,0 +1,54 @@ +package org.itmo; + +import org.openjdk.jcstress.annotations.*; +import org.openjdk.jcstress.infra.results.I_Result; + +@JCStressTest +@Outcome(id = "1", expect = Expect.ACCEPTABLE, desc = "Only one thread marked the vertex") +@Outcome(id = "2", expect = Expect.ACCEPTABLE_INTERESTING, desc = "Race condition: both threads marked vertex as visited") +@State +public class GraphBFSSimpleRaceTest { + + private final Graph graph; + private final VisitCounter counter; + private final boolean[] visited; + + public GraphBFSSimpleRaceTest() { + graph = new Graph(3); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + + visited = new boolean[3]; + counter = new VisitCounter(); + } + + static class VisitCounter { + private int markCount = 0; + + public void markVisited(int vertex, boolean[] visited) { + if (!visited[vertex]) { + visited[vertex] = true; + markCount++; + } + } + + public int getCount() { + return markCount; + } + } + + @Actor + public void actor1() { + counter.markVisited(1, visited); + } + + @Actor + public void actor2() { + counter.markVisited(1, visited); + } + + @Arbiter + public void arbiter(I_Result r) { + r.r1 = counter.getCount(); + } +} 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..58dbab4 --- /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(); + } +} \ No newline at end of file diff --git a/tmp/results.txt b/tmp/results.txt index 027e7f9..4d9f9a4 100644 --- a/tmp/results.txt +++ b/tmp/results.txt @@ -1,32 +1,36 @@ Times for 10 vertices and 50 connections: Serial: 0 -Parallel: 0 +Parallel: 2 -------- Times for 100 vertices and 500 connections: Serial: 0 -Parallel: 0 +Parallel: 1 -------- Times for 1000 vertices and 5000 connections: Serial: 1 -Parallel: 0 +Parallel: 4 -------- Times for 10000 vertices and 50000 connections: Serial: 3 -Parallel: 0 +Parallel: 8 -------- Times for 10000 vertices and 100000 connections: Serial: 2 -Parallel: 0 +Parallel: 14 -------- Times for 50000 vertices and 1000000 connections: -Serial: 30 -Parallel: 0 +Serial: 12 +Parallel: 5 -------- Times for 100000 vertices and 1000000 connections: -Serial: 18 -Parallel: 0 +Serial: 15 +Parallel: 7 -------- Times for 1000000 vertices and 10000000 connections: -Serial: 307 -Parallel: 0 +Serial: 177 +Parallel: 54 +-------- +Times for 2000000 vertices and 10000000 connections: +Serial: 368 +Parallel: 89 --------