对于某些预处理任务,我想使用expl3
属性列表。由于该列表中的项目数量将达到数千个(教科书的索引条目),我想知道这样做的时间效率如何。因此,我做了一些测试:
\documentclass{article}
\usepackage{expl3,xparse, fp}
% timing adapted from http://tex.stackexchange.com/questions/9087
\def\startTimer{\pdfresettimer}
\def\stopTimer{%
\FPdiv\result{\the\pdfelapsedtime}{65536}
\FPround\result{\result}{3}\par
Time (seconds): \result \par}
\ExplSyntaxOn
\prop_new:N \sg_user_mapping
\cs_generate_variant:Nn \prop_gput:Nnn { Nxx }
\int_new:N \i_target
\int_new:N \i_counter
\NewDocumentCommand{\dostuff}{m} {
Adding\ #1\ keys\ to\ user\ mapping\par
%\prop_gclear:N \sg_user_mapping
\startTimer
\int_add:Nn \i_target {#1}
\int_zero:N \i_counter % Hopefully obvious!
\int_while_do:nn { \i_counter < \i_target }
{
\edef\wurst{\int_to_arabic:n \i_counter}
\prop_gput:Nxx \sg_user_mapping {hans\wurst} {0}
\int_incr:N \i_counter
}
\stopTimer\par
}
\ExplSyntaxOff
\begin{document}
\ttfamily
\dostuff{10}
\bigskip
\dostuff{100}
\bigskip
\dostuff{1000}
\bigskip
\dostuff{10000}
\end{document}
以下是我得到的结果:
所以,这显然是 O^2 的情况;我猜测在插入新条目之前会扫描整个列表。
我想知道是否可以使用一些幕后技巧来利用pdftex
内置的哈希表。例如,对于每个键值对,
通过删除任何不能成为命令名称一部分的字符来净化密钥:
#foo%^bar!- baz?
变成foobarbaz
创建名称为
\pl_userland_foobarbaz
在该列表中存储
#foo%^bar!- baz?
=> 。whatever value
我确实尝试过,虽然代码在处理少量键时显然速度较慢,但确实可以按预期以 O^1 进行扩展,并且在约 1000 个键时达到与简单属性列表相同的水平。我犹豫是否要在此处发布该代码,因为我才刚刚开始expl3
,专业人士可能会做得更好;这只是一个实验。
当然,我的建议提出了一个问题,在开始把玩具扔出婴儿车之前,pdfTeX 能够容纳多少这样的键和属性列表实例?或者,也许可以在 TeX 本身中实现一个适当的哈希表;但我在这方面还不够好。
答案1
首先,需要强调的是仅记录接口因为expl3
并且那如果接口没有改变,实现可能会发生变化,恕不另行通知。(在我加入团队期间,我们在这里有过几种不同的实现,过去至少还有一种。)当前实现使用单个宏来保存整个属性列表(之前的实现使用单个 toks,在此之前存在一些 csname 奇怪之处:在我之前!)。
正如在如何在 TeX 中实现(低级)数组,在 TeX 中实现快速查找数组的最有效方法是使用哈希表,并确保控制序列名称不会引起太多冲突。(基本上,不要使用数字!)
然而,这并不是当前prop
数据类型设计的关键设计驱动因素expl3
。代码自在 LaTeX2e 发布之前(1994 年)当然还有另一个担忧:内存。如今,标准 TeX 二进制文件中可用的 csname 数量要多得多,因此保存它们的压力减少了(尽管根据使用情况,仍然有可能用完)。二十年前,压力要大得多:众所周知,在 MS-DOS 上使用 TeX 时,加载 LaTeX2e 内核后,只有大约 50 个“备用”名称。这个因素就是其中之一:随着时间的推移,实现方式已经发生了变化,但不只是简单地烧毁名称仍然是一个因素。
还有其他考虑因素。虽然使用哈希表方法进行随机查找很快,但在“幕后”复制属性列表更容易:它是一次性的,而不是循环。还有一个问题是如何映射列表中的每个条目:对于当前设置,我们可以扩展底层宏,然后处理堆栈上的条目。对每个条目使用单独的宏会使该过程更加棘手。
团队已经讨论过使用不同的实现,但到目前为止,从“用例”的角度来看,还没有强有力的推动力(当然对于团队自己的代码而言)。平衡各种用例并非易事,我们已经讨论过一种“对象”类型,它将具有多个“后端”,并可以在它们之间切换(也许通过用户设置)。