|
from argparse import ArgumentParser |
|
from pathlib import Path |
|
import random |
|
import string |
|
from textwrap import dedent |
|
|
|
def random_string(length: int) -> str: |
|
return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(length)) |
|
|
|
def generate_if_else_func(name: str, n: int, string_length: int) -> str: |
|
header = dedent( |
|
f""" |
|
func {name}(expected string) int {{ |
|
res := 0 |
|
""" |
|
) |
|
|
|
body = " " * 4 |
|
|
|
cases = sorted(set(random_string(string_length) for _ in range(n))) |
|
|
|
for i, test_case in enumerate(cases): |
|
body += f'''if expected == "{test_case}" {{ res = {random.randint(0, 2 ** 16)} }}''' |
|
if not i == len(cases) - 1: |
|
body += " else\n " |
|
|
|
footer = dedent( |
|
f""" |
|
return res |
|
}} |
|
""" |
|
) |
|
return header + body + footer |
|
|
|
def generate_preallocated_map_func(name: str, n: int, string_length: int) -> str: |
|
map_header = dedent( |
|
f""" |
|
var {name}_lookup = map[string]int {{ |
|
""" |
|
) |
|
cases = sorted(set(random_string(string_length) for _ in range(n))) |
|
|
|
map_body = "".join( |
|
f' "{test_case}": {random.randint(0, 2 ** 16)},\n' for i, test_case in enumerate(cases) |
|
) |
|
|
|
map_footer = dedent( |
|
""" |
|
} |
|
""" |
|
) |
|
|
|
header = dedent( |
|
f""" |
|
func {name}(expected string) int {{ |
|
""" |
|
) |
|
|
|
footer = dedent( |
|
f""" |
|
if res, found := {name}_lookup[expected]; found {{ |
|
return res |
|
}} |
|
return 0 |
|
}} |
|
""" |
|
) |
|
|
|
return map_header + map_body + map_footer + header + footer |
|
|
|
def generate_map_func(name: str, n: int, string_length: int) -> str: |
|
header = dedent( |
|
f""" |
|
func {name}(expected string) int {{ |
|
lookup := map[string]int{{ |
|
""" |
|
) |
|
|
|
cases = sorted(set(random_string(string_length) for _ in range(n))) |
|
|
|
body = "".join( |
|
f' "{test_case}": {random.randint(0, 2 ** 16)},\n' for i, test_case in enumerate(cases) |
|
) |
|
|
|
footer = dedent( |
|
f""" |
|
}} |
|
|
|
if res, found := lookup[expected]; found {{ |
|
return res |
|
}} |
|
return 0 |
|
}} |
|
""" |
|
) |
|
|
|
return header + body + footer |
|
|
|
def generate_switch_case_func(name: str, n: int, string_length: int) -> str: |
|
header = dedent( |
|
f""" |
|
func {name}(expected string) int {{ |
|
switch expected {{ |
|
""" |
|
) |
|
|
|
cases = sorted(set(random_string(string_length) for _ in range(n))) |
|
|
|
body = "".join( |
|
f' case "{test_case}": return {random.randint(0, 2 ** 16)}\n' for i, test_case in enumerate(cases) |
|
) |
|
|
|
footer = dedent( |
|
f""" |
|
}} |
|
return 0 |
|
}} |
|
""" |
|
) |
|
|
|
return header + body + footer |
|
|
|
def generate_bench_func(name: str, string_length: int) -> str: |
|
return dedent( |
|
f""" |
|
func Benchmark{name}(b *testing.B) {{ |
|
pattern := RandomString({string_length}) |
|
for b.Loop() {{ |
|
{name}(pattern) |
|
}} |
|
}} |
|
""" |
|
) |
|
|
|
def generate_main_go(n_cases: list[int], string_lengths: list[int], seed: int = 42) -> str: |
|
funcs = [] |
|
for n in n_cases: |
|
for length in string_lengths: |
|
random.seed(seed) |
|
if_else_func = generate_if_else_func(name=f"MatchIfElse_{n}_{length}", n=n, string_length=length) |
|
random.seed(seed) |
|
switch_case_func = generate_switch_case_func(name=f"MatchSwitchCase_{n}_{length}", n=n, string_length=length) |
|
random.seed(seed) |
|
map_func = generate_map_func(name=f"MatchMap_{n}_{length}", n=n, string_length=length) |
|
random.seed(seed) |
|
map_func_prealloc = generate_preallocated_map_func(name=f"MatchMapPrealloc_{n}_{length}", n=n, string_length=length) |
|
funcs += [if_else_func, switch_case_func, map_func, map_func_prealloc] |
|
header = dedent( |
|
""" |
|
package main |
|
|
|
""" |
|
) |
|
body = "\n\n".join(funcs) |
|
|
|
footer = dedent( |
|
""" |
|
func main(){} |
|
""" |
|
) |
|
return header + body + footer |
|
|
|
def generate_code_file(output_dir: Path, n_cases: list[int], string_lengths: list[int], seed: int = 42) -> None: |
|
code = generate_main_go(n_cases, string_lengths, seed) |
|
output_dir.mkdir(exist_ok=True, parents=True) |
|
file = (output_dir/"main.go") |
|
file.write_text(code) |
|
print(f"Created {file}") |
|
|
|
|
|
def generate_main_test_go(n_cases: list[int], string_lengths: list[int], seed: int = 42): |
|
funcs = [] |
|
for n in n_cases: |
|
for length in string_lengths: |
|
random.seed(seed) |
|
if_else_func = generate_bench_func(name=f"MatchIfElse_{n}_{length}", string_length=length) |
|
random.seed(seed) |
|
switch_case_func = generate_bench_func(name=f"MatchSwitchCase_{n}_{length}", string_length=length) |
|
random.seed(seed) |
|
map_func = generate_bench_func(name=f"MatchMap_{n}_{length}", string_length=length) |
|
random.seed(seed) |
|
map_func_prealloc = generate_bench_func(name=f"MatchMapPrealloc_{n}_{length}", string_length=length) |
|
funcs += [if_else_func, switch_case_func, map_func, map_func_prealloc] |
|
|
|
header = dedent( |
|
""" |
|
package main |
|
|
|
import ( |
|
"testing" |
|
"math/rand" |
|
"time" |
|
) |
|
|
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" |
|
|
|
func RandomString(length int) string { |
|
seededRand := rand.New(rand.NewSource(time.Now().UnixNano())) |
|
b := make([]byte, length) |
|
for i := range b { |
|
b[i] = charset[seededRand.Intn(len(charset))] |
|
} |
|
return string(b) |
|
} |
|
""" |
|
) |
|
|
|
body = "\n\n".join(funcs) |
|
|
|
return header + body |
|
|
|
def generate_test_file(output_dir: Path, n_cases: list[int], string_lengths: list[int], seed: int = 42) -> None: |
|
code = generate_main_test_go(n_cases, string_lengths, seed) |
|
output_dir.mkdir(exist_ok=True, parents=True) |
|
file = (output_dir/"main_test.go") |
|
file.write_text(code) |
|
print(f"Created {file}") |
|
|
|
def generate_go_mod_file(output_dir: Path) -> None: |
|
code = dedent( |
|
""" |
|
module benchmarking-matching |
|
|
|
go 1.24.1 |
|
""" |
|
).strip() |
|
output_dir.mkdir(exist_ok=True, parents=True) |
|
file = (output_dir/"go.mod") |
|
file.write_text(code) |
|
print(f"Created {file}") |
|
|
|
def generate_golang_files(output_dir: Path, n_cases: list[int], string_lengths: list[int], seed: int = 42) -> None: |
|
generate_go_mod_file(output_dir) |
|
generate_code_file(output_dir, n_cases=n_cases, string_lengths=string_lengths, seed=seed) |
|
generate_test_file(output_dir, n_cases=n_cases, string_lengths=string_lengths, seed=seed) |
|
|
|
if __name__ == '__main__': |
|
parser = ArgumentParser() |
|
parser.add_argument("-d", "--dir", type=Path) |
|
parser.add_argument("-n", "--num_cases", type=str, help="comma delimited list of number of cases in a match function") |
|
parser.add_argument("-l", "--string_lengths", type=str, help="comma delimited list of string lengths in a match function") |
|
|
|
args = parser.parse_args() |
|
|
|
generate_golang_files( |
|
output_dir=args.dir, |
|
n_cases=[int(n) for n in args.num_cases.split(',')], |
|
string_lengths=[int(n) for n in args.string_lengths.split(',')], |
|
) |