[e-lang] Computing Pi in parallel with several VATs. How to write
it well ?
Kevin Reid
kpreid at attglobal.net
Sat Jul 23 13:28:49 EDT 2005
On Jul 23, 2005, at 10:29, Kevin Reid wrote:
> On Jul 21, 2005, at 12:25, Pascal Grange wrote:
>> I am new to E and I am seeking advice about good programming in E.
>> Here is
>> the issue :
> ...
>> So that was for the properties of the algorithm. My way of
>> implementing it
>> with E is the following : I have several computers (PiComputer), on
>> different
>> remote VATs. I submit a job (an interval) to compute asynchronously
>> to each
>> computer. When a computer finished its computation, I add its result
>> to the
>> current value of Pi being computed and I send the computer a new job.
>> When
>> there are no more job to do and no computation is being performed, the
>> algorithm stops and displays the value of Pi computed.
> ...
>> This code is too spagheti-like for me but I was not able to do it
>> best. This
>> spaghetiness is not a fact, just a feeling, for instance the printing
>> of the
>> result is in the middle of the code, not the end...and so on. I know
>> that
>> it's asynchronous but I would like to know if a E-programmer would
>> code
>> that in a drastically different E-way.
>>
>
> This is an interesting problem, and I'm trying to find a nice
> solution; so far, I have an uglier one.
>
> I'm still trying,
Here's another attempt, which I think is better than my previous one.
This one has the same approach as your original code: setting up a
'local' process for each computer.
Neither of these handle failure as well as could be done: if one
'computer' fails permanently (the 'computer' reference becomes broken,
as opposed to the result of <-compute), it should be discarded and the
interval given to another computer; overall failure should only occur
if all computers fail, or consistently fail when asked to compute a
particular interval.
pragma.enable("easy-return")
pragma.disable("explicit-result-guard")
pragma.enable("easy-when")
def makeComputer() {
def computer {
to compute(piece) {
if (entropy.nextInt(5) == 0) {
return Ref.broken(`BOOM ($piece)`)
} else {
return timer.whenPast(timer.now() + entropy.nextInt(2000),
thunk{ "'" <- add(piece) <- add("', ") })
}
}
}
return computer
}
/** Slightly silly for just this problem, but this seems to be a
common pattern, so it should be packaged up.
Returns a 0-parameter function. 'reactor' will be eventually
invoked exactly once when the function has been called 'n'
times. */
def makeEventCountdown(var n :(int >= 0), reactor) {
def test() { if (n <=> 0) { reactor <- () }}
def countdown() { n -= 1; test() }
test()
return countdown
}
def distribute(pieces :Set, seed, computers :Set, traceOut) {
def [result, resultResolver] := Ref.promise()
def piecesLeft := pieces.getElements().diverge()
var accumulation := seed
def doneAll() {
traceOut.println("resolving result")
resultResolver.resolveRace(accumulation)
}
def doneOne := makeEventCountdown(piecesLeft.size(), doneAll)
for i => computer in computers {
def sendOne() {
if (piecesLeft.size() > 0 && !Ref.isBroken(accumulation)) {
def inPiece := piecesLeft.pop()
traceOut.println(`($i) sending piece $inPiece`)
when (computer <- compute(inPiece)) \
-> computed(resultPiece) {
traceOut.println(`($i) $inPiece => $resultPiece`)
accumulation := accumulation <- add(resultPiece)
} catch p {
traceOut.println(`($i) $inPiece => problem $p`)
accumulation := Ref.broken(p)
doneAll()
} finally {
doneOne()
sendOne()
}
} else {
traceOut.println(`($i) quitting`)
}
}
sendOne()
}
return result
}
def intervals := ["A", "B", "C", "D", "E", "F", "G"].asSet()
def computers := [makeComputer(), makeComputer(),
makeComputer()].asSet()
when (distribute(intervals, "", computers, stderr)) \
-> computed(result) {
println(`The answer is $result.`)
} catch p {
println(`Oops, $p.`)
}
--
Kevin Reid <http://homepage.mac.com/kpreid/>
More information about the e-lang
mailing list