From 97eb8a9c8d18dc39d42c0ff84f4a0d6dbb140567 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 08:04:01 +0000 Subject: [PATCH 1/3] Setting up GitHub Classroom Feedback From 3b20a746403f4d162b0077fc405ef9e738472b7d Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 08:04:04 +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 0c3e82128a4bd82297169dd500e9d7399159f088 Mon Sep 17 00:00:00 2001 From: "p.g.kuznetsov" Date: Tue, 23 Dec 2025 11:14:58 +0300 Subject: [PATCH 3/3] lab1: initial but complete --- .idea/.gitignore | 8 + .idea/.name | 1 + .idea/gradle.xml | 15 ++ .idea/kotlinc.xml | 6 + .idea/misc.xml | 5 + .idea/vcs.xml | 6 + README.md | 1 - build.gradle.kts | 22 +- charts/parallel_time_vs_threads.png | Bin 0 -> 31020 bytes charts/size_vs_time.png | Bin 0 -> 34687 bytes jcstress-results-2025-12-23-11-14-02.bin.gz | Bin 0 -> 10 bytes src/main/java/org/itmo/Graph.java | 217 +++++++++++++++++- src/main/java/org/itmo/UnsafeCounter.java | 13 ++ src/test/java/org/itmo/BFSTest.java | 81 ++++++- .../java/org/itmo/RandomGraphGenerator.java | 45 +++- src/test/java/org/itmo/UnsafeCounterTest.java | 27 +++ tmp/results.txt | 28 ++- 17 files changed, 444 insertions(+), 31 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/.name create mode 100644 .idea/gradle.xml create mode 100644 .idea/kotlinc.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 charts/parallel_time_vs_threads.png create mode 100644 charts/size_vs_time.png create mode 100644 jcstress-results-2025-12-23-11-14-02.bin.gz create mode 100644 src/main/java/org/itmo/UnsafeCounter.java create mode 100644 src/test/java/org/itmo/UnsafeCounterTest.java diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..ec463ac --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +leet \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..f9163b4 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..e805548 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..f79e5d5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 0eb4312..6495c30 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -[![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..f7bf52b 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,9 @@ repositories { dependencies { testImplementation(kotlin("test")) + testImplementation("org.openjdk.jcstress:jcstress-core:0.16") + testAnnotationProcessor("org.openjdk.jcstress:jcstress-core:0.16") + implementation("org.knowm.xchart:xchart:3.8.7") } tasks.test { @@ -24,4 +28,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/charts/parallel_time_vs_threads.png b/charts/parallel_time_vs_threads.png new file mode 100644 index 0000000000000000000000000000000000000000..c8988b8f9792dafefbe58b7b8c32345bae333416 GIT binary patch literal 31020 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%0AeVF6Q!@(Ld7pkD>)INJBqsl%z9bZb+>kio#u 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 literal 0 HcmV?d00001 diff --git a/charts/size_vs_time.png b/charts/size_vs_time.png new file mode 100644 index 0000000000000000000000000000000000000000..c60287ca024b2499dc531073b8b268e1be44d20f GIT binary patch literal 34687 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 frontier = new ConcurrentLinkedQueue<>(); + + for (int v = 0; v < V; v++) { + visited.set(v, 0); + } + + frontier.add(startVertex); + visited.set(startVertex, 1); + + try { + while (!frontier.isEmpty()) { + int tasks = Math.min(numThreads, frontier.size()); + CountDownLatch latch = new CountDownLatch(tasks); + List[] nextFrontiers = new ArrayList[tasks]; + + for (int t = 0; t < tasks; t++) { + final int taskId = t; + pool.submit(() -> { + List nextLevel = new ArrayList<>(); + try { + Integer node; + while ((node = frontier.poll()) != null) { + for (int neighbor : adjList[node]) { + if (visited.get(neighbor) == 0 && visited.compareAndSet(neighbor, 0, 1)) { + nextLevel.add(neighbor); + } + } + } + } finally { + nextFrontiers[taskId] = nextLevel; + latch.countDown(); + } + }); + } + + try { + latch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + + for (List level : nextFrontiers) { + frontier.addAll(level); + } + } + } finally { + pool.shutdown(); + } } + void parallelBFSUnsafe(int startVertex, Integer threadsCount) { + int numThreads = (threadsCount == null) ? Runtime.getRuntime().availableProcessors() : threadsCount; + AtomicIntegerArray visited = new AtomicIntegerArray(V); + ExecutorService pool = Executors.newFixedThreadPool(numThreads); + ConcurrentLinkedQueue frontier = new ConcurrentLinkedQueue<>(); + + for (int v = 0; v < V; v++) visited.set(v, 0); + + frontier.add(startVertex); + visited.set(startVertex, 1); + + try { + while (!frontier.isEmpty()) { + int tasks = Math.min(numThreads, frontier.size()); + CountDownLatch latch = new CountDownLatch(tasks); + List[] nextFrontiers = new ArrayList[tasks]; + + for (int t = 0; t < tasks; t++) { + final int taskId = t; + pool.submit(() -> { + List nextLevel = new ArrayList<>(); + try { + Integer node; + while ((node = frontier.poll()) != null) { + for (int neighbor : adjList[node]) { + if (visited.get(neighbor) == 0 && visited.compareAndSet(neighbor, 0, 1)) { + nextLevel.add(neighbor); + } + } + } + } finally { + nextFrontiers[taskId] = nextLevel; + latch.countDown(); + } + }); + } + + try { + latch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + + for (List level : nextFrontiers) frontier.addAll(level); + } + } finally { + pool.shutdown(); + } + } + +// void parallelBFS(int startVertex) { +// int numThreads = Runtime.getRuntime().availableProcessors(); +// ExecutorService executor = Executors.newFixedThreadPool(numThreads); +// +// AtomicBoolean[] visited = new AtomicBoolean[V]; +// for (int i = 0; i < V; i++) +// visited[i] = new AtomicBoolean(false); +// +// LevelQueues levelQueues = new LevelQueues(); +// +// visited[startVertex].set(true); +// levelQueues.current.add(startVertex); +// +// CyclicBarrier barrier = new CyclicBarrier(numThreads + 1); // +1 — главный поток +// AtomicInteger remaining = new AtomicInteger(); +// +// for (int t = 0; t < numThreads; t++) { +// executor.submit(() -> { +// while (true) { +// try { +// barrier.await(); +// } catch (Exception e) { +// System.out.println("barrier.await broken"); +// } +// +// if (levelQueues.current.isEmpty()) +// barrier.await(); +// +// Integer v; +// while ((v = levelQueues.current.poll()) != null) { +// for (int n : adjList[v]) { +// if (visited[n].compareAndSet(false, true)) { +// levelQueues.next.add(n); +// } +// } +// } +// +// if (remaining.decrementAndGet() == 0) { +// System.out.println("Level process ended"); +// } +// +// try { +// barrier.await(); +// } catch (Exception ignored) { +// } +// } +// }); +// } +// +// try { +// while (!levelQueues.current.isEmpty()) { +// remaining.set(numThreads); +// barrier.await(); +// barrier.await(); +// +// levelQueues.current = levelQueues.next; +// levelQueues.next = new ConcurrentLinkedQueue<>(); +// } +// } catch (Exception e) { +// System.out.println("main process barrier.await broken"); +// } +// +// executor.shutdownNow(); +// } +// +// void parallelBFS_2(int startVertex) { +// ExecutorService pool = Executors.newFixedThreadPool( +// Runtime.getRuntime().availableProcessors()); +// +// AtomicBoolean[] visited = new AtomicBoolean[V]; +// for (int i = 0; i < V; i++) +// visited[i] = new AtomicBoolean(false); +// +// ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); +// queue.add(startVertex); +// visited[startVertex].set(true); +// +// while (!queue.isEmpty()) { +// Integer v = queue.poll(); +// if (v == null) continue; +// +// CompletableFuture.runAsync(() -> { +// for (int n : adjList[v]) { +// if (visited[n].compareAndSet(false, true)) { +// queue.add(n); +// } +// } +// }, pool); +// } +// +// pool.shutdown(); +// } +// +// private class LevelQueues { +// public ConcurrentLinkedQueue current = new ConcurrentLinkedQueue<>(); +// public ConcurrentLinkedQueue next = new ConcurrentLinkedQueue<>(); +// } + //Generated by ChatGPT void bfs(int startVertex) { boolean[] visited = new boolean[V]; @@ -36,12 +239,12 @@ void bfs(int startVertex) { queue.add(startVertex); while (!queue.isEmpty()) { - startVertex = queue.poll(); + startVertex = queue.poll(); // конкуренция за queue for (int n : adjList[startVertex]) { - if (!visited[n]) { + if (!visited[n]) { // здесь гонка visited[n] = true; - queue.add(n); + queue.add(n); // риск многократного добавления n } } } 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..6e63fda 100644 --- a/src/test/java/org/itmo/BFSTest.java +++ b/src/test/java/org/itmo/BFSTest.java @@ -1,11 +1,19 @@ package org.itmo; import org.junit.jupiter.api.Test; +import org.knowm.xchart.BitmapEncoder; +import org.knowm.xchart.XYChart; +import org.knowm.xchart.XYChartBuilder; +import org.knowm.xchart.XYSeries; +import org.knowm.xchart.style.markers.SeriesMarkers; +import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.nio.Buffer; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Random; import java.util.function.BiFunction; import java.util.stream.IntStream; @@ -16,6 +24,12 @@ public class BFSTest { 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}; + + new File("charts").mkdirs(); + + List sizesList = new ArrayList<>(); + List serialTimes = new ArrayList<>(); + List parallelTimes = new ArrayList<>(); Random r = new Random(42); try (FileWriter fw = new FileWriter("tmp/results.txt")) { for (int i = 0; i < sizes.length; i++) { @@ -24,7 +38,10 @@ public void bfsTest() throws IOException { 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); + long parallelTime = executeParallelBfsAndGetTime(g, null); + sizesList.add(sizes[i]); + serialTimes.add(serialTime); + parallelTimes.add(parallelTime); fw.append("Times for " + sizes[i] + " vertices and " + connections[i] + " connections: "); fw.append("\nSerial: " + serialTime); fw.append("\nParallel: " + parallelTime); @@ -32,6 +49,25 @@ public void bfsTest() throws IOException { } fw.flush(); } + int threadsTestIndex = Math.max(6, sizes.length - 1); + int baseSize = sizes[threadsTestIndex]; + int baseConn = connections[threadsTestIndex]; + + int[] threadCounts = new int[]{1, 2, 4, 8, 16, 32, 64}; + List threadsX = new ArrayList<>(); + List parallelByThreads = new ArrayList<>(); + + Graph gThreads = new RandomGraphGenerator().generateGraph(r, baseSize, baseConn); + + for (int tc : threadCounts) { + long t = executeParallelBfsAndGetTime(gThreads, tc); + threadsX.add(tc); + parallelByThreads.add(t); + System.out.println("Threads=" + tc + " -> parallel time " + t + " ms"); + } + + saveSizeChart(sizesList, serialTimes, parallelTimes, "charts/size_vs_time.png"); + saveThreadsChart(threadsX, parallelByThreads, baseSize, baseConn, "charts/parallel_time_vs_threads.png"); } @@ -42,11 +78,50 @@ private long executeSerialBfsAndGetTime(Graph g) { return endTime - startTime; } - private long executeParallelBfsAndGetTime(Graph g) { + private long executeParallelBfsAndGetTime(Graph g, Integer threads) { long startTime = System.currentTimeMillis(); - g.parallelBFS(0); + g.parallelBFS(0, threads); long endTime = System.currentTimeMillis(); return endTime - startTime; } + private void saveSizeChart(List sizes, List serialTimes, List parallelTimes, String outPath) throws IOException { + + double[] x = sizes.stream().mapToDouble(Integer::doubleValue).toArray(); + double[] ySerial = serialTimes.stream().mapToDouble(Long::doubleValue).toArray(); + double[] yParallel = parallelTimes.stream().mapToDouble(Long::doubleValue).toArray(); + + XYChart chart = new XYChartBuilder() + .width(900).height(600) + .title("Время выполнения vs размер входных данных") + .xAxisTitle("Количество вершин") + .yAxisTitle("Время (мс)") + .build(); + + XYSeries s1 = chart.addSeries("Serial", x, ySerial); + s1.setMarker(SeriesMarkers.CIRCLE); + + XYSeries s2 = chart.addSeries("Parallel", x, yParallel); + s2.setMarker(SeriesMarkers.DIAMOND); + + BitmapEncoder.saveBitmap(chart, outPath, BitmapEncoder.BitmapFormat.PNG); + } + + private void saveThreadsChart(List threads, List times, + int baseSize, int baseConn, String outPath) throws IOException { + double[] xThreads = threads.stream().mapToDouble(Integer::doubleValue).toArray(); + double[] yTimes = times.stream().mapToDouble(Long::doubleValue).toArray(); + + XYChart chart = new XYChartBuilder() + .width(900).height(600) + .title("Параллельный BFS: время vs число потоков (size=" + baseSize + ", edges≈" + baseConn + ")") + .xAxisTitle("Число потоков") + .yAxisTitle("Время (мс)") + .build(); + + XYSeries s = chart.addSeries("Parallel", xThreads, yTimes); + s.setMarker(SeriesMarkers.SQUARE); + + BitmapEncoder.saveBitmap(chart, outPath, BitmapEncoder.BitmapFormat.PNG); + } } 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..a831605 --- /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..865fafc 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: 4 -------- Times for 100 vertices and 500 connections: Serial: 0 -Parallel: 0 +Parallel: 2 -------- Times for 1000 vertices and 5000 connections: Serial: 1 -Parallel: 0 +Parallel: 4 -------- Times for 10000 vertices and 50000 connections: -Serial: 3 -Parallel: 0 +Serial: 5 +Parallel: 13 -------- Times for 10000 vertices and 100000 connections: Serial: 2 -Parallel: 0 +Parallel: 22 -------- Times for 50000 vertices and 1000000 connections: -Serial: 30 -Parallel: 0 +Serial: 17 +Parallel: 17 -------- Times for 100000 vertices and 1000000 connections: -Serial: 18 -Parallel: 0 +Serial: 34 +Parallel: 27 -------- Times for 1000000 vertices and 10000000 connections: -Serial: 307 -Parallel: 0 +Serial: 416 +Parallel: 947 +-------- +Times for 2000000 vertices and 10000000 connections: +Serial: 2196 +Parallel: 1787 --------