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
Sunday, February 1, 2009
Ch4, Ex15
I'm putting a regular and a complete lazy version of iterative factorial.
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.
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.
When first element is needed it returns X|<promised next elems> and it calculates the next elements on the need basis.
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
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.
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.
- 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:
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:
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.
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" .
Subscribe to:
Posts (Atom)
