是否有基准测试来衡量哪些包比其他包需要更长的加载时间?
当然,我知道这取决于你正在运行的计算机,甚至可能取决于你的互联网连接(如果你正在“动态”加载它们),但这就是我要求基准测试的原因……是否有人真正花时间测量过哪些包需要更多时间加载?这可以被视为加快我工作速度的一个好方法吗?
答案1
我不确定为什么这么多人反对在 LaTeX 中进行基准测试。这似乎完全合理,而且我也曾这样做过,以找出我自己的 TeX 代码中的瓶颈。
幸运的是,pdfTeX 提供了两个新的方便的原语来控制时间,\pdfresettimer
和\pdfelapsedtime
。前者将内部计时器重置为 0,而后者提供对内部计时器的只读访问。单位是 1/65536 秒。
fp
这是一个使用该包将时间转换为毫秒的简单宏。
\RequirePackage{fp}
\newcount\timer
% \Time\cs{thing to time}
\newcommand\Time[2]{%
\pdfresettimer
#2%
\timer=\pdfelapsedtime
\FPdiv#1{\the\timer}{65.536}%
\FPtrunc#1#1{3}%
}
正如注释所示,您可以使用它\Time\foo{\usepackage{blah}}
来设置\foo
执行第二个参数中的代码所需的时间(以毫秒为单位)。
也可以\documentclass
通过将宏定义放在前面\documentclass
(因此\RequirePackage
)来计时。
article
这是一个完整的示例,展示了加载类和几个包的时间。
\RequirePackage{fp}
\newcount\timer
% \Time\cs{thing to time}
\newcommand\Time[2]{%
\pdfresettimer
#2%
\timer=\pdfelapsedtime
\FPdiv#1{\the\timer}{65.536}%
\FPtrunc#1#1{3}%
}
\Time\documentclasstime{\documentclass{article}}
\Time\tikztime{\usepackage{tikz}}
\Time\siunitxtime{\usepackage{siunitx}}
\Time\captoftime{\usepackage{capt-of}}
\Time\booktabstime{\usepackage{booktabs}}
\Time\graphicxtime{\usepackage{graphicx}}
\Time\hyperreftime{\usepackage{hyperref}}
\Time\mathptmxtime{\usepackage{mathptmx}}
\begin{document}
\begin{tabular}{@{}lS@{}}
\toprule
Code & {Time (\si{\milli\second})}\\
\midrule
\verb!\documentclass{article}! & \documentclasstime \\
\verb!\usepackage{tikz}! & \tikztime \\
\verb!\usepackage{siunitx}! & \siunitxtime \\
\verb!\usepackage{capt-of}! & \captoftime \\
\verb!\usepackage{booktabs}! & \booktabstime \\
\verb!\usepackage{graphicx}! & \graphicxtime \\
\verb!\usepackage{hyperref}! & \hyperreftime \\
\verb!\usepackage{mathptmx}! & \mathptmxtime \\
\bottomrule
\end{tabular}
\end{document}
etoolbox
使用来\patchcmd
修补以自动执行计时可能不会太困难\usepackage
,但我在这里没有这样做。
同样,创建环境比创建命令更容易。这样做的好处是不会对参数进行标记。但是,如果您要走这条路,\pdfresettimer
那么\pdfelapsedtime
直接使用也同样容易。
关于计时的一个注意事项\usepackage
是,如果您想要计时多个包,其中一个包含另一个(或两个都包含一个共同的第三个包),那么计时的顺序将很重要。例如,计时\usepackage{siunitx}
后再计时\usepackage{expl3}
表明expl3
几乎不占用任何时间,而siunitx
花费大量时间。相反,按相反顺序计时表明两者都需要适度的时间。tikz
并且graphicx
具有类似的行为。graphicx
在我的计算机上,如果先加载,则大约需要 4 毫秒tikz
,而从表中可以看出,如果后加载,则大约需要 1/100。
答案2
正如TH 回答。,可以使用 pdfTeX/(u)pTeX 中提供的原语在 TeX 运行中“内部”进行基准测试,或者通过在 LuaTeX 中模拟相同的原语进行基准测试。在 XeTeX 中执行相同操作是不可能的:必须使用“围绕”TeX 运行的系统工具。将会揭示一些细节,但重点是包加载中可能不应该担心。(另外:LaTeX 团队有一些实验代码,但目前尚未发布。)
一个耗时的操作是打开单独的文件。对于相同的代码,通常情况下,单个文件的加载速度比多个单独的文件要快。这就是“自定义”格式文件加载速度快的原因之一:例如具有预编译功能的超快 PDFLaTeX。
什么使得更多实际的区别在于代码在“严格”情况下(例如循环)的谨慎程度。随着时间的推移,这是我在自己的代码中努力解决的问题,并且很难做到正确。例如,LaTeX 团队一直在努力提高 的性能expl3
,这对使用该代码的任何人都产生了连锁反应。
答案3
LaTeX 软件包只是文本文件。一旦您将它们放在计算机上,就可以以相同的方式访问它们。
包的功能越多,涉及的代码就越多,因此文件就会越大。这也意味着加载时间会更长。另一方面,包会加载其他包以使用其功能。包capt-of
有 33 行代码,其中大部分是注释,而包siunitx
有超过一千行代码,并且需要整个 L3 内核。
因此,我们不需要进行基准测试,而是要看看这个软件包能做什么以及它是如何实现的。
答案4
实际上,LaTeX 已经提供了一些钩子来添加一些在包加载时执行的代码:
\makeatletter
\AddToHook{package/before}{\wlog{::::start loading \@currname : \the\pdf@elapsedtime}}
\AddToHook{package/after}{\wlog{::::done loading \@currname : \the\pdf@elapsedtime}}
pdftexcmds
需要包\pdf@elapsedtime
(用于不同引擎之间的可移植性)
::::
然后只需在日志文件中搜索行。
然而……在得到基准测试结果之后,如何将它们很好地可视化却有点困难,因为内容太多了。
我编写了一个小型 Python 脚本来可视化结果(好吧,使用正确的工具来完成工作。我本可以在 expl3 中编写它,但意义何在?)
input_log_file_path="/path/to/a.log"
output_file_name="/path/to/b.tex"
# ^^ fill in those 2. remember to escape `\' if you use them
# extra feature: `\wlog{::::mark something: \the\pdf@elapsedtime}' adds a marker with content `something'
time_scale=100
colors=["green!30!white", "blue!30!white", "red!30!white"]
first_coordinate=0.5
box_width=1.5
layer_separation=2
time_axis_length=27
import functools
lines=open(input_log_file_path, encoding='latin1').read()
printy=functools.partial(print, file=open(output_file_name, "w", encoding='u8'))
def set_globals_locals(globals, locals)->None:
#if both are originally None
#return caller's parent frame's globals and locals
import sys
# because of a Python bug https://bugs.python.org/issue21161 let's pass both as globals
out=sys._getframe(1).f_locals
if globals is None and locals is None:
f=sys._getframe(2)
return {**f.f_globals, **f.f_locals}, None
else:
pass
return globals, locals
def evalz(s: str, globals=None, locals=None, escape_char=None)->None:
globals, locals=set_globals_locals(globals, locals)
if escape_char is None:
escape_char=next(ch for ch in "%!@" if ch in s)
parts=s.split(escape_char)
result=[]
for i in range(len(parts)):
if i%2==0:
result.append(parts[i])
elif parts[i]=="": # %% → %
result.append(escape_char)
else:
try:
item=eval(parts[i], globals, locals)
except:
import sys
printy("Error while executing code ========\n"+ parts[i] + "\n========", file=sys.stderr)
raise
result.append(
item if isinstance(item, str) else
str(item) if isinstance(item, int) else
__import__("sympy").latex(item)
)
return "".join(result)
def printz(s: str, globals=None, locals=None, escape_char: str=None, end: str=None)->None:
globals, locals=set_globals_locals(globals, locals)
printy(evalz(s, globals, locals, escape_char=escape_char), end=end)
printz(
r"""%! TEX program = lualatex
\documentclass{article}
\usepackage{tikz}
\usepackage[a4paper, margin=0.5cm]{geometry}
%\usepackage{tooltip}
%\usepackage{pdfcomment}
\begin{document}
\begin{tikzpicture}[yscale=-1]
\draw [->] (0, 0) -- (0, $time_axis_length$);
\def\a#1#2{
\draw (0.1, #1) -- (-0.1, #1) node [left] {#2};
}
"""
, escape_char="$")
import re
items=[]
mark_event=object()
for line in lines.splitlines():
if not line.startswith("::::"): continue
match=re.fullmatch(r"::::(?:(start|done) loading ([a-zA-Z0-9\-]+)|mark (.*)): (\d+)", line)
assert match, line
moment: float=int(match[4])/65536
if match[3]:
items.append((mark_event, match[3], moment))
else:
is_start: bool=match[1]=="start"
package_name: str=match[2]
items.append((is_start, package_name, moment))
initial_skip=items[0][2]
def time_to_coordinate(t: float)->float:
return (t-initial_skip)*time_scale
for t in range(0, 50):
t/=50
if t>=initial_skip and time_to_coordinate(t)<=time_axis_length:
printz(r"\a{%time_to_coordinate(t)%}{%t%}")
mark_last_y=time_to_coordinate(-1)
mark_y_separation=0.25
package_stack=[]
for is_start, package_name, moment in items:
if not is_start and not package_stack:
continue
if is_start is mark_event:
layer=len(package_stack)
red_line_left=first_coordinate+layer_separation*(layer-1)+box_width
red_line_right=red_line_left+box_width/2
y_coordinate=time_to_coordinate(moment)
node_y_coordinate=mark_last_y=max(mark_last_y+mark_y_separation, y_coordinate)
printz(
r"""\draw[red] (%red_line_left%, %y_coordinate%)
-- (%red_line_right%, %y_coordinate%);
\path (%red_line_right%, %node_y_coordinate%) node [black, right] {\small %package_name%};
"""
)
elif is_start:
package_stack.append((package_name, moment))
else:
package_name1, start_moment=package_stack.pop()
assert package_name1==package_name
layer=len(package_stack)
#package_name_t=evalz(r"\pdftooltip{t}{%package_name%}")
package_name_t=package_name
color=colors[layer%len(colors)]
printz(
r"""\fill[%color%] (
%first_coordinate+layer_separation*layer%,
%time_to_coordinate(start_moment)%
) rectangle
(
%first_coordinate+box_width+layer_separation*layer%,
%time_to_coordinate(moment)%
) node [black, midway] {\small %package_name_t%};
"""
)
printy(
r"""
\end{tikzpicture}
\end{document}
"""
)
运行脚本,然后编译生成的 TeX 文件。
输出可能看起来像这样
(事实证明,相对而言,我的包裹加载速度并没有那么慢。为什么包裹之间的差距这么大?)