This page documents the methods available to connect Swift/T to leaf tasks that may be C, C++, or Fortran functions (native code).
Links:
To report problems with these examples, post to the ExM user list.
1. Overview
Two methods are available for connecting a leaf function: simple mode and advanced mode. Both call to Tcl. It is easy to call from Tcl to native code, as presented in this guide. We tap into SWIG which allows us to use a standard language-language tool to help automate this process. (SWIG is a nice skill to have in general, so the techniques here are usable outside Swift as well!)
The modes have separate syntax:
Simple:
(int o) f(int i) "my_pkg" "version"
[ "set <<o>> [ f <<i>> ]" ];
Thus, in the simple mode the user specifies the exact Tcl line to
execute. Data dependencies are handled by Swift. Thus, that
Tcl line runs after i
has a value and o
is a writable variable.
More complex Tcl programming can be done on that line. However,
it is probably best to pack any additional logic into the Tcl
function f
.
Advanced:
(int o) f(int i) "my_pkg" "version" "f";
In the advanced mode, f
is issued before i
has a value. The
user must use the Turbine API to set up execution dependent on i
.
This guide will demonstrate how to do that.
Additionally, you may call Swift/T from native code.
2. Examples
Complete code samples for each example are found in this tar file:
-
examples.tar.gz Timestamp: 04/21/2015 10:49AM (12 KB)
When you unpack this file, you will have a directory examples/
.
Change to this directory and source setup.sh
to obtain build
settings.
You can use the clean.sh
scripts to clean up.
Note
|
We often use shell options set -eux . |
Note
|
You may need to modify some scripts to set the location of Tcl or other system tools. |
3. Simple mode
Calling Tcl from Swift in simple mode is easy. However, the user does have to have a basic understanding of Tcl. In typical cases, the user will want to make a Tcl package so that multiple complex Tcl functions can be called, which may link to native code.
3.1. Complete example 1: Pure Tcl function
Consider the Tcl source file f.tcl
:
package provide my_pkg 0.1
namespace eval my_pkg {
proc f { x y } {
return [ expr $x + $y ]
}
}
This simply defines a function f
that adds two numbers together.
Tcl command puts
prints the result (as puts()
in C). Thus,
writing a Tcl function for Swift involves:
-
creating a namespace (which will be used to group functions into a package) and
-
defining Tcl functions to call.
It may be tested with this test (test-f-1.tcl
):
source "f.tcl"
set z [ my_pkg::f 2 3 ]
puts $z
Calling this from the shell looks like this:
$ tclsh test-f-1.tcl
5
Note
|
On your system, the Tcl shell tclsh may be called tclsh8.5 |
Thus, to load this into Swift, we need to be able to:
-
load
f.tcl
and -
invoke it.
To make it easy to load f.tcl
, we create a Tcl
package for it. This is done with a simple Tcl line that groups
together a package name. Conventionally this is put in
make-package.tcl
:
puts [ ::pkg::create -name my_pkg -version 0.1 -source f.tcl ]
This simply includes the name, version, and Tcl source files to put in
the package. Multiple -source
arguments may be used.
The Tcl package is represented in a file that must be called
pkgIndex.tcl
. This directory containing this file is found by Tcl
via the TCLLIBPATH
variable. (This is similar to LD_LIBRARY_PATH
,
Java’s CLASSPATH
, Python’s PYTHONPATH
, etc.).
Note
|
TCLLIBPATH is space-separated (which makes for easier Tcl
processing). |
Note
|
TCLLIBPATH is modified by Turbine. To expose a Tcl package to
Swift/T, set SWIFT_PATH . |
We create pkgIndex.tcl
from the shell:
$ tclsh make-package.tcl > pkgIndex.tcl
Now, we can test our function f
by accessing through the package
(instead of a simple source
command). First the Tcl to load and run
the package (test-f-2.tcl
):
package require my_pkg 0.1
set z [ my_pkg::f 2 3 ]
puts $z
Now, invoke this test from the shell (test-f-2.sh
):
$ export TCLLIBPATH=$PWD
$ tclsh test-f-2.tcl
5
The next step is to call this from Swift. First, define the typed
Swift call to the external Tcl function f
(f.swift
):
(int o) f(int i1, int i2) "my_pkg" "0.0"
[ "set <<o>> [ my_pkg::f <<i1>> <<i2>> ]" ];
Then, build a simple Swift test script to try it (test-f.swift
):
import io;
import f;
int x = 2;
int y = 3;
int z = f(x,y);
printf("sum: %i", z);
Now, make the package available to Turbine and run it:
$ stc -r $PWD test-f.swift test-f.tcl
$ turbine test-f.tcl
sum: 5
Conclusion: Now you can make many concurrent calls to a simple Tcl function using Swift.
3.2. Complete example 2: Simple C function
This section describes how to call C functions concurrently from Swift.
3.2.1. Establish the C library
Consider the following target C library header (g.h
):
int g(int i1, int i2);
implemented with (g.c
):
#include <stdio.h>
#include <unistd.h>
#include "g.h"
int g(int i1, int i2)
{
int sum = i1+i2;
printf("g: %i+%i=%i\n", i1, i2, sum);
printf("sleeping for %i seconds...\n", sum);
sleep(sum);
return sum;
}
This function is normally called as (test-g.c
):
#include "g.h"
main()
{
g(2, 3);
}
It may be compiled and run with (test-g.sh
):
#!/bin/sh -ex
gcc -c g.c
gcc -c test-g.c
gcc -o g.x test-g.o g.o
./g.x
> time ./g.x
g: 2+3=5
sleeping for 5 seconds...
./g.x 0.00s user 0.00s system 0% cpu 5.003 total
3.2.2. Export the C library as a Tcl package
Now, we would like to call many concurrent instances of g()
. First,
we need to make the Tcl package.
First, we run SWIG on the header:
swig -module g g.h
SWIG produces g_wrap.c
. Compile this and create a Tcl package
(test-swig-g.sh
):
#!/bin/sh
rm -fv *.o
set -ex
gcc -c -fPIC -Wall g.c
gcc -c -fPIC -I/home/wozniak/sfw/tcl-8.6.0/include g_wrap.c
gcc -shared -o libg.so g_wrap.o g.o
tclsh make-package.tcl > pkgIndex.tcl
This produces libg.so
and the Tcl package file pkgIndex.tcl
.
We test this from Tcl (test-g.tcl
):
package require g 0.0
g 2 3
This is run from the shell as (test-g-tcl.sh
):
> export TCLLIBPATH=$PWD
> tclsh test-g.tcl
g: 2+3=5
sleeping for 5 seconds...
3.2.3. Call the Tcl package from Swift (one-shot)
Then, we bring the Tcl package into Swift (test-g-1.swift
):
import io;
@dispatch=WORKER
(int sum) g(int i1, int i2) "g" "0.0"
[ "set <<sum>> [ g <<i1>> <<i2>> ]" ];
int sum = g(2, 3);
printf("Swift: sum: %i", sum);
Compile and run (test-g-1.sh
):
> stc test-g-1.swift test-g-1.tcl
# Point Turbine to the Tcl package for g
> export TURBINE_USER_LIB=$PWD
# Turn off logging
> export TURBINE_LOG=0
> time turbine test-g-1.tcl
turbine test-g-1.tcl 5.60s user 0.44s system 113% cpu 5.300 total
3.2.4. Call the Tcl package from Swift concurrently
Write a foreach loop around the call to g
(test-g-n.swift
):
import io;
import stats;
@dispatch=WORKER
(int sum) g(int i1, int i2) "g" "0.0"
[ "set <<sum>> [ g <<i1>> <<i2>> ]" ];
int d[];
foreach i in [0:5] {
d[i] = g(i, 5-i);
}
y = sum_integer(d);
printf("y: %i", y);
Compile and run (test-g-n.swift
):
> stc test-g-n.swift test-g-n.tcl
# Point Turbine to the Tcl package for g
> export TURBINE_USER_LIB=$PWD
# Turn off logging
> export TURBINE_LOG=0
# Single worker mode:
> time turbine test-g-n.tcl
g: 0+5=5
sleeping for 5 seconds...
g: 1+4=5
...
g: 5+0=5
sleeping for 5 seconds...
turbine test-g-n.tcl 30.60s user 2.26s system 108% cpu 30.300 total
# Many worker mode:
> time turbine -n 7 test-g-n.tcl
...
turbine -n 7 test-g-n.tcl 7.30s user 0.56s system 146% cpu 5.375 total
Thus, providing 1 worker for each loop iteration yields perfect speedup. Note that we had to provide 7 MPI processes: one for each of the 6 loop iterations and one ADLB server.
3.3. Complete example 3: C function with binary data
In this case, we pass binary data in a byte array between Swift and C.
The application in this case consumes a binary file containing double precision floats. It manipulates the data in Swift and passes it to a C library. The C library returns the sum of the numbers as an array of double precision numbers of length 1.
3.3.1. Create the data set
First, we create our binary file using the provided
turbine-write-doubles
/turbine-read-doubles
tools
(create-data.sh
):
> turbine-write-doubles input.data 1 2 3 10
# Check that it worked:
> du -b input.data
32 input.data
> od -t f8 input.data
0000000 1 2
0000020 3 10
0000040
> turbine-read-doubles input.data
1.0000
2.0000
3.0000
10.0000
Simple manipulation can be performed in Swift as (test-b-simple.swift
):
import blob;
import io;
file data = input_file("input.data");
blob b = blob_read(data);
float v[] = floats_from_blob(b);
printf("size(v) = %i", size(v));
printf("v[0]=%0.2f", v[0]);
printf("v[last]=%0.2f", v[size(v)-1]);
test-b-simple.sh
:
> stc test-b-simple.swift test-b-simple.tcl
> turbine test-b-simple.tcl
size(v) = 4
v[last]=10.00
v[0]=1.00
3.3.2. Define the C function
Our C function is (b.c
):
#include <stdio.h>
#include <stdlib.h>
#include "b.h"
double* b(double* v, int length) {
int i;
double sum = 0.0;
printf("length: %i\n", length);
for (i = 0; i < length; i++) {
sum += v[i];
}
printf("sum: %f\n", sum);
double* result = malloc(sizeof(double));
result[0] = sum;
return result;
}
It may be tested from C with (test-b.c
):
#include <stdlib.h>
#include "b.h"
main() {
double v[4] = { 1, 2, 3, 10 };
double* sum = b(v, 4);
free(sum);
}
test-b.sh
:
> gcc -c b.c
> gcc -c test-b.c
> gcc -o b.x test-b.o b.o
> ./b.x
length: 4
sum: 16.000000
3.3.3. Create the Tcl package
Build the Tcl package with (swig-b.sh
):
rm *.o
swig -module b b.i
gcc -c -fPIC b.c
gcc -c -fPIC $TCL_INCLUDE_SPEC b_wrap.c
gcc -shared -o libb.so b_wrap.o b.o
tclsh make-package.tcl > pkgIndex.tcl
Since the blob pointer is of type void*
, we need to wrap the C
function in some helper Tcl code to transmit blob data to the typed C
function. This is performed in b.tcl
:
namespace eval b {
# v is formatted as a Turbine blob a list of [ pointer length ]
# The pointer is a simple Tcl integer
# The length is the byte length
proc b_tcl { v } {
# Unpack the list
set ptr [ lindex $v 0 ]
set len [ lindex $v 1 ]
# Get the number of numbers to sum
set count [ expr $len / [ blobutils_sizeof_float ] ]
# Convert the pointer number to a SWIG pointer
set ptr [ blobutils_cast_int_to_dbl_ptr $ptr ]
# Call the C function
set s [ b $ptr $count ]
# Pack result as a Turbine blob and return it
set r [ blobutils_cast_to_int $s ]
return [ list $r 8 ]
}
}
Thus, Swift calls the Tcl wrapper, which calls the C function. Additional information about blobs is found in the Blob Utilities Guide.
Note that we return blob containing a pointer to freshly allocated memory to Swift. The system will release this memory.
3.3.4. Call from Swift
The Swift code to pass the blob to b()
is as follows
(test-b.swift
):
import blob;
import io;
(blob sum) b(blob v) "b" "0.0"
[ "set <<sum>> [ b::b_tcl <<v>> ]" ];
file data = input_file("input.data");
blob v = blob_read(data);
blob s = b(v);
float sum[] = floats_from_blob(s);
printf("sum (swift): %f", sum[0]);
test-b-swift.sh
:
> stc test-b.swift test-b.tcl
> turbine test-b.tcl
length: 4
sum: 16.000000
sum (swift): 16.000000
3.4. Complete example 4: Calling a Fortran function
In this example, we call Fortran from Swift. We do this by wrapping the Fortran function in a C++ wrapper by using FortWrap. Then, we wrap for Swift by using SWIG.
This example will demonstrate multiplying y=A*x
using Swift to call
a user Fortran library, which in turn calls the BLAS function
dgemv
. This demonstrates the generality of our model.
In these scripts, you must have the BLAS archive. Change the appropriate shell variable to point to this file.
3.4.1. Establish the Fortran function
Consider the Fortran function (mvm.f
):
! Matrix-Vector Multiply: y = A*x via BLAS
subroutine MVM(A, x, y, n)
integer, intent(IN) :: n
double precision, intent(IN) :: A(n,n)
double precision, intent(IN) :: x(n)
double precision, intent(OUT) :: y(n)
double precision :: alpha, beta
alpha = 1.0D0
beta = 0.0D0
call dgemv('N', n, n, alpha, A, n, x, 1, beta, y, 1)
end subroutine
A test program to run this is (test-mvm.f
):
program testMVM
parameter (n = 2)
double precision :: A(n,n)
double precision :: x(n)
double precision :: y(n)
double precision :: alpha, beta
open (unit=1, file='A.data', form='unformatted',
$ access='direct', recl=n*n*8)
read (1, rec=1) A
close (1)
open (unit=1, file='x.data', form='unformatted',
$ access='direct', recl=n*8)
read (1, rec=1) x
close (1)
do i = 1,n
y(i) = 0.0D0
end do
call MVM(A, x, y, n)
print *, "y"
print *, y(1)
print *, y(2)
end program
It is built and run with (test-mvm.sh
):
#!/bin/bash -eu
# Note Fortran memory layout:
turbine-write-doubles A.data 1 3 2 4
turbine-write-doubles x.data 5 6
gfortran -c mvm.f
gfortran -c test-mvm.f
gfortran -o test-mvm.x test-mvm.o mvm.o ${BLAS}
./test-mvm.x
FortWrap will scan mvm.f
and produce the C++ files FortFuncs.h
and
FortFuncs.cpp
, which we wrap with SWIG to produce a Tcl function
called FortFuncs_MVM
. We can call this from Swift.
3.4.2. Write the Swift code and Tcl wrapper
Our goal is to rewrite test-mvm.f
in Swift so we can call MVM
concurrently on many processors. Our Swift replacement is
(test-mvm.swift
):
import blob;
import io;
(blob y) mvm_blob(blob A, blob x, int n) "mvm" "0.0"
[ "set <<y>> [ mvm::mvm <<A>> <<x>> <<n>> ]" ];
int n = 2;
blob A_blob = blob_read(input_file("A.data"));
blob x_blob = blob_read(input_file("x.data"));
blob y_blob = mvm_blob(A_blob, x_blob, n);
float y[] = floats_from_blob(y_blob);
foreach v, i in y {
printf("y[%i]=%f", i, v);
}
Our Tcl wrapper converts the Tcl call to mvm
on blob arguments
to the Tcl call to FortFuncs_MVM
on pointers. This is performed
similarly to the C example above (mvm.tcl
).
3.4.3. Build and run
Now, we build everything (build.sh
):
#!/bin/bash -eu
TURBINE=$( which turbine )
TURBINE_HOME=$( dirname $( dirname ${TURBINE} ) )
source ${TURBINE_HOME}/scripts/turbine-config.sh
# Wrap the Fortran in C++
fortwrap.py --array-as-ptr --no-vector --no-fmat mvm.f
# Wrap the C++ in Tcl
swig -c++ -module mvm FortFuncs.h
# Minor fix to the wrapper code
sed -i '11i#include "FortFuncs.h"' FortFuncs_wrap.cxx
# Compile everything
g++ -c -fPIC -I . FortFuncs.cpp
g++ -c -fPIC ${TCL_INCLUDE_SPEC} FortFuncs_wrap.cxx
gfortran -c -fPIC mvm.f
# Build the shared object
g++ -shared -o libmvm.so FortFuncs_wrap.o FortFuncs.o mvm.o ${BLAS} -lgfortran
# Make the Tcl package
${TCLSH} make-package.tcl > pkgIndex.tcl
We run it in Swift with (test-mvm-swift.sh
):
#!/bin/sh
swift-t -r $PWD test-mvm.swift
This produces:
y[0]=17.000000
y[1]=39.000000
3.5. Complete example 5: Calling C with argc/argv
It may be desirable to call an existing C program from Swift
with as little modification as possible. This example considers the
case that the Swift program will construct an array of strings to pass
to the C code. The C program is minimally modified by
renaming its main()
function and calling that from Swift/T.
Consider the user code (main.c
):
#include <stdio.h>
#include "main.h"
int main(int argc, char* argv[]) {
for (int i = 0; i < argc; i++)
printf("arg[%i]: %s\n", i, argv[i]);
return 0;
}
The function essentially acts like /bin/echo
, reporting its
arguments.
Consider the user code (main_leaf.c
):
#include <stdio.h>
#include "main.h"
int main_leaf(int argc, char** argv) {
for (int i = 0; i < argc; i++)
printf("arg[%i]: %s\n", i, argv[i]);
return 0;
}
This is essentially the same program except that its main()
function
has been renamed to swift_main()
. Also, the header swift-main.h
has been created to allow the Swift/T framework to call this code.
The Swift/T distribution comes with functionality to make this easy to
call. The key program is genleaf
.
The equivalent Swift code is as follows (test-main.swift
):
import io;
mainapp;
printf("Swift...");
string A[] = [ "arg1", "arg2", "arg3" ];
rc = main_leaf(A);
printf("exit code: %i", rc);
The Swift version defines the extension function symbol swift_main
,
then calls it with the given array of strings. The "exit code"
(actually just a return value) is available to Swift.
This example may be compiled and run (from C, Tcl, and Swift) by using the provided Makefile.
The above process is semi-automated by the genleaf
script. The
script takes a C program and a header file as input and produces the
required Tcl-C extension and Tcl leaf function. It also produces a
simple source Swift and stc-compiled Tcl code ready to be run via
turbine
. Invoke genleaf
as follows (test-main.sh
):
genleaf -vv main.c main.h test-main.swift
swift-t -r $PWD user-code.swift
Thus, main_leaf.c
is generated by genleaf
, creating an easy-to-use
interface for Swift.
3.6. Complete example 6: Calling Fortran with argc/argv
It may be desirable to call an existing Fortran program from Swift
with as little modification as possible. This example considers the
case that the Swift program will construct an array of strings to pass
to the Fortran code. The Fortran program is minimally modified by
removing its "open code" and exposing a function that accepts an array
of strings instead of using Fortran builtins
command_argument_count()
and get_command_argument()
.
Assume you wish to call the Fortran function func()
(func.f90
):
subroutine func(argc, argv, output)
implicit none
integer, intent(in) :: argc
type (string_array) :: argv
double precision, intent(out) :: output
The argc/argv
is stored in a string_array
, defined in the top of
func.f90
.
This data structure is constructed and filled by the test program
prog-f90.f90
as follows:
argc = command_argument_count()
call string_array_create(argv, argc)
do i = 1, argc
call get_command_argument(i, tmp)
call string_array_set(argv, i, tmp)
end do
To call this function from Swift, run ./build.sh
. (You need to set
the Tcl installation directory in build.sh
.)
The Makefile
proceeds as follows:
-
We first wrap the Fortran with FortWrap to produce C++ wrappers. Note that FortWrap wraps the Fortran type
string_array
with a C++ class (an example of how to use the class directly from C++ is provided inprog-cxx.cxx
). -
We wrap the generated C++ header (
FortWrap.h
) with SWIG. -
We compile everything.
-
A Tcl function (
func.tcl
) is used to retrieve the string array from Swift and create an output buffer for the output double.
After the build, you can run the C++ or Fortran test programs to see how they work:
./prog-f90 a b c
...
./prog-cxx a b c
We call the function from Swift using the simple syntax:
+
(float v) func(string A[]) "f" "0.0"
[
"set <<v>> [ func <<A>> ]"
];
We run with:
+
make
swift-t -r $PWD prog-swift.swift
4. Advanced mode
In this mode, the user provides the Swift/T runtime with dataflow information so that more complex operations may be performed.
4.1. Complete example 7: Leaf task with dataflow
When using the advanced syntax, the final argument is the name of a Tcl function:
(int o) f(int i) "my_pkg" "version" "f";
This function is called before i
is set. Thus, f
must issue a
Turbine rule
statement to schedule real leaf work when i
is set.
Consider the following Tcl code (f.tcl
):
package provide my_pkg 0.0
namespace eval my_pkg {
# Function called by Swift/T
proc f { outputs inputs } {
set x [ lindex $inputs 0 ]
set y [ lindex $inputs 1 ]
set z [ lindex $outputs 0 ]
rule [ list $x $y ] "my_pkg::f_body $z $x $y" \
type $turbine::WORK
}
# Function called by Swift/T rule
proc f_body { z x y } {
set x_value [ retrieve_integer $x ]
set y_value [ retrieve_integer $y ]
set z_value [ f_impl $x_value $y_value ]
store_integer $z $z_value
}
# Real Tcl implementation: does actual work
proc f_impl { x_value y_value } {
return [ expr $x_value + $y_value ]
}
}
In this case, Tcl function f_impl
is the implementation: the
actual code the user desires to call. This may be an existing Tcl
routine or linkage to native code as described above. It consumes two
Tcl integers and returns their sum as a Tcl integer.
Two function, f
and f_body
, are required to connect this to
Swift/T. f
is the interface: it is called by Swift/T when the
call is issued but x
and y
may not yet have values. The Tcl
variables x
and y
are the Turbine Datum (TD) identifiers, not the
values, of Swift variables x
and y
. The outputs are packed in Tcl
list outputs
and the inputs are in Tcl list inputs
.
When x
and y
are stored elsewhere, the Swift/T runtime evaluates the
action item in the rule: "my_pkg::f_body $z $x $y"
. Thus, f_body
receives the TDs for z
, x
, and y
. It retrieves the values of
x
and y
, calls f_impl
to compute the value for z
, and stores
it. Now, any Swift/T statements (rules) dependendent on z
will be
executed, and so on.
This Tcl code is called by the following simple Swift/T test
(test-f.swift
):
import io;
(int o) f(int i1, int i2) "my_pkg" "0.0" "f";
int x = 2;
int y = 3;
int z = f(x,y);
printf("sum: %i", z);
For more information about the APIs used to store/retrieve Swift/T data and issue rules, see Turbine Internals.
The interface/body/implementation pattern is used by much of the
Swift/T library. The source of this library is found in the Turbine
installation directory. The Swift headers are in turbine/export
and
their Tcl implementations are in turbine/lib
. This is a good way to
find out what else may be done.
4.2. Complete example 8: Calling an MPI library
Calling an existing MPI library is similar to calling any other C code. In this example, we will show how to obtain the new communicator from Swift/T. This functionality only works when using Swift/T on an MPI-3 implementation such as MPICH 3.0.
4.2.1. Identify the function
Consider the following MPI library function (f.c
):
double
f(MPI_Comm comm, int k)
{
printf("f()\n");
int task_rank, task_size;
MPI_Comm_rank(comm, &task_rank);
MPI_Comm_size(comm, &task_size);
printf("In f(): rank: %i/%i\n", task_rank, task_size);
MPI_Barrier(comm);
sleep(task_rank);
MPI_Barrier(comm);
MPI_Comm_free(&comm);
if (task_rank == 0)
// Return a real value
return sin(k+task_size);
// Return a placeholder
return 0.0;
}
This function accepts an MPI communicator comm
freshly created for
the task. It also accepts user data k
and must return a double
.
Rank 0 in the task communicator returns the real value- the other
ranks may cooperate in the computation but do not interact with the
data store.
Communication on comm
is isolated from Swift/T implementation
communication. Point-to-point messaging and collectives (such as
MPI_Barrier()
, as shown) are allowed.
We desire to call this from Swift as (test-f.swift
):
import io;
@par @dispatch=WORKER (float z) f(int k) "f" "0.0" "f_tcl";
float z = @par=2 f(3);
printf("z: %0.3f", z);
In the declaration, @par
allows us to call the function as a
parallel function. At call time, we use @par=2
.
We wrap this function with SWIG and a Swift/T interface/body:
namespace eval f {
proc f_tcl { outputs inputs args } {
set z [ lindex $outputs 0 ]
set k [ lindex $inputs 0 ]
rule $k "f::f_tcl_body $z $k" {*}$args type $turbine::WORK
}
proc f_tcl_body { z k } {
# Retrieve k
set k_value [ retrieve_integer $k ]
# Look up MPI information
set comm [ turbine::c::task_comm ]
set rank [ adlb::rank $comm ]
# Run the user code
set z_value [ f $comm $k_value ]
# Store result
if { $rank == 0 } {
store_float $z $z_value
}
}
}
This differs from the single-process task (example 5) in that it
passes the additional parallelism information in variable args
into
the rule
statement so that Swift/T will allocate 2 workers for the
task.
By convention, only rank 0 contacts the data store to store result
z
. If multiple processes write to z
, a run-time error will
result.
At run time, ensure Turbine has enough workers to run the user task:
> turbine -n 4 test-f.tcl
z: -0.959
5. Calling Swift/T from native code
5.1. Complete example 9: Calling Swift/T from C
Swift/T itself may be called from native code as a library.
5.1.1. Compilation
The Turbine installation directory contains the turbine.h
header and
the libtclturbine.so
shared object. See build.sh
for the example
compiler commands.
5.1.2. Call
The call to Swift/T requires the MPI communicator in which to run, the
compiled Swift/T program to run, a C-formatted argc
/argv
, and an
output pointer.
The C call to Swift/T is written as (controller.c
):
turbine_code rc = turbine_run(comm, "test-f.tcl", argc, argv, output);
where argc
/argv
are C-style arguments, and output
is the buffer
to hold the output.
The Swift script is a normal Swift script except for the call to
turbine_run_output_blob()
, which returns data to the controller
(test-f.swift
):
import blob;
import io;
import sys;
printf("args: %s", args());
string s = "MY_OUTPUT";
blob b = blob_from_string(s);
turbine_run_output_blob(b);
The controller MPI program is run as: (run.sh
):
#!/bin/sh -e
./build.sh
mpiexec -n 4 ./controller.x
producing:
args: howdy ok bye
controller: output: MY_OUTPUT
Note that only rank 0 in the Swift/T communicator has access to the
output
data.