Iteration Speed, Collections, and speed

Richard A. O'Keefe ok at atlas.otago.ac.nz
Wed Jan 30 23:50:55 UTC 2002


"Mark Mullin" <mark at vibrant3d.com> wrote:
	What I mean to say is that for iterated collections such as ordered
	collection, the process of adding and searching the collection is
	significantly more expensive than in a lower level compiled language such as
	C++.  I'm not denying the benefits of 1st class oop, garbage collection, or
	all the other great things I get, I'm just observing on a performance
	number.
	
Yes, but that performance number has NOTHING to do with iteration,
and EVERYTHING to do with the fact that C++ is compiled to native code
and Squeak isn't.

	What this means in using Squeak is that you can't opt out to the C++ dumb
	ass solution of "I got a list of 10K items, I'll just grovel through them to
	find the one I want."  What is ignorable in C++ is a slow mover in
	Smalltalk.
	
I don't get it.  What _exactly_ is the difference between

    vector<string> x;

    ...
    x.push_back("foo");
    ...
    for (vector<string>::const_iterator b = x.begin(), e = x.end(); b != e; ) {
	string s = *b++;
	... do something with s ...
    }

and

    | aCollection |
    aCollection := OrderedCollection new.
    ...
    aCollection addLast: "foo".
    ...
    aCollection do: [:s |
	"do something with s"].

vector<T> (at least in the Rogue Wave library) and OrderedCollection
are basically the SAME animal: something that looks a lot like an array
and is implemented using a pointer to an array which is grown whenever
necessary.  In terms of what's actually going on, they're pretty much
the same.

You might argue that the Smalltalk version contains a block, which is
going to be slower.  So you might prefer as the Smalltalk equivalent:

    | aCollection aReadStream s |
    aCollection := OrderedCollection new.
    ...
    aCollection addLast: "foo".
    ...
    aReadStream := aCollection readStream.
    [aReadStream atEnd] whileFalse: [
        s := aReadStream next.
        "do something with s"].

because the blocks there will be open-coded.

So now the actual loop looks like

L1:
    call vector<string>::const_iterator::"!="(b, e)
    branch-if-false L2
    call vector<string>::const_iterator::"postfix ++"(b)
    name the result %t
    call vector<string>::const_iterator::"*"(%t)
    name the result s
    store the result in s
    do something with s
    branch-always L1
L2:

for C++, and
L1:
    call aReadStream atEnd
    branch-if-false L2
    call aReadStream next
    store the result in s
    do something with s
    branch-always L1
L2:

What's the big difference?  Simply this:
(a) the variables b,e have a type which is *statically* known to the C++
    compiler.
(b) the C++ compiler therefore knows *exactly* which method to call;
    there will be no dynamic dispatch.
(c) since the C++ compiler knows which function to call, it may well
    compile some or all of these method calls as in-line code.

In the C++ library I checked, vector<> had about 500 lines of non-inline
code and about 1500 lines of inlinable code.  It's an amazing tangle of
template code, default arguments, special cases to be optimised away.
I'd hate to have to write that kind of stuff for a living.

The bottom line is that it's *NOT* iteration as such.
In the one actual case we've been told about, Squeak and C++ are
actually doing essentially the same thing.

The difference is
 - static type system
 - variables with non-polymorphic types
 - a computation-universal "template" sub-language
 - compiling method calls inline

Note that very little in the Smalltalk collection classes is actually
incompatible with these things.  Now that Eiffel has "agents" (closures),
one could migrate the collection classes over as Eiffel generic classes
and get C++-or-better performance out of them (using the SmallEiffel
compiler), without changing anything about the way iteration is done.




More information about the Squeak-dev mailing list