lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]

> Even if for realistic numbers of
> arguments the quadratic complexity doesn't bite one, I feel dirty writing
> such code knowing it's there and on the other hand knowing that the linear
> algorithms carry extra cost is again annoying because it means that in the
> realistic case, they actually are less efficient (though not by much).
> I should probably just get over it (one way or the other).

I don't know how this compares speed-wise against the quadratic
version (for our purposes it's
been fast enough), and there are obviously more algorithms to
consider, but it's not too bad
making an apairs() iterator that's almost garbage-free.

This came up a while back in our project when we were getting
noticeable GC hits that proved
hard to rectify with mere tuning. Among other problems, several
stateful iterators had a habit
of churning out little helper tables which quite quickly piled up.

For iterators, what I did was build several on top of a pattern that
allows for recycling, consisting
basically of the following four operations:

(1 - iterator setup
(2 - is it done?
(3 - the standard iterator body that supplies the values we want
(4 - a cleanup function (which can also be called explicitly, say in
the middle of a loop we exit early)

These all get combined into one function that provides the interface.
When this function is called, it
first checks a cache. If the cache is empty, a new closure is built
that wraps the above four operations
and any state shared among them; if not empty, such a closure gets
extracted instead. The setup logic
then initializes its state, and your iterator is ready to go. When
it's done, the cleanup is called to clear
the state, and the closure puts itself back in the cache.

In the apairs() case, setup is the familar { n = select("#", ...), ...
}, but reusing the table; cleanup wipes
the table clean, so it's good for the next use. For "unrealistic"
numbers of arguments it might be best
to just throw the table away and grab another next time.

The cache approach also has a few helpful consequences:

(1 - You can nest the iterator without problems
(2 - You can save the closure for later without crippling the iterator
(3 - If an error occurs mid-iteration the closure just becomes garbage
without breaking the iterator

In the attachments:

VarOps.lua has CollectArgsInto(), which I use to stuff tables, and a
couple utilities. The Collect()
helper function is of course easily written in C if that option is available.

In Iterators.lua you can see CachedCallback() and APairs(), among others.

(License is MIT; these were in the demo I linked to in another recent thread.)

Attachment: VarOps.lua
Description: Binary data

Attachment: Iterators.lua
Description: Binary data