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