Skip to content

Commit 8f7c83c

Browse files
committed
add benchmarking against pyvsc
Between 12x and 50x faster than pyvsc, depending on testcase
1 parent 03e86d0 commit 8f7c83c

File tree

8 files changed

+260
-3
lines changed

8 files changed

+260
-3
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -361,17 +361,17 @@ The user is free to either inherit from `RandObj` (for more complex cases like t
361361
In local testing, the instruction above can be randomized at a rate of approximately 2000Hz, which is 'fast enough' for most pre-silicon verification simulations.
362362

363363
## `TODO`
364-
- Add meaningful benchmarking against similar libraries (mainly [`pyvsc`](https://github.com/fvutils/pyvsc).)
365364
- Add equivalent SystemVerilog testcases for benchmarking.
366365
- Add type hinting.
367366

368367
## Contributions
369368

370369
Please feel free to contribute to the project, following these guidelines:
371370
- Please contribute by creating a fork and submitting a pull request.
372-
- Pull requests should be as small as possible for what issue they are trying to address.
371+
- Pull requests should be as small as possible to resolve the issue they are trying to address.
373372
- Pull requests must respect the goals of the library, as stated above.
374-
- Pull requests should pass all the tests in the `tests/` directory.
373+
- Pull requests should pass all the tests in the `tests/` directory. Run `python -m tests`.
374+
- Pull requests should take care not to make performance worse except for cases which require bug fixes. Run `python -m tests` and `python -m benchmarks`.
375375

376376
## Contact the author(s)
377377

benchmarks/__init__.py

Whitespace-only changes.

benchmarks/__main__.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# SPDX-License-Identifier: MIT
2+
# Copyright (c) 2023 Imagination Technologies Ltd. All Rights Reserved
3+
4+
'''
5+
Benchmark against pyvsc library for equivalent testcases.
6+
'''
7+
8+
import unittest
9+
import timeit
10+
import sys
11+
12+
from argparse import ArgumentParser
13+
14+
from constrainedrandom import Random
15+
from benchmarks.pyvsc.basic import vsc_basic, cr_basic
16+
from benchmarks.pyvsc.in_keyword import vsc_in, cr_in, cr_in_order
17+
from benchmarks.pyvsc.ldinstr import vsc_ldinstr
18+
from examples.ldinstr import ldInstr
19+
20+
21+
22+
TEST_LENGTH_MULTIPLIER = 1
23+
24+
25+
class BenchmarkTests(unittest.TestCase):
26+
27+
def run_one(self, name, randobj, iterations):
28+
'''
29+
Benchmark one object that implements the .randomize() function
30+
for N iterations.
31+
'''
32+
start_time = timeit.default_timer()
33+
for _ in range(iterations):
34+
randobj.randomize()
35+
end_time = timeit.default_timer()
36+
total_time = end_time - start_time
37+
hz = iterations / total_time
38+
print(f'{self._testMethodName}: {name} took {total_time:.4g}s for {iterations} iterations ({hz:.1f}Hz)')
39+
return total_time, hz
40+
41+
def run_benchmark(self, randobjs, iterations, check):
42+
'''
43+
Reusable function to run a fair benchmark between
44+
two or more randomizable objects.
45+
46+
randobjs: dictionary where key is name, value is an object that implements .randomize()
47+
iterations: how many times to call .randomize()
48+
check: function taking a dictionary of results to check.
49+
'''
50+
iterations *= TEST_LENGTH_MULTIPLIER
51+
results = {}
52+
winner = None
53+
best_hz = 0
54+
for name, randobj in randobjs.items():
55+
total_time, hz = self.run_one(name, randobj, iterations)
56+
if hz > best_hz:
57+
winner = name
58+
best_hz = hz
59+
# Store both total time and Hz in case the test wants to specify
60+
# checks on how long should be taken in wall clock time.
61+
results[name] = total_time, hz
62+
print(f'{self._testMethodName}: The winner is {winner} with {best_hz:.1f}Hz!')
63+
# Print summary of hz delta
64+
for name, (_total_time, hz) in results.items():
65+
if name == winner:
66+
continue
67+
speedup = best_hz / hz
68+
print(f'{self._testMethodName}: {winner} was {speedup:.2f}x faster than {name}')
69+
check(results)
70+
71+
def test_basic(self):
72+
'''
73+
Test basic randomizable object.
74+
'''
75+
randobjs = {'vsc': vsc_basic(), 'cr': cr_basic(Random(0))}
76+
def check(results):
77+
self.assertGreater(results['cr'][1], results['vsc'][1])
78+
self.run_benchmark(randobjs, 100, check)
79+
80+
def test_in(self):
81+
'''
82+
Test object using 'in' keyword.
83+
'''
84+
randobjs = {
85+
'vsc': vsc_in(),
86+
'cr': cr_in(Random(0)),
87+
'cr_order': cr_in_order(Random(0)),
88+
}
89+
def check(results):
90+
self.assertGreater(results['cr'][1], results['vsc'][1])
91+
self.run_benchmark(randobjs, 100, check)
92+
93+
def test_ldinstr(self):
94+
'''
95+
Test LD instruction example.
96+
'''
97+
randobjs = {
98+
'vsc': vsc_ldinstr(),
99+
'cr': ldInstr(Random(0)),
100+
}
101+
def check(results):
102+
self.assertGreater(results['cr'][1], results['vsc'][1])
103+
self.run_benchmark(randobjs, 100, check)
104+
105+
106+
def parse_args():
107+
parser = ArgumentParser(description='Run unit tests for constrainedrandom library')
108+
parser.add_argument('--length-mul', type=int, default=1, help='Multiplier for test length, when desiring greater certainty on performance.')
109+
args, extra = parser.parse_known_args()
110+
return args, extra
111+
112+
113+
if __name__ == "__main__":
114+
args, extra = parse_args()
115+
TEST_LENGTH_MULTIPLIER = args.length_mul
116+
# Reconstruct argv
117+
argv = [sys.argv[0]] + extra
118+
unittest.main(argv=argv)

benchmarks/pyvsc/__init__.py

Whitespace-only changes.

benchmarks/pyvsc/basic.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# SPDX-License-Identifier: MIT
2+
# Copyright (c) 2023 Imagination Technologies Ltd. All Rights Reserved
3+
4+
'''
5+
Basic random object from pyvsc documentation.
6+
'''
7+
8+
from constrainedrandom import RandObj
9+
import vsc
10+
11+
12+
@vsc.randobj
13+
class vsc_basic(object):
14+
15+
def __init__(self):
16+
self.a = vsc.rand_bit_t(8)
17+
self.b = vsc.rand_bit_t(8)
18+
self.c = vsc.rand_bit_t(8)
19+
self.d = vsc.rand_bit_t(8)
20+
21+
@vsc.constraint
22+
def ab_c(self):
23+
self.a < self.b
24+
25+
26+
class cr_basic(RandObj):
27+
28+
def __init__(self, random):
29+
super().__init__(random)
30+
self.add_rand_var('a', bits=8)
31+
self.add_rand_var('b', bits=8, order=1)
32+
self.add_rand_var('c', bits=8)
33+
self.add_rand_var('d', bits=8)
34+
35+
self.add_multi_var_constraint(lambda a, b : a < b, ('a', 'b'))

benchmarks/pyvsc/in_keyword.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# SPDX-License-Identifier: MIT
2+
# Copyright (c) 2023 Imagination Technologies Ltd. All Rights Reserved
3+
4+
'''
5+
Random object using 'in' keyword from pyvsc documentation.
6+
'''
7+
8+
from constrainedrandom import RandObj
9+
import vsc
10+
11+
12+
@vsc.randobj
13+
class vsc_in(object):
14+
15+
def __init__(self):
16+
self.a = vsc.rand_bit_t(8)
17+
self.b = vsc.rand_bit_t(8)
18+
self.c = vsc.rand_bit_t(8)
19+
self.d = vsc.rand_bit_t(8)
20+
21+
@vsc.constraint
22+
def ab_c(self):
23+
24+
self.a in vsc.rangelist(1, 2, vsc.rng(4,8))
25+
self.c != 0
26+
self.d != 0
27+
28+
self.c < self.d
29+
self.b in vsc.rangelist(vsc.rng(self.c,self.d))
30+
31+
32+
class cr_in(RandObj):
33+
'''
34+
Basic implementation, does thes same thing as vsc_in. No ordering hints.
35+
'''
36+
37+
def __init__(self, random):
38+
super().__init__(random)
39+
40+
self.add_rand_var('a', domain=[1,2] + list(range(4,8)))
41+
self.add_rand_var('b', bits=8, constraints=(lambda b : b != 0))
42+
self.add_rand_var('c', bits=8)
43+
self.add_rand_var('d', bits=8, constraints=(lambda d : d != 0))
44+
45+
def c_lt_d(c, d):
46+
return c < d
47+
self.add_multi_var_constraint(c_lt_d, ('c', 'd'))
48+
49+
def b_in_range(b, c, d):
50+
return b in range(c, d)
51+
self.add_multi_var_constraint(b_in_range, ('b', 'c', 'd'))
52+
53+
54+
class cr_in_order(RandObj):
55+
'''
56+
cr_in, but with ordering hints.
57+
'''
58+
59+
def __init__(self, random):
60+
super().__init__(random)
61+
62+
self.add_rand_var('a', domain=[1,2] + list(range(4,8)), order=0)
63+
self.add_rand_var('b', bits=8, constraints=(lambda b : b != 0), order=2)
64+
self.add_rand_var('c', bits=8, order=0)
65+
self.add_rand_var('d', bits=8, constraints=(lambda d : d != 0), order=1)
66+
67+
def c_lt_d(c, d):
68+
return c < d
69+
self.add_multi_var_constraint(c_lt_d, ('c', 'd'))
70+
71+
def b_in_range(b, c, d):
72+
return b in range(c, d)
73+
self.add_multi_var_constraint(b_in_range, ('b', 'c', 'd'))

benchmarks/pyvsc/ldinstr.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# SPDX-License-Identifier: MIT
2+
# Copyright (c) 2023 Imagination Technologies Ltd. All Rights Reserved
3+
4+
'''
5+
Implement realistic load instruction case from constrainedrandom examples.ldinstr
6+
'''
7+
8+
import vsc
9+
10+
@vsc.randobj
11+
class vsc_ldinstr(object):
12+
13+
def __init__(self):
14+
self.imm0 = vsc.rand_bit_t(11)
15+
self.src0 = vsc.rand_bit_t(5)
16+
self.dst0 = vsc.rand_bit_t(5)
17+
self.wb = vsc.rand_bit_t(1)
18+
self.enc = 0xfa800000
19+
# Make this the same as in examples.ldinstr
20+
self.src0_value_getter = lambda : 0xfffffbcd
21+
22+
@vsc.constraint
23+
def wb_src0_dst0(self):
24+
with vsc.if_then(self.wb == 1):
25+
self.src0 != self.dst0
26+
27+
@vsc.constraint
28+
def sum_src0_imm0(self):
29+
self.imm0 + self.src0_value_getter() <= 0xffffffff
30+
(self.imm0 + self.src0_value_getter()) & 3 == 0

tests/__main__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from constrainedrandom import Random, RandObj
1313
from examples.ldinstr import ldInstr
1414

15+
1516
TEST_LENGTH_MULTIPLIER = 1
1617

1718

0 commit comments

Comments
 (0)