I didn't realize that the compiler was re-invoked after every loop
It's not. Looping has nothing to do with it, for starters.
The regexp compiler is invoked every time a qr//, m// or s/// operator is invoked (and more?), if the regexp has changed since the last time that match or substitute operator has been invoked.
For example, notice there's no "Compiling" message on the third pass, no matter whether qr// or m// is used.
>perl -Mre=debug -e"/$_/ for qw( foo bar bar foo )" 2>&1 | find "Compi
+ling"
Compiling REx `foo'
Compiling REx `bar'
Compiling REx `foo'
>perl -Mre=debug -e"qr/$_/ for qw( foo bar bar foo )" 2>&1 | find "Com
+piling"
Compiling REx `foo'
Compiling REx `bar'
Compiling REx `foo'
A regexp compiled with qr// won't get recompiled when it's included in m// or s/// if it the regexp operand consist entirely of the qr// regexp. However, it might be recompiled when interpolated into another regexp.
>perl -Mre=debug -e"$re=qr/foo/; /$re/" 2>&1 | find "Compiling"
Compiling REx `foo'
>perl -Mre=debug -e"$re=qr/foo/; /a$re/" 2>&1 | find "Compiling"
Compiling REx `foo'
Compiling REx `a(?-xism:foo)'
|