|
Homework 6 // Due at Lecture Wednesday, October 19, 2005
Please direct questions to Jayaram Bobba (bobba@cs...)
who helped create this assignment.
You should do this assignment alone. No late assignments.
Purpose
Concurrent Data Structures form the framework on top of which parallel software
can be developed. In this assignment, we will implement a concurrent data structure
with varying levels of concurrency. The idea is to gain an appreciation of the
implementation trade-offs
between synchronization efficiency and complexity and see if transactional memory can alter this.
Programming environment
You will use the GEMS/LogTM simulator for this assignment. Parallel programs will be written using pthreads.
Programming Task: Concurrent Double Ended Queues
A Double Ended Queue(deque) is a linear data structure that supports enqueue/dequeue operations on both
ends. A concurrent deque should allow multiple entities to perform enqueue/dequeue operations simultaneously
without comprising the consistency of the data structure. For example, one thread might enqueue some
data on one end while another thread simultaneously dequeues data from the other end. Concurrent deque
structures can be used to implement higher-level abstractions such as work queues in a parallel program environment and hence
a high performance implementation is desirable.
Data Structure
The deque supports four operations - enqueue-left, dequeue-left, enqueue-right and dequeue-right. These operations
have the 'obvious' semantics. In addition, each deque operation also requires the queue to perform some fixed amount
of useful work(like sleeping or read/write junk). Varying this parameter will affect the contention seen on the deque.
Implement the deque with a statically allocated array. For simplicity, the array does not need to wrap-around.
Do not worry about resizing the array when the deque hits one of the array boundaries. Consider it as deque full
condition.
Work Abstraction
The various threads in the program need to simulate some real system that uses the deque. For simplicity, we
assume that the threads do some random amount of useful work in between each deque access. They terminate after
performing a fixed number of deque operations. Each access is randomly chosen to be one of the four possible
operations.
Problem 1: Implement a coarse-grained deque
First, implement a deque that supports accesses from only a single thread.
Next, modify
the implementation to support concurrent accesses from multiple threads by adding a single lock around
the whole data structure. You could use any locking technique for the lock. Test your implementation on cabernet.
Problem 2: Implement an obstruction free deque
An obvious drawback of the above implementation is that it serializes all the threads for the entire duration
of the queue operation. You will implement a non-blocking implementation of deque to overcome this limitation.
The implementation should be based on the algorithm presented in "Obstruction-free synchronization: ..." by Herlihy et al. [ICDCS 2003]. Note that the algorithm uses a hardware 'compare and swap(CAS)' primitive. SPARC-v9 architecture provides an atomic CAS instruction. Please look at the Example to learn the usage of the CAS instruction.
Again test your implementation on cabernet.
Problem 3: Implement a transactional deque
Implement a transactional version of the deque. Use the guidelines from the previous homework for writing, compiling and simulating
the transactional code. Test your implementation on GEMS/LogTM.
Problem 4: Performance Analysis
The first three problems should have given you an idea of the complexity needed to implement different flavors of concurrency.
We will now study the performance characteristics of these implementations.
Remember that, given the number of threads and the total number of deque operations to be performed, there
are two parameters that you could adjust to vary the behavior of the three systems. First, you could alter
the amount of useful work done by the deque as part of the deque operation. You could also alter the
amount of useful work done by the threads between successive deque operations. These two knobs allow
you to control and study your systems under varying levels of contention.
For this assignment, use '8' threads on a simulated 8-processor machine. These 8 threads perform a total of
32K operations. As an example of useful work, pre-allocate an integer array of size N in your stack and then
proceed to accumulate the sum of all the elements into another local data word. N decides the amount of
work done.
Given these specifications, you will study the performance under 3 cases -
- High contention among threads. Each deque operation does some useful work (N = 16). A thread also
performs no useful work(N = 0) between successive deque operations.
- Moderate contention among threads. Each deque operation does no useful work (N = 0).
A thread performs some useful work (N = rand[1,64]) between successive deque operations.
- Low contention among threads. Each deque operation does no useful(N = 0) work.
A thread performs a lot of useful work(N = rand[1,512]) between successive deque operations.
You will now design experiments and present data to answer the following questions.
- In what cases do all the three implementations perform similarly and why?
- When is an obstruction-free implementation better than a coarse-grained implementation?
- Do the transactional and obstruction-free implementations perform similarly across the board or
are there cases where one performs better than the other?
Support all your answers with relevant explanations and experimental data.
Tricks/ Tips
- Simulation is SLOW . Typical slow-downs with GEMS/LogTM are in the range of 10,000 to 20,000 times i.e a program that
takes 1 sec when run natively on hardware may take 10,000 to 20,000 secs when simulated with Ruby. So run your lock-based programs
first on cabernet. The execution time of the phase of the program that you intend to simulate with GEMS/LogTM should be less than 0.5 s.
- Do NOT make i/o calls within a transaction.
- Be careful when using compiler optimization flags (-O3 is known to break transactional memory programs).
What to Hand In
- A printout of any one deque operation in your obstruction free implementation.
- A prinout of any one transaction in your LogTM implementation.
- Performance analysis along with the explanations and data.
Please turn this homework in on paper at the beginning of lecture.
REQUIREMENTS
|