EndOfStream performance cost [was EndOfStream unused]

nicolas cellier ncellier at ifrance.com
Fri Nov 9 01:18:00 UTC 2007

Hi Andreas

To answer these important questions:
- what performance drop/gain can we expect (trade off)?
- should we have to rewrite anything apart core #next methods?

you can see I completed your tests at 

Here I take rationale again with a similar more systematic approach.


Analyzing residual costs with Micro bench should tell us the price:
1) == nil test costs = p (like pastEndTest)
2) atEnd test costs = a
3) exception handling cost = e when not handled and = h when handled

4) stream size is n

We get three main ways to write loops:
5) (P) a pastEnd loop costs (residual only)
	n*p   before,
	n*p+e after EndOfStream change.
6) (A) a atEnd test loop costs
	n*a before,
	n*a after.
7) (E) an exception loop
	is impossible before,
        	costs h after.


On my machine:

[200000 timesRepeat: []] timeToRun. 148
[200000 timesRepeat: [$a == nil ifFalse: [nil]]] timeToRun. 167

[5000 timesRepeat: []] timeToRun. 3
[5000 timesRepeat: [EndOfStream signal]] timeToRun. 159

[5000 timesRepeat: []] timeToRun. 3
[5000 timesRepeat: [[EndOfStream signal] on: EndOfStream do: [:exc | exc
return]]] timeToRun. 209

s := ReadStream on: (1 to: 1000) asArray.
[200000 timesRepeat: []] timeToRun. 148
[200000 timesRepeat: [s atEnd ifFalse: [nil]]] timeToRun.  223

e=0.03 h=0.04 a=0.00037 p=0.0001


Obviously, no hurry...
Since no exception is raised, cost is unchanged
It would be worth only if average n > h/a.
	(ON MY MACHINE n>120)


- Introducing the change costs a penalty e on each pastEnd loop (P)
- This is neglectable as soon as n >> e/p
- But does de-optimize small pastEnd loops as noticed by Andreas.


- rewrite (P) in (E) for small pastEnd loops is no use
   because n*p + e and h are same for small n
- rewrite (P) in (A) would be viable if n*p+e > n*a, n < e/(a-p)
	(ON MY MACHINE n<110)
- rewrite (P) in (E) would be worth for n > h/p.
	(ON MY MACHINE n>400)
   But no hurry, we have seen that penalty is small for such loops.


Introducing the change potentially cost a penalty on pastEnd short
stream tight loops.
If necessary, we might re-optimize a little most critical loops with a 
(P)->(A) rewrite, but without reaching previous performance.

Suggested by Paolo, my guess however is that:
- Most pastEnd loops are written for files. files are long and 
performance drop would be neglectable. A rewrite would benefit but no hurry.
- Most String processing loops use atEnd loop (upper level ReadStream
does not use == nil trick, client code might however uses few).

POSSIBLE PLAN to confirm: trap EndOfStream to trace usage of pastEnd
loops and run maximum activity in squeak...

Introducing this change does not seem worth per se, except maybe for 
huge files.
However - TO BE CONFIRMED, it might as well be neutral toward 
performances with current VMs.

It would be worth in this case for enabling Stream extensions having 
costly atEnd tests and no possible == nil trick.

Apart core #next, no rewrite of basic method is necessary.
No hurry to do massive rewrite of code anyway.



More information about the Squeak-dev mailing list