summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/source/latex/l3experimental/l3benchmark/l3benchmark.dtx
blob: b8e976677dae5fb8cea0fcbdac2de240438d6f84 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
% \iffalse meta-comment
%
%% File: l3benchmark.dtx Copyright (C) 2011,2012,2014-2018 The LaTeX3 Project
%
% It may be distributed and/or modified under the conditions of the
% LaTeX Project Public License (LPPL), either version 1.3c of this
% license or (at your option) any later version.  The latest version
% of this license is in the file
%
%    http://www.latex-project.org/lppl.txt
%
% This file is part of the "l3experimental bundle" (The Work in LPPL)
% and all files in that bundle must be distributed together.
%
% -----------------------------------------------------------------------
%
% The development version of the bundle can be found at
%
%    https://github.com/latex3/latex3
%
% for those people who are interested.
%
%<*driver|package>
\RequirePackage{expl3}
%</driver|package>
%<*driver>
\documentclass[full]{l3doc}
\begin{document}
  \DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
%
% \title{^^A
%   The \pkg{l3benchmark} package\\ Experimental benchmarking^^A
% }
%
% \author{^^A
%  The \LaTeX3 Project\thanks
%    {^^A
%      E-mail:
%        \href{mailto:latex-team@latex-project.org}
%          {latex-team@latex-project.org}^^A
%    }^^A
% }
%
% \date{Released 2018-10-26}
%
% \maketitle
%
% \begin{documentation}
%
% \section{Additions to \pkg{l3sys}: elapsed time}
%
% \begin{function}{\sys_gzero_timer:}
%   \begin{syntax}
%     \cs{sys_gzero_timer:}
%   \end{syntax}
%   Resets the timer to zero.
% \end{function}
%
% \begin{function}[EXP]{\sys_timer:}
%   \begin{syntax}
%     \cs{sys_timer:}
%   \end{syntax}
%   Expands to the current value of the engine's timer clock, a
%   non-negative integer.  In engines without clock support this
%   expands to $0$.
% \end{function}
%
% \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.
% \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{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.
% \end{function}
%
% \begin{function}{\benchmark:n, \benchmark_in_ops:n, \benchmark_silent: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.
% \end{function}
%
% \begin{function}{\benchmark_tic:, \benchmark_toc:}
%   \begin{syntax}
%     \cs{benchmark_tic:} \meta{slow code} \cs{benchmark_toc:}
%   \end{syntax}
%   When it is not possible to run \cs{benchmark:n} (e.g., the code is
%   part of the execution of a package which cannot be looped) the
%   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.
% \end{function}
%
% \end{documentation}
%
% \begin{implementation}
%
% \section{\pkg{l3benchmark} implementation}
%
% Our working unit is the scaled second, namely $2^{-16}$ seconds.
%
%    \begin{macrocode}
%<*initex|package>
%    \end{macrocode}
%
%    \begin{macrocode}
%<*package>
\ProvidesExplPackage{l3benchmark}{2018-10-26}{}
  {L3 Experimental benchmarking}
%</package>
%    \end{macrocode}
%
% \subsection{Additions to \pkg{l3sys}: elapsed time}
%
%    \begin{macrocode}
%<@@=sys>
%    \end{macrocode}
%
% \begin{macro}[EXP]{\sys_timer:}
% \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.
%    \begin{macrocode}
\sys_if_engine_luatex:TF
  {
    \cs_new:Npn \sys_timer:
      { \int_eval:w \lua_now:n { l3kernel.elapsedtime() } \scan_stop: }
  }
  {
    \cs_if_exist:NT \tex_elapsedtime:D
      {
        \cs_new:Npn \sys_timer:
          { \int_eval:w \tex_elapsedtime:D }
      }
  }
\@@_const:nn { sys_if_timer_exist }
  { \cs_if_exist_p:N \sys_timer: }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\sys_gzero_timer:, \@@_timer_fp:}
% \begin{variable}{\g_@@_timer_base_fp}
%   Three case, and in each case we define \cs{sys_gzero_timer:}.
%    \begin{macrocode}
\cs_if_exist:NTF \sys_timer:
  {
    \sys_if_engine_luatex:TF
      {
        \cs_new_protected:Npn \sys_gzero_timer:
          { \lua_now:n { l3kernel.resettimer() } }
      }
      { \cs_new_protected:Npn \sys_gzero_timer: { \tex_resettimer:D } }
  }
%    \end{macrocode}
%   If the elapsed time cannot be accessed then we define user
%   commands to produce errors.
%    \begin{macrocode}
  {
    \__kernel_msg_new:nnnn { kernel } { no-elapsed-time }
      { No~clock~detected~for~#1. }
      { The~current~engine~provides~no~way~to~access~the~system~time. }
    \cs_new_protected:Npn \sys_gzero_timer:
      {
        \__kernel_msg_error:nnn { kernel } { no-elapsed-time }
          { \sys_gzero_timer: }
      }
    \cs_new:Npn \sys_timer:
      {
        \int_value:w
        \__kernel_msg_expandable_error:nnn { kernel } { no-elapsed-time }
          { \sys_timer: }
        \c_zero_int
      }
  }
%    \end{macrocode}
% \end{variable}
% \end{macro}
%
% \subsection{Benchmarking code}
%
%    \begin{macrocode}
%<@@=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{macrocode}
\fp_new:N \g_benchmark_time_fp
\fp_new:N \g_benchmark_ops_fp
%    \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}
%
%    \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. }
    \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
      { \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 }
    \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}
%
% \begin{variable}{\g_@@_nesting_int}
% \begin{macro}{\@@_raw:nN, \@@_raw_aux:N, \@@_raw_end:N}
%   Store in the given integer variable the time it took to perform a given
%   piece of code, in scaled seconds.  We call \cs{sys_timer:} as
%   close before and after the code as possible.  We store the
%   intermediate result in a new integer when \cs{@@_raw:nN} is
%   nested.
%    \begin{macrocode}
\int_new:N \g_@@_nesting_int
\cs_new_protected:Npn \@@_raw:nN #1
  {
    \int_gincr:N \g_@@_nesting_int
    \exp_args:Nc \@@_raw_aux:N
      { g_@@_ \int_use:N \g_@@_nesting_int _int }
    \@@_raw_aux:
    #1
    \@@_raw_end:N
  }
\cs_new_protected:Npn \@@_raw_aux:N #1
  {
    \int_gzero_new:N #1
    \cs_gset_protected:Npn \@@_raw_aux: { \int_gset:Nn #1 { \sys_timer: } }
  }
\cs_new_protected:Npn \@@_raw_end:N #1
  {
    \int_gset:Nn #1
      {
        \sys_timer: -
        \int_use:c { g_@@_ \int_use:N \g_@@_nesting_int _int }
      }
    \int_gdecr:N \g_@@_nesting_int
  }
%    \end{macrocode}
% \end{macro}
% \end{variable}
%
% \begin{macro}{\@@_raw_replicate:nnN, \@@_tmp:w}
%   Here, we wish to measure the time it takes for the piece of code
%   |#2| to be run |#1| times, and store the result in the
%   integer~|#3|.
%
%   If the number of copies required is large, defining \cs{@@_tmp:w}
%   would exhaust \TeX{}'s main memory. In that case, we replicate
%   $|#1|/5000$ times the given code before passing it to the main call
%   to \cs{@@_tmp:w}.  Of course the division rounds to an integer, so
%   that step introduces a relative error of order at most
%   $5000/500000$, less than many other sources of variability.
%
%   We subtract the time for another call to \cs{@@_tmp:w}, with the
%   same arguments (to capture the time it takes to read the argument)
%   but empty expansion.
%    \begin{macrocode}
\cs_new_eq:NN \@@_tmp:w ?
\cs_new_protected:Npn \@@_raw_replicate:nnN #1
  {
    \int_compare:nNnTF {#1} > { 500000 }
      { \@@_raw_replicate_large:nnN {#1} }
      { \@@_raw_replicate_small:nnN {#1} }
  }
\cs_new_protected:Npn \@@_raw_replicate_large:nnN #1#2
  {
    \cs_set:Npx \@@_tmp:w ##1 { \prg_replicate:nn { 5000 } {##1} }
    \exp_args:Nno \@@_raw_replicate:nnN { #1 / 5000 }
      { \@@_tmp:w {#2} }
  }
\cs_new_protected:Npn \@@_raw_replicate_small:nnN #1#2
  {
    \cs_set:Npx \@@_tmp:w ##1##2 { \prg_replicate:nn {#1} {##1} }
    \@@_raw:nN { \@@_tmp:w {#2} { } } \g_@@_time_int
    \exp_args:No \@@_raw_replicate_aux:nnN
      { \int_use:N \g_@@_time_int } {#2}
  }
\cs_new_protected:Npn \@@_raw_replicate_aux:nnN #1#2#3
  {
    \@@_raw:nN { \@@_tmp:w { } {#2} } \g_@@_time_int
    \int_gset:Nn #3 { #1 - \g_@@_time_int }
    \cs_set_eq:NN \@@_tmp:w \prg_do_nothing:
  }
%    \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.
%    \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:
  {
    \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 } }
  }
\cs_new_protected:Npn \benchmark_silent: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 }
  }
\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 }
      {
        \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_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 }
          {
            \g_@@_duration_int * \g_@@_repeat_int
            / ( \g_@@_time_int * 4 )
          }
      }
    \int_compare:nNnTF \g_@@_repeat_int = 1
      { \int_gset_eq:NN \g_@@_time_a_int \g_@@_time_int }
      { \@@_run:N \g_@@_time_a_int }
    \@@_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_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
      }
  }
\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.
%    \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
  {
    \@@_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 }
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Benchmark tic toc}
%
% \begin{variable}{\g_@@_tictoc_int, \g_@@_tictoc_seq}
%    \begin{macrocode}
\int_new:N \g_@@_tictoc_int
\seq_new:N \g_@@_tictoc_seq
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\benchmark_tic:, \@@_tic:}
%    \begin{macrocode}
\cs_new_protected:Npn \benchmark_tic:
  {
    \int_compare:nTF { \g_@@_tictoc_int == 0 }
      {
        \sys_gzero_timer:
        \@@_tic:n { 0 }
      }
      { \@@_tic: }
  }
\cs_new_protected:Npn \@@_tic:
  {
    \exp_args:NNf \seq_put_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}
%
% \begin{macro}{\benchmark_toc:, \@@_toc:}
%    \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:
  }
\cs_new_protected:Npn \@@_toc:
  {
    \tl_set:Nx \l_@@_tictoc_tl
      {
        \fp_to_decimal:n
          { round( ( \sys_timer: - \l_@@_tictoc_pop_tl) / 65536 , 3 ) }
      }
    \iow_term:x
      {
        \prg_replicate:nn {\g_@@_tictoc_int} {---+} \c_space_tl
        TOC: \c_space_tl
        \l_@@_tictoc_tl \c_space_tl s
      }
    \int_gdecr:N \g_@@_tictoc_int
  }
\msg_new:nnn {benchmark} {toc-first}
  {
    \token_to_str:N \benchmark_toc: \space without~
    \token_to_str:N \benchmark_tic: \space !
  }
%    \end{macrocode}
% \end{macro}
%
%    \begin{macrocode}
%</initex|package>
%    \end{macrocode}
%
% \end{implementation}
%
% \PrintIndex