包裹加载速度

包裹加载速度

是否有基准测试来衡量哪些包比其他包需要更长的加载时间?

当然,我知道这取决于你正在运行的计算机,甚至可能取决于你的互联网连接(如果你正在“动态”加载它们),但这就是我要求基准测试的原因……是否有人真正花时间测量过哪些包需要更多时间加载?这可以被视为加快我工作速度的一个好方法吗?

答案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 文件。

输出可能看起来像这样

示例输出

(事实证明,相对而言,我的包裹加载速度并没有那么慢。为什么包裹之间的差距这么大?)

相关内容