Skip to content

Commit 2dd19b2

Browse files
committed
added "mypy stack size" experiment
1 parent c458d47 commit 2dd19b2

3 files changed

Lines changed: 98 additions & 10 deletions

File tree

README.md

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ Here is one possible script (also found in `typing_machines/app.py`):
1818

1919
```python
2020
from typing_machines.app import * # import application
21-
2221
with open("example.py", "w") as python_file: # write palindromes machine and input "abbabba"
2322
python_file.write(encode(Algorithm.Grigore, palindromes, "abbabba"))
2423
sleep(1) # wait for write operation
@@ -45,16 +44,9 @@ makes `mypy` throw a segmentation fault:
4544

4645
```python
4746
from typing import TypeVar, Generic
48-
4947
T = TypeVar("T", contravariant=True)
50-
51-
5248
class N(Generic[T]): ...
53-
54-
5549
class C(N[N["C"]]): ...
56-
57-
5850
_: N[C] = C()
5951
```
6052

@@ -64,5 +56,5 @@ certain programs is unavoidable.
6456
## What's new?
6557

6658
We introduce an alternative construction that is supposed to compile much faster for large inputs. You can try the new
67-
construction by typing `R` instead of
68-
`G` in the first argument to `app.py`.
59+
construction by using `Algorithm.Roth` instead of
60+
`Algorithm.Grigore` in the script above.

typing_machines/experiment/__init__.py

Whitespace-only changes.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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

Comments
 (0)