diff options
Diffstat (limited to 'Master/texmf-dist/source/latex/l3experimental/l3benchmark')
-rw-r--r-- | Master/texmf-dist/source/latex/l3experimental/l3benchmark/l3benchmark.dtx | 421 |
1 files changed, 209 insertions, 212 deletions
diff --git a/Master/texmf-dist/source/latex/l3experimental/l3benchmark/l3benchmark.dtx b/Master/texmf-dist/source/latex/l3experimental/l3benchmark/l3benchmark.dtx index 8f3fc919b17..cf8414f22fc 100644 --- a/Master/texmf-dist/source/latex/l3experimental/l3benchmark/l3benchmark.dtx +++ b/Master/texmf-dist/source/latex/l3experimental/l3benchmark/l3benchmark.dtx @@ -44,7 +44,7 @@ % }^^A % } % -% \date{Released 2018-10-31} +% \date{Released 2018-11-19} % % \maketitle % @@ -71,54 +71,32 @@ % \section{Benchmark} % % \begin{variable}{\g_benchmark_duration_target_fp} -% This variable controls roughly for how long \cs{benchmark:n} will -% repeat code to more accurately benchmark it. The actual duration of -% one call to \cs{benchmark:n} typically lasts between half and twice -% \cs{g_benchmark_duration_target_fp} seconds, unless of course -% running the code only once already lasts longer than this. +% This variable (default value: $1$) controls roughly for how long +% \cs{benchmark:n} will repeat code to more accurately benchmark it. +% The actual duration of one call to \cs{benchmark:n} typically lasts +% between half and twice \cs{g_benchmark_duration_target_fp} seconds, +% unless of course running the code only once already lasts longer +% than this. % \end{variable} % -% \begin{variable}{\g_benchmark_time_fp, \g_benchmark_ops_fp} -% Functions such as \cs{benchmark:n} store the measured time in -% \cs{g_benchmark_time_fp} (in seconds). Functions such as -% \cs{benchmark_normalized:n} store the estimated number of operations -% in \cs{g_benchmark_ops_fp}. -% \end{variable} -% -% \begin{function}{\benchmark_display:, \benchmark_display_in_ops:} -% \begin{syntax} -% \cs{benchmark_display:} -% \end{syntax} -% Prints the time \cs{g_benchmark_time_fp} (in seconds) or the -% estimated number of operations \cs{g_benchmark_ops_fp} to the -% terminal. These functions are called by functions such as -% \cs{benchmark:n} and can be redefined by the user. -% \end{function} -% -% \begin{function}{\benchmark_once:n, \benchmark_once_in_ops:n, \benchmark_once_silent:n} +% \begin{function}{\benchmark_once:n} % \begin{syntax} % \cs{benchmark_once:n} \Arg{code} % \end{syntax} -% Measures the time taken by \TeX{} to run the \meta{code} once, sets -% \cs{g_benchmark_time_fp} and \cs{g_benchmark_ops_fp}, and calls -% \cs{benchmark_display:}. The \meta{code} is run only once so the -% time may be quite inaccurate for fast code. The -% \cs{benchmark_once_silent:n} function omits the call to -% \cs{benchmark_display:}, while \cs{benchmark_once_in_ops:n} calls -% \cs{benchmark_display_in_ops:} instead. +% Prints to the terminal the time taken by \TeX{} to run the +% \meta{code}, and an estimated number of elementary operations. The +% \meta{code} is run only once so the time may be quite inaccurate for +% fast code. % \end{function} % -% \begin{function}{\benchmark:n, \benchmark_in_ops:n, \benchmark_silent:n} +% \begin{function}{\benchmark:n} % \begin{syntax} % \cs{benchmark:n} \Arg{code} % \end{syntax} -% Measures the time taken by \TeX{} to run the \meta{code}, sets -% \cs{g_benchmark_time_fp} and \cs{g_benchmark_ops_fp}, and calls -% \cs{benchmark_display:}. The \meta{code} may be run many times and -% not within a group, thus code with side-effects may cause problems. -% The \cs{benchmark_silent:n} function omits the call to -% \cs{benchmark_display:}, while \cs{benchmark_in_ops:n} calls -% \cs{benchmark_display_in_ops:} instead. +% Prints to the terminal the time taken by \TeX{} to run the +% \meta{code}, and an estimated number of elementary operations. The +% \meta{code} may be run many times and not within a group, thus code +% with side-effects may cause problems. % \end{function} % % \begin{function}{\benchmark_tic:, \benchmark_toc:} @@ -130,8 +108,8 @@ % tic/toc commands can be used instead to time between two points in % the code. When executed, \cs{benchmark_tic:} will print a line to the % terminal, and \cs{benchmark_toc:} will print a matching line with a -% time to indicate the duration between them in seconds. Note that -% these commands can be nested. +% time to indicate the duration between them in seconds. +% These commands can be nested. % \end{function} % % \end{documentation} @@ -148,7 +126,7 @@ % % \begin{macrocode} %<*package> -\ProvidesExplPackage{l3benchmark}{2018-10-31}{} +\ProvidesExplPackage{l3benchmark}{2018-11-19}{} {L3 Experimental benchmarking} %</package> % \end{macrocode} @@ -163,8 +141,7 @@ % \begin{macro}[EXP,pTF]{\sys_if_timer_exist:} % In \LuaTeX{}, use emulation (see \pkg{l3luatex}), % otherwise try to locate the primitive. The -% elapsed time will be available if this succeeds or if unrestricted -% shell escape is available. +% elapsed time will be available if this succeeds. % \begin{macrocode} \sys_if_engine_luatex:TF { @@ -228,97 +205,35 @@ %<@@=benchmark> % \end{macrocode} % -% \begin{variable}{\g_benchmark_time_fp, \g_benchmark_ops_fp} -% Functions such as \cs{benchmark:n} store the measured time in -% \cs{g_benchmark_time_fp} (in seconds) and the estimated number of -% operations in \cs{g_benchmark_ops_fp}. +% \begin{variable}{\g_benchmark_duration_target_fp} +% The benchmark is constrained to take roughly (from half to twice) +% \cs{g_benchmark_duration_target_fp} seconds, unless one iteration of +% the code takes longer. % \begin{macrocode} -\fp_new:N \g_benchmark_time_fp -\fp_new:N \g_benchmark_ops_fp +\fp_new:N \g_benchmark_duration_target_fp +\fp_gset:Nn \g_benchmark_duration_target_fp { 1 } % \end{macrocode} % \end{variable} % -% \begin{macro}{\benchmark_display:, \benchmark_display_in_ops:} -% Function to display the time that was measured or the estimated -% number of operations. This can be redefined by the user. -% \begin{macrocode} -\cs_new_protected:Npn \benchmark_display: - { \iow_term:x { \fp_to_tl:N \g_benchmark_time_fp \c_space_tl seconds } } -\cs_new_protected:Npn \benchmark_display_in_ops: - { - \iow_term:x - { - \fp_compare:nTF { \g_benchmark_ops_fp > 1e9 } - { \fp_to_tl:n { \g_benchmark_ops_fp * 1e-9 } ~ G~ops } - { - \fp_compare:nTF { \g_benchmark_ops_fp > 1e6 } - { \fp_to_tl:n { \g_benchmark_ops_fp * 1e-6 } ~ M~ops } - { - \fp_compare:nTF { \g_benchmark_ops_fp > 1e3 } - { \fp_to_tl:n { \g_benchmark_ops_fp * 1e-3 } ~ K~ops } - { \fp_to_tl:n { \g_benchmark_ops_fp } ~ ops } - } - } - } - } -% \end{macrocode} -% \end{macro} -% +% Having access to the system time is essential. % \begin{macrocode} \sys_if_timer_exist:F { - \msg_new:nnnn { benchmark } { no-time } - { The~l3benchmark~package~failed~to~access~a~clock. } - { The~current~engine~provides~no~way~to~access~the~system~time. } + \fp_gset:Nn \g_benchmark_duration_target_fp { nan } \cs_new_protected:Npn \benchmark_once:n #1 { \msg_error:nn { benchmark } { no-time } } - \cs_new_protected:Npn \benchmark_once_in_ops:n #1 - { \msg_error:nn { benchmark } { no-time } } - \cs_new_protected:Npn \benchmark_once_silent:n #1 + \cs_new_eq:NN \benchmark:n \benchmark_once:n + \cs_new_protected:Npn \benchmark_tic: { \msg_error:nn { benchmark } { no-time } } - \cs_new_protected:Npn \benchmark:n #1 - { \msg_error:nn { benchmark } { no-time } } - \cs_new_protected:Npn \benchmark_in_ops:n #1 - { \msg_error:nn { benchmark } { no-time } } - \cs_new_protected:Npn \benchmark_silent:n #1 - { \msg_error:nn { benchmark } { no-time } } - \fp_gset:Nn \g_benchmark_time_fp { nan } - \fp_gset:Nn \g_benchmark_ops_fp { nan } + \cs_new_eq:NN \benchmark_toc: \benchmark_tic: + \msg_new:nnnn { benchmark } { no-time } + { The~l3benchmark~package~failed~to~access~a~clock. } + { The~current~engine~provides~no~way~to~access~the~system~time. } \msg_critical:nn { benchmark } { no-time } } % \end{macrocode} % -% \begin{variable}{\g_benchmark_duration_target_fp, \g_@@_duration_int} -% The benchmark is constrained to take roughly (from half to twice) -% \cs{g_benchmark_duration_target_fp} seconds, more precisely -% \cs{g_@@_duration_int} scaled seconds, unless one iteration of the -% code takes longer. -% \begin{macrocode} -\fp_new:N \g_benchmark_duration_target_fp -\fp_gset:Nn \g_benchmark_duration_target_fp { 1 } -\int_new:N \g_@@_duration_int -% \end{macrocode} -% \end{variable} -% -% \begin{variable}{\g_@@_time_int, \g_@@_time_a_int, \g_@@_time_b_int, \g_@@_time_c_int, \g_@@_time_d_int} -% These variables hold the time for running a piece of code, as an -% integer in scaled seconds. -% \begin{macrocode} -\int_new:N \g_@@_time_int -\int_new:N \g_@@_time_a_int -\int_new:N \g_@@_time_b_int -\int_new:N \g_@@_time_c_int -\int_new:N \g_@@_time_d_int -% \end{macrocode} -% \end{variable} -% -% \begin{variable}{\g_@@_repeat_int} -% Holds the number of times that the piece of code was -% repeated when timing. -% \begin{macrocode} -\int_new:N \g_@@_repeat_int -% \end{macrocode} -% \end{variable} +% \subsubsection{Raw measurement} % % \begin{variable}{\g_@@_nesting_int} % \begin{macro}{\@@_raw:nN, \@@_raw_aux:N, \@@_raw_end:N} @@ -401,74 +316,115 @@ % \end{macrocode} % \end{macro} % -% \begin{macro}{\benchmark:n, \benchmark_silent:n} -% The main timing function. First time the user code once. If that -% took more than a third of a second we're done. If that took much -% less than a second, quadruple the number of copies until it takes a -% reasonable amount of time (this is to avoid division by a possibly -% zero time). Once we reach a reasonable time, compute a number of -% times that can fit in one quarter of a second and measure that four -% times. To save time we reuse the result of the first pass if -% \cs{g_@@_repeat_int} is one. Once we have four results, find the smallest, -% divided by $65536$ and by the number of repetitions, and display that. +% \subsubsection{Main benchmarking} +% +% \begin{variable}{\g_@@_time_fp, \g_@@_ops_fp} +% Functions such as \cs{benchmark:n} store the measured time in +% \cs{g_@@_time_fp} (in seconds) and the estimated number of +% operations in \cs{g_@@_ops_fp}. +% \begin{macrocode} +\fp_new:N \g_@@_time_fp +\fp_new:N \g_@@_ops_fp +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\g_@@_duration_int} +% A conversion of \cs{g_benchmark_duration_target_fp} seconds into scaled seconds. +% \begin{macrocode} +\int_new:N \g_@@_duration_int +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\g_@@_time_int, \g_@@_time_a_int, \g_@@_time_b_int, \g_@@_time_c_int, \g_@@_time_d_int} +% These variables hold the time for running a piece of code, as an +% integer in scaled seconds. +% \begin{macrocode} +\int_new:N \g_@@_time_int +\int_new:N \g_@@_time_a_int +\int_new:N \g_@@_time_b_int +\int_new:N \g_@@_time_c_int +\int_new:N \g_@@_time_d_int +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\g_@@_repeat_int} +% Holds the number of times that the piece of code was +% repeated when timing. +% \begin{macrocode} +\int_new:N \g_@@_repeat_int +% \end{macrocode} +% \end{variable} +% +% \begin{variable}{\g_@@_code_tl} +% Holds the piece of code to repeat. % \begin{macrocode} -\fp_new:N \g_@@_one_op_fp \tl_new:N \g_@@_code_tl -\cs_new_protected:Npn \benchmark:n #1 - { \benchmark_silent:n {#1} \benchmark_display: } -\cs_new_protected:Npn \benchmark_in_ops:n #1 - { \benchmark_silent:n {#1} \benchmark_display_in_ops: } -\cs_new_protected:Npn \@@_measure_op: +% \end{macrocode} +% \end{variable} +% +% \begin{macro}{\benchmark_once:n} +% Convert the raw time from scaled seconds to seconds, and convert to +% a number of operations. It is important to measure the elementary +% operation before running the user code because both measurements use +% the same temporary variables. +% \begin{macrocode} +\cs_new_protected:Npn \benchmark_once:n #1 { - \int_gset:Nn \g_@@_duration_int { 256 } - \tl_gset:Nn \g_@@_code_tl - { \int_gset:Nn \g_@@_duration_int { 256 } } % arbitrary single operation - \@@_aux: - \fp_gset_eq:NN \g_@@_one_op_fp \g_benchmark_time_fp - \fp_compare:nNnT \g_@@_one_op_fp < { 1e-6 } - { \fp_gset:Nn \g_@@_one_op_fp { 1e-6 } } + \@@_measure_op: + \@@_raw:nN {#1} \g_@@_time_int + \fp_gset:Nn \g_@@_time_fp { \g_@@_time_int / 65536 } + \@@_display: } -\cs_new_protected:Npn \benchmark_silent:n #1 +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\benchmark:n} +% After setting up some variables the work is done by \cs{@@_aux:}. +% \begin{macrocode} +\cs_new_protected:Npn \benchmark:n #1 { \@@_measure_op: - \int_gset:Nn \g_@@_duration_int - { \fp_to_int:n { 65536 * \g_benchmark_duration_target_fp } } \tl_gset:Nn \g_@@_code_tl {#1} \@@_aux: - \fp_gset:Nn \g_benchmark_ops_fp - { \g_benchmark_time_fp / \g_@@_one_op_fp } + \@@_display: } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\@@_aux:} +% The main timing function. First time the user code once. If that +% took more than half the allotted time (\cs{g_@@_duration_int}) we're +% done. If that took much less, repeatedly quadruple the number of +% copies until it takes a reasonable amount of time. Once we reach a +% reasonable time (or we risk an overflow), compute a number of times +% that can fit in one quarter of the allotted time and measure that +% four times. To save time we reuse the result of the first pass if +% \cs{g_@@_repeat_int} is one. Once we have four results, find the +% smallest, divided by $65536$ and by the number of repetitions, and +% display that. +% \begin{macrocode} \cs_new_protected:Npn \@@_aux: { \int_gset:Nn \g_@@_repeat_int { 1 } \@@_raw:nN { \g_@@_code_tl } \g_@@_time_int \int_compare:nNnF \g_@@_time_int < { \g_@@_duration_int / 2 } + { \prg_break: } + \bool_until_do:nn { - \int_gset_eq:NN \g_@@_time_a_int \g_@@_time_int - \int_gset_eq:NN \g_@@_time_b_int \g_@@_time_int - \int_gset_eq:NN \g_@@_time_c_int \g_@@_time_int - \int_gset_eq:NN \g_@@_time_d_int \g_@@_time_int - \prg_break: + \int_compare_p:nNn \g_@@_time_int > { \g_@@_duration_int / 32 } + || \int_compare_p:nNn \g_@@_repeat_int > { \c_max_int / 4 } } - \int_while_do:nNnn \g_@@_time_int < { \g_@@_duration_int / 100 } { - \int_compare:nNnT \g_@@_repeat_int > { \c_max_int / 4 } - { - \int_gzero:N \g_@@_time_a_int - \int_gzero:N \g_@@_time_b_int - \int_gzero:N \g_@@_time_c_int - \int_gzero:N \g_@@_time_d_int - \prg_break: - } \int_gset:Nn \g_@@_repeat_int { 4 * \g_@@_repeat_int } \@@_run:N \g_@@_time_int } \int_gset:Nn \g_@@_repeat_int { - \int_max:nn { 1 } + \fp_to_int:n { - \g_@@_duration_int * \g_@@_repeat_int - / ( \g_@@_time_int * 4 ) + max ( 1 , min ( \c_max_int , + \g_@@_duration_int * \g_@@_repeat_int / + \int_eval:n { 4 * \g_@@_time_int } ) ) } } \int_compare:nNnTF \g_@@_repeat_int = 1 @@ -477,67 +433,112 @@ \@@_run:N \g_@@_time_b_int \@@_run:N \g_@@_time_c_int \@@_run:N \g_@@_time_d_int - \prg_break_point: - \fp_gset:Nn \g_benchmark_time_fp + \int_gset:Nn \g_@@_time_int { \int_min:nn { \int_min:nn \g_@@_time_a_int \g_@@_time_b_int } { \int_min:nn \g_@@_time_c_int \g_@@_time_d_int } - / \g_@@_repeat_int / 65536 } + \prg_break_point: + \int_compare:nNnT \g_@@_time_int < 3 { \int_gzero:N \g_@@_time_int } + \fp_gset:Nn \g_@@_time_fp + { \g_@@_time_int / \g_@@_repeat_int / 65536 } } \cs_new_protected:Npn \@@_run:N { \exp_args:NNo \@@_raw_replicate:nnN \g_@@_repeat_int { \g_@@_code_tl } } % \end{macrocode} % \end{macro} % -% \begin{macro}{\benchmark_once:n, \benchmark_once_in_ops:n, \benchmark_once_silent:n} -% Convert from scaled seconds to seconds. +% \subsubsection{Display} +% +% \begin{variable}{\g_@@_one_op_fp} +% Time for one operation. % \begin{macrocode} -\cs_new_protected:Npn \benchmark_once:n #1 - { \benchmark_once_silent:n {#1} \benchmark_display: } -\cs_new_protected:Npn \benchmark_once_in_ops:n #1 - { \benchmark_once_silent:n {#1} \benchmark_display_in_ops: } -\cs_new_protected:Npn \benchmark_once_silent:n #1 +\fp_new:N \g_@@_one_op_fp +% \end{macrocode} +% \end{variable} +% +% \begin{macro}{\@@_measure_op:} +% Measure one arbitrary single operation (which we put in \cs{g_@@_code_tl}). +% This uses a common auxiliary \cs{@@_aux:} with the main benchmark function. +% \begin{macrocode} +\cs_new_protected:Npn \@@_measure_op: { - \@@_measure_op: - \@@_raw:nN {#1} \g_@@_time_int - \fp_gset:Nn \g_benchmark_time_fp { \g_@@_time_int / 65536 } - \fp_gset:Nn \g_benchmark_ops_fp - { \g_benchmark_time_fp / \g_@@_one_op_fp } + \int_gset:Nn \g_@@_duration_int + { \fp_to_int:n { 65536 * \g_benchmark_duration_target_fp } / 4 } + \tl_gset:Nn \g_@@_code_tl + { \int_gadd:Nn \g_@@_duration_int { 0 } } + \@@_aux: + \fp_gset:Nn \g_@@_one_op_fp { max(\g_@@_time_fp, 1e-16) } + \int_gset:Nn \g_@@_duration_int + { \fp_to_int:n { 65536 * \g_benchmark_duration_target_fp } } + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\@@_fp_to_tl:N, \@@_fp_to_tl_aux:nN} +% Similar to \cs{fp_to_tl:N} but rounds to $3$ significant digits and +% uses scientific notation starting from |1e3|. +% \begin{macrocode} +\cs_new:Npn \@@_fp_to_tl:N #1 + { + \fp_compare:nTF { abs(#1) < 1000 } + { \fp_to_tl:n { round(#1, 2 - logb(#1)) } } + { + \exp_args:Nf \@@_fp_to_tl_aux:nN + { \fp_to_int:n { logb(#1) } } #1 + } + } +\cs_new:Npn \@@_fp_to_tl_aux:nN #1#2 + { \fp_to_tl:n { round(#2 * 1e-#1, 2) } e#1 } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\@@_display:} +% Function to display the time that was measured and the estimated +% number of operations. +% \begin{macrocode} +\cs_new_protected:Npn \@@_display: + { + \fp_gset:Nn \g_@@_ops_fp { \g_@@_time_fp / \g_@@_one_op_fp } + \iow_term:x + { + \@@_fp_to_tl:N \g_@@_time_fp \c_space_tl seconds \c_space_tl + ( \@@_fp_to_tl:N \g_@@_ops_fp \c_space_tl ops) + } } % \end{macrocode} % \end{macro} % % \subsection{Benchmark tic toc} % -% \begin{variable}{\g_@@_tictoc_int, \g_@@_tictoc_seq} +% \begin{variable}{\g_@@_tictoc_int, \g_@@_tictoc_seq, \l_@@_tictoc_pop_tl} % \begin{macrocode} \int_new:N \g_@@_tictoc_int \seq_new:N \g_@@_tictoc_seq +\tl_new:N \l_@@_tictoc_pop_tl % \end{macrocode} % \end{variable} % -% \begin{macro}{\benchmark_tic:, \@@_tic:} +% \begin{macro}[EXP]{\@@_tictoc_prefix:} +% We include the package name in analogy with continuation lines of +% error/warning messages. % \begin{macrocode} -\cs_new_protected:Npn \benchmark_tic: +\cs_new:Npn \@@_tictoc_prefix: { - \int_compare:nTF { \g_@@_tictoc_int == 0 } - { - \sys_gzero_timer: - \@@_tic: - } - { \@@_tic: } + (l3benchmark) \c_space_tl + + \prg_replicate:nn { \g_@@_tictoc_int } { -+ } \c_space_tl } -\cs_new_protected:Npn \@@_tic: +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\benchmark_tic:} +% \begin{macrocode} +\cs_new_protected:Npn \benchmark_tic: { - \exp_args:NNf \seq_put_right:Nn \g_@@_tictoc_seq { \sys_timer: } + \iow_term:x { \@@_tictoc_prefix: TIC } + \exp_args:NNf \seq_gput_right:Nn \g_@@_tictoc_seq { \sys_timer: } \int_gincr:N \g_@@_tictoc_int - \iow_term:x - { - \prg_replicate:nn {\g_@@_tictoc_int} {---+} \space - TIC - } } % \end{macrocode} % \end{macro} @@ -546,30 +547,26 @@ % \begin{macrocode} \cs_new:Npn \benchmark_toc: { - \int_compare:nT { \g_@@_tictoc_int == 0 } - { \msg_error:nn {benchmark} {toc-first} } - \seq_pop_right:NN \g_@@_tictoc_seq \l_@@_tictoc_pop_tl - \@@_toc: + \seq_gpop_right:NNTF \g_@@_tictoc_seq \l_@@_tictoc_pop_tl + { \@@_toc: } + { \msg_error:nn { benchmark } { toc-first } } } \cs_new_protected:Npn \@@_toc: { - \tl_set:Nx \l_@@_tictoc_tl - { - \fp_to_decimal:n - { round( ( \sys_timer: - \l_@@_tictoc_pop_tl) / 65536 , 3 ) } - } + \int_gdecr:N \g_@@_tictoc_int + \fp_gset:Nn \g_@@_time_fp + { ( \sys_timer: - \l_@@_tictoc_pop_tl ) / 65536 } \iow_term:x { - \prg_replicate:nn {\g_@@_tictoc_int} {---+} \c_space_tl + \@@_tictoc_prefix: TOC: \c_space_tl - \l_@@_tictoc_tl \c_space_tl s + \@@_fp_to_tl:N \g_@@_time_fp \c_space_tl s } - \int_gdecr:N \g_@@_tictoc_int } -\msg_new:nnn {benchmark} {toc-first} +\msg_new:nnn { benchmark } { toc-first } { - \token_to_str:N \benchmark_toc: \space without~ - \token_to_str:N \benchmark_tic: \space ! + \token_to_str:N \benchmark_toc: \c_space_tl without~ + \token_to_str:N \benchmark_tic: \c_space_tl ! } % \end{macrocode} % \end{macro} |