CILK
'Cilk' is a general-purpose programming language designed for multithreaded parallel programming.
The major principle behind the design of the Cilk language is that the programmer should be responsible for ''exposing'' the parallelism, identifying elements that can safely be executed in parallel; it should then be left to the run-time environment, particularly the scheduler, to decide during execution how to actually divide the work between processors. It is because these responsibilities are separated that a Cilk program can run without rewriting on any number of processors, including one.
The Cilk language has been developed since 1994 at the MIT Laboratory for Computer Science. It is based on GNU C, with the addition of just a handful of Cilk-specific keywords. When the Cilk keywords are removed from Cilk source code, the result is a valid C program, called the 'serial elision' (or ''C elision'') of the full Cilk program. Cilk is a clean extension of C and the serial elision of any Cilk program is always a valid serial implementation in C of the semantics of the parallel Cilk program. Despite several similarities, Cilk is not directly related to AT&T Bell Labs' Concurrent C.
The first Cilk keyword is in fact 'cilk', which identifies a function which is written in Cilk. Since Cilk procedures can call C procedures directly, but C procedures cannot directly call or spawn Cilk procedures, this keyword is needed to distinguish Cilk code from C code.
The remaining keywords are:
★ 'spawn'
★ 'sync'
★ 'inlet'
★ 'abort'
They are described in further detail below.
Two keywords are all that are needed to start using the parallel features of Cilk:
'spawn' -- this keyword indicates that the procedure call it modifies can safely operate in parallel with other executing code. Note that the scheduler is not ''obligated'' to run this procedure in parallel; the keyword merely alerts the scheduler that it can do so.
'sync' -- this keyword indicates that execution of the current procedure cannot proceed until all previously spawned procedures have completed and returned their results to the parent frame. This is an example of a barrier method.
Below is a recursive implementation of the Fibonacci function in Cilk, with parallel recursive calls, which demonstrates the 'cilk', 'spawn', and 'sync' keywords. (Cilk program code is not numbered; the numbers have been added only to make the discussion easier to follow.)
If this code was executed by a ''single'' processor to determine the value of fib(2), that processor would create a frame for ''fib(2)'', and execute lines 01 through 05. On line 06, it would create spaces in the frame to hold the values of ''x'' and ''y''. On line 08, the processor would have to suspend the current frame, create a new frame to execute the procedure ''fib(1)'', execute the code of that frame until reaching a return statement, and then resume the ''fib(2)'' frame with the value of fib(1) placed into ''fib(2)'''s ''x'' variable. On the next line, it would need to suspend again to execute ''fib(0)'' and place the result in ''fib(2)'''s ''y'' variable.
When the code is executed on a ''multiprocessor'' machine, however, execution proceeds differently. One processor starts the execution of ''fib(2)''; when it reaches line 08, however, the 'spawn' keyword modifying the call to ''fib(n-1)'' tells the processor that it can safely give the job to a second processor: this second processor can create a frame for ''fib(1)'', execute its code, and store its result in ''fib(2)'''s frame when it finishes; the first processor continues executing the code of ''fib(2)'' at the same time. A processor is not obligated to assign a spawned procedure elsewhere; if the machine only has two processors and the second is still busy on ''fib(1)'' when the processor executing ''fib(2)'' gets to the procedure call, the first processor will suspend ''fib(2)'' and execute ''fib(0)'' itself, as it would if it were the only processor. Of course, if another processor ''is'' available, then it will be called into service, and all three processors would be executing separate frames simultaneously.
(The preceding description is not entirely accurate. Even though the common terminology for discussing Cilk refers to processors making the decision to spawn off work to other processors, it is actually the scheduler which assigns procedures to processors for execution, using a policy called ''work-stealing'', described later.)
If the processor executing ''fib(2)'' were to execute line 13 before both of the other processors had completed their frames, it would generate an incorrect result or an error; ''fib(2)'' would be trying to add the values stored in ''x'' and ''y'', but one or both of those values would be missing. This is the purpose of the 'sync' keyword, which we see in line 09: it tells the processor executing a frame that it must suspend its own execution, until all the procedure calls it has spawned off have returned. When ''fib(2)'' is allowed to proceed past the 'sync' statement in line 11, it can only be because ''fib(1)'' and ''fib(0)'' have completed and placed their results in ''x'' and ''y'', making it safe to perform calculations on those results.
The two remaining Cilk keywords are slightly more advanced, and concern the use of ''inlets''. Ordinarily, when a Cilk procedure is spawned, it can only return its results to the parent procedure by putting those results in a variable in the parent's frame, as we assigned the results of our spawned procedure calls in the example to
The alternative is to use an inlet. An inlet is a function internal to a Cilk procedure which handles the results of a spawned procedure call as they return. One major reason to use inlets is that all the inlets of a procedure are guaranteed to operate atomically with regards to each other and to the parent procedure, thus avoiding the bugs that could occur if the multiple returning procedures tried to update the same variables in the parent frame at the same time.
'inlet' -- This keyword identifies a function defined within the procedure as an inlet.
'abort' -- This keyword can only be used inside an inlet; it tells the scheduler that any other procedures that have been spawned off by the parent procedure can safely be aborted.
The Cilk scheduler uses a policy called "work-stealing" to divide procedure execution efficiently among multiple processors. Again, it is easiest to understand if we look first at how Cilk code is executed on a single-processor machine.
The processor maintains a stack on which it places each frame that it has to suspend in order to handle a procedure call. If it is executing ''fib(2)'', and encounters a recursive call to ''fib(1)'', it will save ''fib(2)'''s state, including its variables and where the code suspended execution, and put that state on the stack. It will not take a suspended state off the stack and resume execution until the procedure call that caused the suspension, and any procedures called in turn by that procedure, have all been fully executed.
With multiple processors, things of course change. Each processor still has a stack for storing frames whose execution has been suspended; however, these stacks are more like deques, in that suspended states can be removed from either end. A processor can still only remove states from its ''own'' stack from the same end that it puts them on; however, any processor which is not currently working (having finished its own work, or not yet having been assigned any) will pick another processor at random, through the scheduler, and try to "steal" work from the opposite end of their stack -- suspended states, which the stealing processor can then begin to execute. The states which get stolen are the states that the processor stolen from would get around to executing last.
★ Cilk Project website at MIT
| Contents |
| Design |
| Basic parallelism with Cilk |
| Sample code |
| Advanced parallelism with Cilk: Inlets |
| Work-stealing |
| References |
| External links |
Design
The major principle behind the design of the Cilk language is that the programmer should be responsible for ''exposing'' the parallelism, identifying elements that can safely be executed in parallel; it should then be left to the run-time environment, particularly the scheduler, to decide during execution how to actually divide the work between processors. It is because these responsibilities are separated that a Cilk program can run without rewriting on any number of processors, including one.
The Cilk language has been developed since 1994 at the MIT Laboratory for Computer Science. It is based on GNU C, with the addition of just a handful of Cilk-specific keywords. When the Cilk keywords are removed from Cilk source code, the result is a valid C program, called the 'serial elision' (or ''C elision'') of the full Cilk program. Cilk is a clean extension of C and the serial elision of any Cilk program is always a valid serial implementation in C of the semantics of the parallel Cilk program. Despite several similarities, Cilk is not directly related to AT&T Bell Labs' Concurrent C.
The first Cilk keyword is in fact 'cilk', which identifies a function which is written in Cilk. Since Cilk procedures can call C procedures directly, but C procedures cannot directly call or spawn Cilk procedures, this keyword is needed to distinguish Cilk code from C code.
The remaining keywords are:
★ 'spawn'
★ 'sync'
★ 'inlet'
★ 'abort'
They are described in further detail below.
Basic parallelism with Cilk
Two keywords are all that are needed to start using the parallel features of Cilk:
'spawn' -- this keyword indicates that the procedure call it modifies can safely operate in parallel with other executing code. Note that the scheduler is not ''obligated'' to run this procedure in parallel; the keyword merely alerts the scheduler that it can do so.
'sync' -- this keyword indicates that execution of the current procedure cannot proceed until all previously spawned procedures have completed and returned their results to the parent frame. This is an example of a barrier method.
Sample code
Below is a recursive implementation of the Fibonacci function in Cilk, with parallel recursive calls, which demonstrates the 'cilk', 'spawn', and 'sync' keywords. (Cilk program code is not numbered; the numbers have been added only to make the discussion easier to follow.)
01 cilk int fib (int n)
02 {
03 if (n < 2) return n;
04 else
05 {
06 int x, y;
07
08 x = spawn fib (n-1);
09 y = spawn fib (n-2);
10
11 sync;
12
13 return (x+y);
14 }
15 }
If this code was executed by a ''single'' processor to determine the value of fib(2), that processor would create a frame for ''fib(2)'', and execute lines 01 through 05. On line 06, it would create spaces in the frame to hold the values of ''x'' and ''y''. On line 08, the processor would have to suspend the current frame, create a new frame to execute the procedure ''fib(1)'', execute the code of that frame until reaching a return statement, and then resume the ''fib(2)'' frame with the value of fib(1) placed into ''fib(2)''
When the code is executed on a ''multiprocessor'' machine, however, execution proceeds differently. One processor starts the execution of ''fib(2)''; when it reaches line 08, however, the 'spawn' keyword modifying the call to ''fib(n-1)'' tells the processor that it can safely give the job to a second processor: this second processor can create a frame for ''fib(1)'', execute its code, and store its result in ''fib(2)''
(The preceding description is not entirely accurate. Even though the common terminology for discussing Cilk refers to processors making the decision to spawn off work to other processors, it is actually the scheduler which assigns procedures to processors for execution, using a policy called ''work-stealing'', described later.)
If the processor executing ''fib(2)'' were to execute line 13 before both of the other processors had completed their frames, it would generate an incorrect result or an error; ''fib(2)'' would be trying to add the values stored in ''x'' and ''y'', but one or both of those values would be missing. This is the purpose of the 'sync' keyword, which we see in line 09: it tells the processor executing a frame that it must suspend its own execution, until all the procedure calls it has spawned off have returned. When ''fib(2)'' is allowed to proceed past the 'sync' statement in line 11, it can only be because ''fib(1)'' and ''fib(0)'' have completed and placed their results in ''x'' and ''y'', making it safe to perform calculations on those results.
Advanced parallelism with Cilk: Inlets
The two remaining Cilk keywords are slightly more advanced, and concern the use of ''inlets''. Ordinarily, when a Cilk procedure is spawned, it can only return its results to the parent procedure by putting those results in a variable in the parent's frame, as we assigned the results of our spawned procedure calls in the example to
x and y.The alternative is to use an inlet. An inlet is a function internal to a Cilk procedure which handles the results of a spawned procedure call as they return. One major reason to use inlets is that all the inlets of a procedure are guaranteed to operate atomically with regards to each other and to the parent procedure, thus avoiding the bugs that could occur if the multiple returning procedures tried to update the same variables in the parent frame at the same time.
'inlet' -- This keyword identifies a function defined within the procedure as an inlet.
'abort' -- This keyword can only be used inside an inlet; it tells the scheduler that any other procedures that have been spawned off by the parent procedure can safely be aborted.
Work-stealing
The Cilk scheduler uses a policy called "work-stealing" to divide procedure execution efficiently among multiple processors. Again, it is easiest to understand if we look first at how Cilk code is executed on a single-processor machine.
The processor maintains a stack on which it places each frame that it has to suspend in order to handle a procedure call. If it is executing ''fib(2)'', and encounters a recursive call to ''fib(1)'', it will save ''fib(2)''
With multiple processors, things of course change. Each processor still has a stack for storing frames whose execution has been suspended; however, these stacks are more like deques, in that suspended states can be removed from either end. A processor can still only remove states from its ''own'' stack from the same end that it puts them on; however, any processor which is not currently working (having finished its own work, or not yet having been assigned any) will pick another processor at random, through the scheduler, and try to "steal" work from the opposite end of their stack -- suspended states, which the stealing processor can then begin to execute. The states which get stolen are the states that the processor stolen from would get around to executing last.
References
External links
★ Cilk Project website at MIT
This article provided by Wikipedia. To edit the contents of this article, click here for original source.
psst.. try this: add to faves

العربية
中国
Français
Deutsch
Ελληνική
हिन्दी
Italiano
日本語
Português
Русский
Español



