|
| 1 | +from os import remove |
| 2 | +from random import Random |
| 3 | +from resource import RLIMIT_STACK, setrlimit |
| 4 | +from subprocess import Popen, DEVNULL |
| 5 | +from time import sleep |
| 6 | +from typing import Callable, List, Tuple, Iterable |
| 7 | + |
| 8 | +import matplotlib.pyplot as plt |
| 9 | + |
| 10 | +from typing_machines.app import encode, Algorithm |
| 11 | +from typing_machines.examples.machines import palindromes |
| 12 | + |
| 13 | + |
| 14 | +def binary_search(le: Callable[[int], bool]) -> int: |
| 15 | + """ |
| 16 | + Finds a natural number according to the given "lower equals" predicate. |
| 17 | + Returns -1 if no number is found. |
| 18 | + """ |
| 19 | + l: int = -1 |
| 20 | + u: int = 1 |
| 21 | + while le(u): |
| 22 | + l = u |
| 23 | + u *= 2 |
| 24 | + u -= 1 |
| 25 | + while l < u: |
| 26 | + m: int = (l + u) // 2 + (l + u) % 2 |
| 27 | + if le(m): |
| 28 | + l = m |
| 29 | + else: |
| 30 | + u = m - 1 |
| 31 | + return l |
| 32 | + |
| 33 | + |
| 34 | +def get_random_palindrome(n: int) -> str: |
| 35 | + """ |
| 36 | + Returns a random palindrome over {a, b} of length n. |
| 37 | + Always returns the same word for a given n. |
| 38 | + """ |
| 39 | + random: Random = Random(n) |
| 40 | + palindrome: str = "" |
| 41 | + while n > 0: |
| 42 | + l: str = "a" if random.getrandbits(1) else "b" |
| 43 | + palindrome = l + palindrome + l |
| 44 | + n -= 1 |
| 45 | + return palindrome |
| 46 | + |
| 47 | + |
| 48 | +def get_stack_size(algorithm: Algorithm, input_word: str) -> int: |
| 49 | + """ |
| 50 | + Get the call stack size mypy requires to compile the palindromes typing machine with the given |
| 51 | + algorithm and input palindrome. |
| 52 | + """ |
| 53 | + |
| 54 | + def compiles(n: int) -> bool: |
| 55 | + with open("test.py", "w") as python_file: |
| 56 | + python_file.write(encode(algorithm, palindromes, input_word)) |
| 57 | + sleep(1) |
| 58 | + stack_size: int = (n + 5) * 1000000 |
| 59 | + with Popen(["mypy", "test.py"], preexec_fn=lambda: setrlimit(RLIMIT_STACK, (stack_size, stack_size)), |
| 60 | + stdout=DEVNULL, stderr=DEVNULL) as p: |
| 61 | + retcode = p.wait(timeout=10) |
| 62 | + remove("test.py") |
| 63 | + return retcode != 0 |
| 64 | + |
| 65 | + depth: int = binary_search(compiles) |
| 66 | + depth = 5 if depth == -1 else depth + 5 |
| 67 | + return depth |
| 68 | + |
| 69 | + |
| 70 | +def run_experiment(algorithm: Algorithm, input_lengths: Iterable[int]) -> List[Tuple[int, int]]: |
| 71 | + """ |
| 72 | + Find mypy stack sizes for given algorithm and input lengths. |
| 73 | + """ |
| 74 | + results: List[Tuple[int, int]] = [] |
| 75 | + for n in input_lengths: |
| 76 | + s: int = get_stack_size(algorithm, get_random_palindrome(n)) |
| 77 | + results.append((n * 2, s)) |
| 78 | + print(f"mypy requires {s}M stack size with algorithm {algorithm.name} and palindrome of length {n * 2}") |
| 79 | + return results |
| 80 | + |
| 81 | + |
| 82 | +if __name__ == '__main__': |
| 83 | + grigore_results: List[Tuple[int, int]] = run_experiment(Algorithm.Grigore, range(5, 9)) |
| 84 | + print("Grigore's results:") |
| 85 | + for n, s in grigore_results: |
| 86 | + print(f"Grigore\t{n}\t{s}") |
| 87 | + roth_results: List[Tuple[int, int]] = run_experiment(Algorithm.Roth, range(5, 46, 5)) |
| 88 | + print("Roth's results:") |
| 89 | + for n, s in roth_results: |
| 90 | + print(f"Roth\t{n}\t{s}") |
| 91 | + plt.plot([n for n, _ in grigore_results], [s for _, s in grigore_results], color="blue", label="Grigore") |
| 92 | + plt.scatter([n for n, _ in grigore_results], [s for _, s in grigore_results], color="blue", marker="o") |
| 93 | + plt.plot([n for n, _ in roth_results], [s for _, s in roth_results], color="green", label="Roth") |
| 94 | + plt.scatter([n for n, _ in roth_results], [s for _, s in roth_results], color="green", marker="x") |
| 95 | + plt.legend(loc="upper right") |
| 96 | + plt.show() |
0 commit comments