CSE.240b Programming Assignment 1

    
Due: May 9 (you may request an extension but it is best to finish it by this date)

The goal of this assignment is to get comfortable with the DataStar machines, and some general hacking.
Please browse the SDSC DataStar Userguide so that you understand what
information is available.

Errata:

May 02, 2006 7:00 pm

Typo in Dekker's fixed.

May 02, 2006 7:00 pm

According to Saturnino, what is given as Dekker's algorithm is apparently Peterson's algorithm:
{Peterson, G. L. Myths about the mutual exclusion problem. Information Processing Letters 12, 3 (13 June 1981), 115--116.}
Dekker's is more complicated. (Serves me right for trusting those old MIT lecture notes!)


Running on the DataStar machines.

CPU Time

Each student has been allocated 100 SUs of CPU time; it is up to you to
manage this time and make sure you do not run over. 1 SU is equal to
either one-half to one CPU-hours, depending on the priority of the machine
or job queue you use. You can type:

reslist -a csd366

to see how much time you have left. Note that when you submit to the
batch queues, you are actually being billed for all 8 of the processors
on the machine.

Types of nodes

There are several types of datastar nodes. These are enumerated
in SDSC DataStar Userguide: Running Jobs.

dslogin.sdsc.edu

This machine is for editing, compiling, running very short programs,
and submitting jobs to the batch nodes.

batch nodes

These machines are for running compute-intensive programs, and for timing the runtime with accuracy. All official timing runs for the class must be run on these machines. You can submit jobs to the "Normal" or "High" job queues, which dispatches to one of 265 8-processor machines. Once a job has been submitted to the job queue, it will wait in the queue until one of the 8-processor machines is available and the job will be run. Your program has the whole machine to itself. The output will be directed to a file LL_out.XXXXX.
You should receive an email telling you when the job has been launched.

dsdirect.sdsc.edu

These machines are for running, testing and debugging short compute intensive jobs. These
machines are shared machines, which means timing numbers will not be accurate.


Provided Code

For this project, some helper code has been provided for the assignment in:

/rmount/users02/ucsd/mbtaylor/prog1

You can use cp to copy it to your local directory. This code is provided to help you with parts of the assignment.

I recommend you read through the code (including the Makefile, but not the file run-script.template, which is specific to the DataStar system) and make sure you understand all of it. (Use man pages and the web to look things up. Note: some of the unix man pages are missing on the DataStar machines, you may want to try a linux machine if you don't find the function.) Unless you are a big hacker, you will learn advanced features of make, POSIX, etc by reading and understanding the code. Almost everything included demonstrates some useful feature or programming idiom.

This code includes:

Makefile

This file contains some example rules for building your code with xlc, the IBM C compiler, and gcc, the GNU C compiler. xlc probably generates better code, but gcc may be easier and less buggy (especially at high optimization levels) to use. The default rule actually builds the code on both gcc and xlc (the names end in the compiler used to create them; e.g., binary.xlc or binary.gcc) The Makefile also has a rule which allows you to submit programs to the batch queue by typing:

gmake binary_name.submit

e.g.,

gmake binary.xlc.submit


run-script.template

This file contains the configuration settings for submitting jobs to the batch nodes. The Makefile fills in some of its fields with a sed script in order to submit jobs. You can refer to the DataStar LoadLeveler guide to learn how to modify this. (Keep in mind that if you modify this without renaming it, it will affect the behaviour of the .submit rule.)

helper.c, helper.h

This contains helper code for timing sections of your programs. Contains
get_processor_freq() for determining the speed of the processor, and diff_cycles(), for computing the number of cycles between two calls to read_real_time().

objdump

Executable file (from GNU binutils) that allows you to disassemble binaries. You will write a rule in the Makefile that will invoke this to disassemble a binary -- .e.g.,

gmake binary.xlc.dis
gmake binary.gcc.dis

Aside: You can also look at the assembly generated by the C compiler (-S) using a pattern rule in the Makefile. This usually has more information about local-labels than the output of objdump; however in some cases, it doesn't tell you what actually was generated in the end. For instance, xlc -O5 does intra-procedural analysis at link time which is only reflected in the object dump. Here is an example of generating assembly using the makefile pattern rule:

gmake <.c file prefix>.gcc.s
gmake <.c file prefix>.xlc.s

e.g.,

gmake helper.gcc.s

main.c

Some sample code that performs timing, uses inline asm, and creates POSIX threads.


The Assignment

Collaboration Policy: For parts I and II of this assignment, you may not post code on the forum. However, you may describe the problems you are having abstractly in text, or inquire/suggest as to what function, macro or instruction you should use to overcome the problem. You may also supply weblinks for others to use to address an issue. For part III, you can post anything as long it does not give away anything for parts I and II.

For parts I, II, and III, you may also discuss your assignment with classmates one-on-one, but try to tell people where to find answers as opposed to telling them the answer. Also please acknowledge their help on your assignment.

Deliverables: In the following parts of the assignment, certain parts are marked as "Deliverables".
These are the parts to be printed out and submitted. Please keep these concise and do not include excess material. Excessively unclear writeups will not be given full credit.


I. Add a pattern rule to Makefile that invokes objdump as described in the above objdump section.
For example if the user has a binary named foo, then the user should be able to type:

gmake foo.dis

and Makefile will automatically generate a new foo.dis, if the Makefile (and thus the compilation flags)
or the source binary (or the sources it depends on) has changed since the last foo.dis was created.

Deliverable:

The lines that you added to implement this rule.


II. Using the following pseudo-code for Dekker's algorithm:

(note: c1 and c2 start out as 0, and turn as 1.)

thread_1_critical()
{
   c1 = 1;
   turn = 1;
L:
   if c2==1 && turn==1
      then goto L;

   < critical section >

   c1 = 0;
}


thread_2_critical()
{
   c2 = 1;
   turn = 2;
L:
   if c1==1 && turn==2
      then goto L;

   < critical section >

   c2 = 0;
}


IIa. Come up with a critical section that demonstrates that Dekker's algorithm fails to appropriately synchronize data between two threads. Write the program in C, compiling with gcc and c, running with two threads on two processors on the Power4 batch queue machines. You should modify main.c with the new code. Make sure that the two threads are actually running simultaneously for a significant period of time (e.g., many seconds), and not just alternating.

Deliverable:

A copy of the code. Make it as simple, readable, and small as it can be. Also, include a short description of how the critical section exposes the failure of mutual exclusion.



IIb. Add the appropriate inline sync, lwsync or isync instructions to (IIa) to make the implementation of Dekker's correct. You will also need the volatile keyword. Refer to von Praun's Deconstructing Redundant Memory Synchronization (ISCA Workshop on Duplicating, Deconstructing, and Debunking "WDDD", June 2005) paper for the approximate execution time of the instructions.

Use the disassembly rules to verify that the barrier instructions still are being found in the assembly source. You may find that the disassembler uses different names. You can verify that the instructions are correct by using the gmake .dis rule to see the actual bits that make up the instructions, and looking them up in the PowerPC manual. Your implementation must minimize the execution time due to synchronization, without being incorrect. For the purposes of this assignment, you do not need to measure the execution time, rather, just sum the total cost of synchronization instructions in the loop body.

Deliverables:

1. The loops, with synchronization instructions.

2. Include a discussion of why each instruction was the minimum sufficient instruction required.

3. For first two of the following 4 cases, include the part of the .dis (or .xlc.s or .gcc.s) file corresponding to Dekker's algorithm and the critical section, with annotations as to which line of C the disassembled code corresponds to. Give only the code for the parts corresponding to the mutual exclusion and critical section part of a single thread. (If this is more than 150 instructions per function, just pick the most relevant 150 instructions to do for a given piece of code.)

(To figure out how to do this, you may have to experiment with
simpler pieces of code and refer to the PowerPC manuals for instruction mneumonics.)

The 4 cases are:
i) gcc with the flags given in the supplied Makefile.
ii) xlc with the flags given in the supplied Makefile.
iii) gcc, with no optimization.
iv) gcc with the flags given in the supplied Makefile, but with no synchronization instructions or volatile keywords. What happened to the code in this circumstance?

4. Explain what the differences are of the compiler output between the four cases listed about. Which one of the four cases is the easiest to reverse-engineer and why? Which do you think is the most correct and has the best performance?
(You may want to keep this in mind for future assignments.)

5. Does your implementation of Dekker's algorithm work with xlc -O5?
If not, why not?



IIc. Implement mutual exclusion for the same critical section using load-reserve and store-conditional.
Running the same number of iterations, determine the respective performance of the two mutual exclusion implementations (Dekker's versus locks with load-reserve and store-conditional).

Deliverable: Measure the relative performance. Explain the results.

III. Pick a micro-architectural mechanism in the Power4 and write a piece of code that exposes its existence, through some form of timing anamoly. Make sure to post on the forum to reserve your anomaly. At most one other person is allowed to reserve an anomaly that has already been previously reserved. This mirrors the scientific process where people verify other people's experiments. (I will generally be much more impressed if you propose a new anomaly.) In some cases, it may be easiest to expose an anomaly with assembly language. In other cases, it may be easier to do this with a C program. If you use C, you will have to figure out the correct flags for the compiler (and which compiler.) You probably will want to disassemble the C to verify that what you expected is being generated.

Deliverable: A complete description of the experiment, including a summary of the mechanism you are trying to detect, the reasoning behind the experiment, the code, the data collected, and the explanation of the data.
This is the most significant part of the assignment.



Since this assignment is newly created, please do not hesitate to post for clarifications on the assignment, subject to the restriction that you should not give away any answers. Students are free to respond to these clarification requests.


Resources

The SDSC DataStar Userguide contains a wealth of information about using and programming DataStar.

The GNU Make manual may be useful to understand the Makefile used in the provided code.

Power 4 Manuals: Book 1, Book 2, Book 3.