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 Support for measuring the speed of different parsers and configurations.
32 '''
33
34 from time import time
35 from sys import stdout
36 from gc import collect
37
38 from lepl.support.lib import fmt
39
40
41 DEFAULT = 'default'
42
43
44 -def print_timing(input, matchers, count=10, count_compile=None, best_of=3,
45 parse_all=False, out=None, reference=None):
46 '''
47 Generate timing information for the given input and parsers.
48
49 `input` can be a sequence or a function that generates sequences (this is
50 useful if you need to subvert caching). Note that a function is evaluated
51 once before the timing starts.
52
53 `matchers` is a dict that maps names to matchers. The names are used in
54 the output. Alternatively, a single matcher can be given (which will be
55 called "default"). A typical use might look like:
56 matcher = ....
57 time("...", {'default': matcher.clone().config.default().matcher,
58 'clear': matcher.clone().config.clear().matcher})
59
60 `count` is the number of parses to make when measuring a single time. This
61 is to make sure that the time taken is long enough to measure accurately
62 (times less that 10ms or so will be imprecise). The final time reported
63 is adjusted to be for a single parse, no matter what the value of `count`.
64 By default 10 matches are made.
65
66 `count_compile` allows a different `count` value for timing when the
67 compiler parser is not re-used. By default this is the same as `count`.
68
69 `best_of` repeats the test this many times and takes the shortest result.
70 This corrects for any slow-down caused by other programs running.
71
72 `parse_all`, if True, evaluates all parses through backtracking (otherwise
73 a single parse is made)
74
75 `out` is the destination for the printing (stdout by default).
76
77 `reference` names the matcher against which others are compared. By
78 default any matcher called "default" will be used; otherwise the first
79 matcher when sorted alphabetically is used.
80 '''
81 try:
82 input()
83 source = input
84 except TypeError:
85 source = lambda: input
86 if not isinstance(matchers, dict):
87 matchers = {DEFAULT: matchers}
88 if out is None:
89 out = stdout
90 if count_compile is None:
91 count_compile = count
92
93
94 def prnt(msg='', end='\n'):
95 out.write(msg + end)
96
97 prnt('\n\nTiming Results (ms)')
98 prnt('-------------------')
99 prnt(fmt('\nCompiling: best of {1:d} averages over {0:d} repetition(s)',
100 count_compile, best_of))
101 prnt(fmt('Parse only: best of {1:d} averages over {0:d} repetition(s)',
102 count, best_of))
103 prnt('\n Matcher Compiling | Parse only')
104 prnt('-----------------------------------------+------------------')
105
106 references = [None, None]
107 names = sorted(matchers.keys())
108 if not reference:
109 if DEFAULT in names:
110 reference = DEFAULT
111 else:
112 reference = names[0]
113 assert reference in names, 'Reference must be in names'
114 names = [reference] + [name for name in names if name != reference]
115
116 for name in names:
117 prnt(fmt('{0:>20s} ', name), end='')
118 matcher = matchers[name]
119 for (compile, n, end, r) in ((True, count_compile, ' |', 0),
120 (False, count, '\n', 1)):
121 times = []
122 for i in range(best_of):
123 collect()
124 t = time()
125 for j in range(n):
126 if compile:
127 matcher.config.clear_cache()
128 if parse_all:
129 list(matcher.parse_all(source()))
130 else:
131 matcher.parse(source())
132 times.append(time() - t)
133
134 times.sort()
135 best = 1000 * times[0] / float(n)
136 prnt(fmt('{0:7.2f}', best), end='')
137
138 ref = references[r]
139 if ref is None:
140 references[r] = best
141 prnt(' ', end=end)
142 elif ref:
143 ratio = best / ref
144 prnt(fmt(' (x {0:5.1f})', ratio), end=end)
145 else:
146 prnt(' (x -----)', end=end)
147
148 prnt()
149