\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%,
\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
不知大佬们怎么看


你的这种写法确实会带来一点性能提升。这是由于 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\relaxcount 寄存器 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 则不会报错。
至于哪个更好,可能因人而异。
谢谢大佬,
我更新了问题,画了示意图,
我感觉:编译时要找出 前后配对的 if fi ,
loop 那种 配对会很长,可能会有一定影响,
xuanhuan 的 循环体没有嵌套,会简短点
@u870 不是你写的那样,上面已经说了,先展开
\fi,再展开\iterate,所以\iterate在\if... \fi的外面,并不是嵌套的。@u10307 大佬看下我这样理解有没有问题:
宏展开是旧宏展开(替换)成新宏,
也就是说旧宏 在展开过程中消失了 产生了新的宏,
引擎 parse if 时会根据 其定义,找到匹配的 fi,然后展开(旧的随即消失)
所以在 TeX 消化道中,并没有嵌套很深的结构,因为旧的都已经被消化了
@u870 大致如此。
但“寻找”和“展开”还是有区别的:
比如:
则首先是“寻找”
\else或\fi,当找到了\else,则是展开 else 分支,展开到\fi则停止。“寻找”不会展开。