loop 改进循环:嵌套 一》首尾相连接

发布于 2022-05-14 14:23:37
原版 loop 循环定义:
\long\def\loop#1\repeat{\def\iterate{#1\relax\expandafter\iterate\fi}\iterate \let \iterate \relax}

loop 做 if 条件 事repeat

感觉这样展开会产生大量嵌套结构,总操心要栈溢出,

想改成如下这种形式
\long\def\xunhuan#1\repeat{\def\iterate{#1\else\let\iterate\relax \fi\iterate}\iterate}

经测试,改进版性能略高一丁点 1%,

调试 MWE 如下:
\documentclass[UTF8]{article}
\begin{document}

\long\def\xunhuan#1\repeat{\def\iterate{#1\else\let\iterate\relax \fi\iterate}\iterate}

0\count5656=0
\count5657=29999

\xunhuan \the\count5656 \advance\count5656by1 \ifnum \count5656<\count5657  else \repeat

\loop \the\count5656 \advance\count5656by1 \ifnum \count5656<\count5657 else \repeat

\end{document}

为减少边际效应的干扰,可以考虑用 plain TeX 来编译

\long\def\loop#1\repeat{\def\iterate{#1\relax\expandafter\iterate\fi}\iterate \let\iterate\relax}
\long\def\xunhuan#1\repeat{\def\iterate{#1\else\let\iterate\relax \fi\iterate}\iterate}

\count206=0
\count207=399999999

\xunhuan \relax \advance\count206by1 \ifnum \count206<\count207\repeat

%\loop \relax \advance\count206by1 \ifnum \count206<\count207\repeat
\bye

编译: pdftex -quiet -time-statistics MWE.tex

不知大佬们怎么看

loop 示意图

image.png

xuanhuan 示意图

image.png

LoopXuanhuan执行示意图.xlsx

查看更多

关注者
0
被浏览
135
雾月
雾月 2022-05-14

你的这种写法确实会带来一点性能提升。这是由于 TeX 进行了更少的展开和内部操作。哪怕是在 \else\expandafter 前增加一个 \relax 都会增加编译时间。

\documentclass{article}
\begin{document}

\long\def\loop#1\repeat{\def\iterate{#1\relax\expandafter\iterate\fi}\iterate \let\iterate\relax}
\long\def\xunhuan#1\repeat{\def\iterate{#1\else\let\iterate\relax \fi\iterate}\iterate}
%\long\def\xunhuan#1\repeat{\def\iterate{#1\relax\else\let\iterate\relax \fi\iterate}\iterate \let\iterate\relax}

\count5656=0
\count5657=300000000

%\xunhuan \advance\count5656by1 \ifnum \count5656<\count5657  \relax \repeat
%\the\count5656

\count5656=0
\loop \advance\count5656by1 \ifnum \count5656<\count5657 \relax \repeat
\the\count5656

\end{document}

(谨慎运行!)

这种最简单的循环,在我的电脑上,使用 \loop 大概 61.7s,使用 \xunhuan 大概 57.4s,\loop 大致慢了 6%-8% 左右,但是当需要大量的计算时,用在 \loop 中的时间几乎可以忽略不计,实际区别很小。

但是你的代码与原来的 loop 并不是完全等价的,这里暂时先不说。

只要循环中不保存内容(不修改内部的 hash 表)、不留下 typeset material、不输出信息(log 等),仅包含(有效的)单纯的计算(比如 \advance),(可能所列并不完整,)TeX 不会在循环中消耗一丁点内存,因此理论上这样的“循环”的循环次数是无限的。这与其它编程语言是不同的。

诸如 C 之类的语言,在函数执行时会为其开辟新的内存空间,因此如果在循环时内存不回收,则可能会溢出,
然而在递归时,想要回收这些内存是很难的。

但是 TeX 不一样,TeX 在执行时,只是展开这些宏,

\loop \advance\count5656by1 \ifnum \count5656<\count5657 \repeat

展开,变成了

\def\iterate{\advance\count5656by1 \ifnum \count5656<\count5657 
  \relax\expandafter\iterate\fi}\iterate 
\let \iterate \relax

定义 \iterate,再展开 \iterate,此时 \let\iterate\relax 还未执行:

\advance\count5656by1 \ifnum \count5656<\count5657 \relax\expandafter\iterate\fi
\let\iterate\relax

count 寄存器 5656 加 1,即使是 \ifnum 也是执行展开,假设判断为真,则要么向后找到一个 \else(并不必须是 \else,任何一个被 \let 为 primitive \else 的都可以,要么向后找到一个 \fi(同样不必是 \fi),这一点必须要注意,与 \def 中的参数定界符不同。
这里没有发现 \else,TeX 先展开 \fi,然后将 \iterate 放在即将执行的输出流中。继续递归。

如果判断为假,TeX 不展开 \relax\expandafter\iterate\fi,而是直接找 \else\fi(不必是 \else、\fi)。而此时 \relax\expandafter\iterate\fi 四者完全可能是 \else\fi 二者之一。如果 \relax\expandafter\iterate 均不是 \else\fi 二者之一,且 \fi 是 primitive \fi,那么 TeX 正确的结束此次循环,并

\let\iterate\relax

否则,在预想情况下,应该出错(报错)。因为重定义 \iterate 是不被允许的。

可以看到,在此循环中并不涉及内存分配,因此并不会出现爆栈或 overflow。

而若包含 typeset material 等内容,当一个段落的内容过多,或待输出的 typeset material 过多,或在循环过程中在 hash 表中增加了巨量的内容,则会出现内存用光的情况。这是这两种循环都会出现的。

考虑如下代码:

\documentclass{article}
\begin{document}
\long\def\xunhuan#1\repeat{\def\iterate{#1\else\let\iterate\relax \fi\iterate}\iterate}

\count5656=0
\count5657=3 %00000000

\loop \advance\count5656by1 \let\iterate\fi \ifnum \count5656>\count5657 \repeat

\end{document}

编译报错了,原因正是如上所说的 TeX 并不需要 \fi 是 \fi,任何被 \let 为 primitive \fi 的都可以。但是换成你的 \xunhuan 则不会报错。

至于哪个更好,可能因人而异。

1 个回答

撰写答案

请登录后再发布答案,点击登录

发布
问题

分享
好友

手机
浏览

扫码手机浏览