Skip to content

Commit e97100e

Browse files
Copilotmmcky
andcommitted
Migrate timing code from tic/toc to Timer context manager
Co-authored-by: mmcky <8263752+mmcky@users.noreply.github.com>
1 parent 0c5b754 commit e97100e

File tree

2 files changed

+114
-26
lines changed

2 files changed

+114
-26
lines changed

lectures/numba.md

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,52 @@ import quantecon as qe
4141
import matplotlib.pyplot as plt
4242
```
4343

44+
```{code-cell} ipython3
45+
# Temporary fallback for Timer until quantecon is updated
46+
# This code will be removed once the new quantecon version is released
47+
import time
48+
49+
if not hasattr(qe, 'Timer'):
50+
class Timer:
51+
def __init__(self, message="", precision=2, unit="seconds", silent=False):
52+
self.message = message
53+
self.precision = precision
54+
self.unit = unit.lower()
55+
self.silent = silent
56+
self.elapsed = None
57+
self._start_time = None
58+
59+
def __enter__(self):
60+
self._start_time = time.time()
61+
return self
62+
63+
def __exit__(self, exc_type, exc_val, exc_tb):
64+
end_time = time.time()
65+
self.elapsed = end_time - self._start_time
66+
67+
if not self.silent:
68+
# Convert to requested unit
69+
if self.unit == "milliseconds":
70+
elapsed_display = self.elapsed * 1000
71+
unit_str = "ms"
72+
elif self.unit == "microseconds":
73+
elapsed_display = self.elapsed * 1000000
74+
unit_str = "μs"
75+
else: # seconds
76+
elapsed_display = self.elapsed
77+
unit_str = "seconds"
78+
79+
# Format the message
80+
if self.message:
81+
prefix = f"{self.message}: "
82+
else:
83+
prefix = ""
84+
85+
print(f"{prefix}{elapsed_display:.{self.precision}f} {unit_str} elapsed")
86+
87+
qe.Timer = Timer
88+
```
89+
4490
## Overview
4591

4692
In an {doc}`earlier lecture <need_for_speed>` we learned about vectorization, which is one method to improve speed and efficiency in numerical work.
@@ -133,17 +179,17 @@ Let's time and compare identical function calls across these two versions, start
133179
```{code-cell} ipython3
134180
n = 10_000_000
135181
136-
qe.tic()
137-
qm(0.1, int(n))
138-
time1 = qe.toc()
182+
with qe.Timer(silent=True) as timer1:
183+
qm(0.1, int(n))
184+
time1 = timer1.elapsed
139185
```
140186

141187
Now let's try qm_numba
142188

143189
```{code-cell} ipython3
144-
qe.tic()
145-
qm_numba(0.1, int(n))
146-
time2 = qe.toc()
190+
with qe.Timer(silent=True) as timer2:
191+
qm_numba(0.1, int(n))
192+
time2 = timer2.elapsed
147193
```
148194

149195
This is already a very large speed gain.
@@ -153,9 +199,9 @@ In fact, the next time and all subsequent times it runs even faster as the funct
153199
(qm_numba_result)=
154200

155201
```{code-cell} ipython3
156-
qe.tic()
157-
qm_numba(0.1, int(n))
158-
time3 = qe.toc()
202+
with qe.Timer(silent=True) as timer3:
203+
qm_numba(0.1, int(n))
204+
time3 = timer3.elapsed
159205
```
160206

161207
```{code-cell} ipython3
@@ -639,9 +685,8 @@ This is (approximately) the right output.
639685
Now let's time it:
640686

641687
```{code-cell} ipython3
642-
qe.tic()
643-
compute_series(n)
644-
qe.toc()
688+
with qe.Timer():
689+
compute_series(n)
645690
```
646691

647692
Next let's implement a Numba version, which is easy
@@ -660,9 +705,8 @@ print(np.mean(x == 0))
660705
Let's see the time
661706

662707
```{code-cell} ipython3
663-
qe.tic()
664-
compute_series_numba(n)
665-
qe.toc()
708+
with qe.Timer():
709+
compute_series_numba(n)
666710
```
667711

668712
This is a nice speed improvement for one line of code!

lectures/numpy.md

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,52 @@ from mpl_toolkits.mplot3d.axes3d import Axes3D
6363
from matplotlib import cm
6464
```
6565

66+
```{code-cell} python3
67+
# Temporary fallback for Timer until quantecon is updated
68+
# This code will be removed once the new quantecon version is released
69+
import time
70+
71+
if not hasattr(qe, 'Timer'):
72+
class Timer:
73+
def __init__(self, message="", precision=2, unit="seconds", silent=False):
74+
self.message = message
75+
self.precision = precision
76+
self.unit = unit.lower()
77+
self.silent = silent
78+
self.elapsed = None
79+
self._start_time = None
80+
81+
def __enter__(self):
82+
self._start_time = time.time()
83+
return self
84+
85+
def __exit__(self, exc_type, exc_val, exc_tb):
86+
end_time = time.time()
87+
self.elapsed = end_time - self._start_time
88+
89+
if not self.silent:
90+
# Convert to requested unit
91+
if self.unit == "milliseconds":
92+
elapsed_display = self.elapsed * 1000
93+
unit_str = "ms"
94+
elif self.unit == "microseconds":
95+
elapsed_display = self.elapsed * 1000000
96+
unit_str = "μs"
97+
else: # seconds
98+
elapsed_display = self.elapsed
99+
unit_str = "seconds"
100+
101+
# Format the message
102+
if self.message:
103+
prefix = f"{self.message}: "
104+
else:
105+
prefix = ""
106+
107+
print(f"{prefix}{elapsed_display:.{self.precision}f} {unit_str} elapsed")
108+
109+
qe.Timer = Timer
110+
```
111+
66112
(numpy_array)=
67113
## NumPy Arrays
68114

@@ -1636,9 +1682,8 @@ np.random.seed(123)
16361682
x = np.random.randn(1000, 100, 100)
16371683
y = np.random.randn(100)
16381684
1639-
qe.tic()
1640-
B = x / y
1641-
qe.toc()
1685+
with qe.Timer("Broadcasting operation"):
1686+
B = x / y
16421687
```
16431688

16441689
Here is the output
@@ -1696,14 +1741,13 @@ np.random.seed(123)
16961741
x = np.random.randn(1000, 100, 100)
16971742
y = np.random.randn(100)
16981743
1699-
qe.tic()
1700-
D = np.empty_like(x)
1701-
d1, d2, d3 = x.shape
1702-
for i in range(d1):
1703-
for j in range(d2):
1704-
for k in range(d3):
1705-
D[i, j, k] = x[i, j, k] / y[k]
1706-
qe.toc()
1744+
with qe.Timer("For loop operation"):
1745+
D = np.empty_like(x)
1746+
d1, d2, d3 = x.shape
1747+
for i in range(d1):
1748+
for j in range(d2):
1749+
for k in range(d3):
1750+
D[i, j, k] = x[i, j, k] / y[k]
17071751
```
17081752

17091753
Note that the `for` loop takes much longer than the broadcasting operation.

0 commit comments

Comments
 (0)