Sunday, February 1, 2009

Ch4, Ex16

Value of a variable is "needed" when some thread has to wait for it to determine or the variable is determined. So clearly in second case thread won't wait and X will be computed.

Required Example:
X = {ALazyFunc}
X = 3

Ch4, Ex15

I'm putting a regular and a complete lazy version of iterative factorial.
declare Fact LazyFact
%Regular factorial
fun {Fact N}
fun {Iter X A}
if X>0 then
{Iter X-1 X*A}
else A end
end
in
{Iter N 1}
end
%Lazy Factorial
local
fun lazy {LazyMul X Y}
X*Y
end
fun lazy {LazySubstract X Y}
X-Y
end
in
fun lazy {LazyFact N}
fun lazy {Iter X A}
if X>0 then
{Iter {LazySubstract X 1} {LazyMul X A}}
else A end
end
in
{Iter N 1}
end
end

%Use following code to find elasped time
for I in 1..5 do
N = 30000
R1 T1 T2 in
T1 = {Property.get 'time.total'}
% R1 = {Fact N} %COMMENT FOR LAZY FACT
% doing +/-1 to trigger the lazy fact, assuming it
% takes negligible time
R1 = {LazyFact N}+1-1 %COMMENT FOR REGULAR FACT
T2 = {Property.get 'time.total'}
{Show fact(n:N timeInMs:(T2-T1))}
end


Results with N=20000, 25000, 30000:

Regular Fact:
fact(n:20000 timeInMs:2038)
fact(n:20000 timeInMs:763)
fact(n:20000 timeInMs:1914)
fact(n:20000 timeInMs:919)
fact(n:20000 timeInMs:1976)
fact(n:25000 timeInMs:1976)
fact(n:25000 timeInMs:2553)
fact(n:25000 timeInMs:2459)
fact(n:25000 timeInMs:2505)
fact(n:25000 timeInMs:2522)
fact(n:30000 timeInMs:4373)
fact(n:30000 timeInMs:3160)
fact(n:30000 timeInMs:3066)
fact(n:30000 timeInMs:3922)
fact(n:30000 timeInMs:3034)

Lazy Fact:
fact(n:20000 timeInMs:2879)
fact(n:20000 timeInMs:1105)
fact(n:20000 timeInMs:2148)
fact(n:20000 timeInMs:2521)
fact(n:20000 timeInMs:2692)
fact(n:25000 timeInMs:3455)
fact(n:25000 timeInMs:2801)
fact(n:25000 timeInMs:3986)
fact(n:25000 timeInMs:3334)
fact(n:25000 timeInMs:3380)
fact(n:30000 timeInMs:5577)
fact(n:30000 timeInMs:5468)
fact(n:30000 timeInMs:5343)
fact(n:30000 timeInMs:5592)
fact(n:30000 timeInMs:5001)

Conclusion:
Lazy version is usually slow due to the thread overhead caused by implementation of laziness.

Ch4, Ex14

This is iterative even without dataflow variables due to the lazy nature and hence incremental computation of successive elements of append result.
fun lazy {LAppend Xs Ys}
case Xs of nil then Ys
[] X|Xr then X|{LAppend Xr Ys}
end
end

When first element is needed it returns X|<promised next elems> and it calculates the next elements on the need basis.

Ch4, Ex13

Execute Following and notice both are monolithic
declare
X = {Reverse1 [a b c]}
Y = {Reverse2 [a b c]}
{Browse X}
{Browse Y}
%First execute till here only
% then execute this
{Browse X.1}
% and then this
{Browse Y.1}

%Notice that whole [c b a] is displayed as soon as
%you display X.1 or Y.1

There is no difference in the results produced. Both are monolithic, that is as soon as the first element of the reversed list is needed, reverse of whole input list is calculated.

Second version is not good in terms of execution efficiency because making Rev lazy doesn't change anything but incurs the overhead of threads due to lazyness implementation.

Ch4, Ex12

- In concurrent model, there is practically no flow control. Producer can freely generate the stream far ahead than actually needed by Sum at any point in time. While in lazy model its the other extreme where Producer only produces the next element when Sum explicitly asks for it. If we talk in terms of bounded buffer flow control then first case is where the buffer length is infinite whereas in second case its 0.

- It wouldn't have any effect. It'll behave same as the lazy version.

Ch4, Ex11

It happens because the way the expression (X+Y)+Z is evaluated by oz implementation. It'll actually compute (X+Y)+Z by doing following:
local T R in
T=X+Y
R=T+Z
{Browse R}
end

Now T=X+Y immediately needs X and Y, {MakeX} and {MakeY} are called immediately which in turn print x, y. Need for Z arises only when execution reaches R=T+Z (which is after ~6 secs due to the explicit delay in MakeY) and hence z is printed only after ~6 secs. Now MakeZ also puts in explicit delay of 9 secs so R is only evaluated in 9+6=15 seconds.

Time Taken can be measure by following:
declare
fun lazy {MakeX} {Browse x} {Delay 3000} 1 end
fun lazy {MakeY} {Browse y} {Delay 6000} 2 end
fun lazy {MakeZ} {Browse z} {Delay 9000} 3 end
X={MakeX}
Y={MakeY}
Z={MakeZ}

local T1 T2 in
T1 = {Property.get 'time.total'}
%CHANGE HERE
%{Browse thread X+Y end +Z}
{Browse (X+Y)+Z}
T2 = {Property.get 'time.total'}
{Browse 'timeTaken:'#(T2-T1)}
end

For non-concurrent version it takes about 15 secs while for the concurrent version it takes around 9 secs(because Max of all explicit delays is 9 secs in MakeZ).

From above results it may seem that we should always use concurrency for adding integers but that is not true because here the evaluation of x, y and Z takes some time due to explicit delay but in adding simple integer values its practically 0 and if we then use concurrent version then we're unnecessarily burdening our program with thread management overhead and also spoiling the program structure.

Ch4, Ex10

Well this is implementation dependent, Current behavior is completely in compliance with the symantics given in section 4.5.1 and lazy function is translated to ByNeed. Each call to {Three} creates a new trigger which is activated upon the need and in turn a thread is created that executes statements defined in function Three. But, in principle, its indeed possible to implement lazyness in a way so that value of a lazy function is calculated only ones upon the first time need and stored for later also.... as far as I know its called "memoization" .