diff --git a/hw2/Ex1/.idea/workspace.xml b/hw2/Ex1/.idea/workspace.xml index 8e0ee87..68beecc 100644 --- a/hw2/Ex1/.idea/workspace.xml +++ b/hw2/Ex1/.idea/workspace.xml @@ -2,9 +2,13 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1573415904376 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No facets are configured + + + + + + + + + + + + + + + + + + + + + + Ex2 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hw2/Ex2/src/ConcurrentVoteCounter.java b/hw2/Ex2/src/ConcurrentVoteCounter.java new file mode 100644 index 0000000..280fa9a --- /dev/null +++ b/hw2/Ex2/src/ConcurrentVoteCounter.java @@ -0,0 +1,20 @@ +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; + +public class ConcurrentVoteCounter implements VoteCounter { + private final ConcurrentMap electionResults = new ConcurrentHashMap<>(); + + public void addVote(Integer id) { + // Alternative implementation (using plain Integers, slower due to synchronization costs): + // electionResults.compute(id, (k, v) -> v != null ? v + 1 : 1); + + // Faster version due to architecture-level optimization + electionResults.putIfAbsent(id, new AtomicInteger(0)); + electionResults.get(id).addAndGet(1); + } + + public Integer getVoteCount(Integer id) { + return electionResults.get(id).intValue(); + } +} \ No newline at end of file diff --git a/hw2/Ex2/src/Tester.java b/hw2/Ex2/src/Tester.java new file mode 100644 index 0000000..46b7e41 --- /dev/null +++ b/hw2/Ex2/src/Tester.java @@ -0,0 +1,93 @@ +import java.util.concurrent.CountDownLatch; + +public class Tester { + + private static final int NUM_THREADS = 1024; + private static final int NUM_ITERATIONS = 64; + private static final int OCCUR = 32; + private final VoteCounter counter; + private final CountDownLatch threadsReady, + measurementStarted, threadsCompleted; + + private class TesterThread extends Thread { + + private final int startValue; + + TesterThread(int startValue) { + this.startValue = startValue; + } + + public void run() { + int iter = NUM_ITERATIONS * OCCUR; + int nextValue = startValue; + + threadsReady.countDown(); + + try { + measurementStarted.await(); + } + catch (InterruptedException e) { + e.printStackTrace(); + System.exit(1); + } + + for (int i = 0; i < iter; i++) { + counter.addVote(nextValue); + nextValue = (nextValue + 1) % NUM_ITERATIONS; + } + + threadsCompleted.countDown(); + } + } + + public Tester(VoteCounter counter) { + this.counter = counter; + this.threadsReady = new CountDownLatch(NUM_THREADS); + this.measurementStarted = new CountDownLatch(1); + this.threadsCompleted = new CountDownLatch(NUM_THREADS); + } + + private long runMeasurement() throws InterruptedException { + for (int i = 0; i < NUM_THREADS; ++i) { + Thread t = new TesterThread( NUM_ITERATIONS / 2); + t.start(); + } + + threadsReady.await(); + long timeBegin = System.nanoTime(); + + measurementStarted.countDown(); + + threadsCompleted.await(); + long timeEnd = System.nanoTime(); + + return timeEnd - timeBegin; + } + + private void check() { + int expectedOccur = OCCUR * NUM_THREADS; + for (int i = 0; i < NUM_ITERATIONS; i++) { + int foundOccur; + if ((foundOccur = counter.getVoteCount(i)) != expectedOccur) { + System.err.printf(" - ERROR: Expected: %d ; Found: %d\n", + expectedOccur, foundOccur); + } + } + } + + private static void testImplementation(ConcurrentVoteCounter implementation, + String name) throws Exception { + + System.out.println("Results for implementation: " + name); + Tester tester = new Tester(implementation); + long elapsedTime = tester.runMeasurement(); + tester.check(); + System.out.printf(" - Test completed, elapsed time: %d ms\n", + (elapsedTime / 1000000)); + } + + public static void main(String[] args) throws Exception { + //testImplementation(new BinTreeSimple(), "Simple version"); + testImplementation(new ConcurrentVoteCounter(), "Concurrent version (correct)"); + } +} diff --git a/hw2/Ex2/src/VoteCounter.java b/hw2/Ex2/src/VoteCounter.java new file mode 100644 index 0000000..2d4382f --- /dev/null +++ b/hw2/Ex2/src/VoteCounter.java @@ -0,0 +1,4 @@ +public interface VoteCounter { + void addVote(Integer id); + Integer getVoteCount(Integer id); +} diff --git a/hw2/submission.tex b/hw2/submission.tex index 5511096..16f1f15 100644 --- a/hw2/submission.tex +++ b/hw2/submission.tex @@ -18,6 +18,18 @@ I have implemented the class \texttt{BinTreeFullSync} as a thread-safe alternative of \texttt{BinTreeSimple} where the only code modification between the two were the addition of \texttt{synchronized} keywords on already existing threads. Since I was not sure how to interpret this specific question, I have implemented \texttt{BinTreeFullSyncEdited}, a thread-safe refactoring of \texttt{BinTreeSimple} where the only synchronization mechanism is method-level \texttt{synchronized} blocks, which are used on new methods created in the subclass \texttt{Node}. \subsection{Question 5} -\texttt{BinTreeFullSync} has the worst performance of all 3 implementations since it allows only sequential access to the tree structure. \texttt{BinTreeFullSyncEdited} is slightly faster, but \texttt{BinTreeCAS} is the fastest since the first one is a \texttt{synchronized} block imitation of the second one, which is instead based on \texttt{Atomic*} objects and can use the extra speed provided by architecture-based optimization. +\texttt{BinTreeFullSync} has the worst performance of all 3 implementations since it allows only sequential access to the tree structure. \texttt{BinTreeFullSyncEdited} is slightly faster, but \texttt{BinTreeCAS} is the fastest since the first one is a \texttt{synchronized} block imitation of the second one, which is instead based on \texttt{Atomic*} objects and can use the extra speed provided by architecture based optimization. +\section{Exercise 2} +\subsection{Question 1} +\texttt{SimpleVoteCounter} behaves incorrectly since its implementation is not thread-safe: the \texttt{Map} implementation is not thread-safe, and the \texttt{addVote(Integer)} method is also not thread safe since its operation of access, conditional computation and storage is not atomic nor synchronized. + +\subsection{Question 2} +\texttt{SyncMapVoteCounter} is still not thread safe since \texttt{addVote(Integer)} is still not synchronized. This means that two threads may assess that a candidate has no vote at almost the same time and count the first vote twice. + +\subsection{Question 3} +\texttt{LockVoteCounter} is thread safe since only a thread at a time can either add a vote or get the votes for a candidate. However, this type of synchronization is inefficient since concurrent accesses to the number of votes of a candidate are safe if no other thread is casting a vote. + +\subsection{Question 4} +As with \texttt{SyncMapVoteCounter}, \texttt{ConcurrentVoteCounter} is not thread safe since the operation of casting a vote is not atomic nor synchronized. \end{document}