On Wednesday, February 20, 2013, Michael Kay wrote:
Another possible culprit is the call on preceding::* in

node()[preceding::*[. intersect $docNodes][1] is $final]

I think the question here is, how big is $docNodes? Testing the intersect operator will be linear in the size of $docNodes. 

This seems to be the issue. A small document with 40 $docNodes runs in about a second, while a more typical document has about 800 $docNodes and takes 1.5 hours. There are quite a lot of expressions that do an intersection test, so switching to a set of keys will definitely help.

Once again your undisputed mastery of XSLT lights the way.