55along.
66"""
77
8+ import os
89import pyperf
910import greenlet
1011
12+ # This is obsolete now, we always expose frames for Python 3.12.
13+ # See https://github.com/python-greenlet/greenlet/pull/393/
14+ # for a complete discussion of performance.
15+ EXPOSE_FRAMES = 'EXPOSE_FRAMES' in os .environ
1116
17+ # Exposing
18+ # 100 frames Mean +- std dev: 5.62 us +- 0.10 us
19+ # 200 frames Mean +- std dev: 14.0 us +- 0.6 us
20+ # 300 frames Mean +- std dev: 22.7 us +- 0.4 us
21+ #
22+ # Non-exposing
23+ # 100 frames Mean +- std dev: 3.64 us +- 0.06 us -> 1.54/1.98us
24+ # 200 frames Mean +- std dev: 9.49 us +- 0.13 us -> 1.47/4.51us
25+ # 300 frames Mean +- std dev: 15.7 us +- 0.3 us -> 1.45/7us
1226
1327def link (next_greenlet ):
1428 value = greenlet .getcurrent ().parent .switch ()
@@ -23,6 +37,7 @@ def bm_chain(loops):
2337 start_node = greenlet .getcurrent ()
2438 for _ in range (CHAIN_GREENLET_COUNT ):
2539 g = greenlet .greenlet (link )
40+ g .gr_frames_always_exposed = EXPOSE_FRAMES
2641 g .switch (start_node )
2742 start_node = g
2843 x = start_node .switch (0 )
@@ -51,7 +66,8 @@ def bm_getcurrent(loops):
5166 return end - begin
5267
5368SWITCH_INNER_LOOPS = 10000
54- def bm_switch (loops ):
69+ def bm_switch_shallow (loops ):
70+ # pylint:disable=attribute-defined-outside-init
5571 class G (greenlet .greenlet ):
5672 other = None
5773 def run (self ):
@@ -60,15 +76,63 @@ def run(self):
6076 o .switch ()
6177
6278 begin = pyperf .perf_counter ()
79+
80+ for _ in range (loops ):
81+ gl1 = G ()
82+ gl2 = G ()
83+ gl1 .gr_frames_always_exposed = EXPOSE_FRAMES
84+ gl2 .gr_frames_always_exposed = EXPOSE_FRAMES
85+ gl1 .other = gl2
86+ gl2 .other = gl1
87+ gl1 .switch ()
88+
89+ gl1 .switch ()
90+ gl2 .switch ()
91+ gl1 .other = gl2 .other = None
92+ assert gl1 .dead
93+ assert gl2 .dead
94+
95+ end = pyperf .perf_counter ()
96+ return end - begin
97+
98+ def bm_switch_deep (loops , _MAX_DEPTH = 200 ):
99+ # pylint:disable=attribute-defined-outside-init
100+ class G (greenlet .greenlet ):
101+ other = None
102+ def run (self ):
103+ for _ in range (SWITCH_INNER_LOOPS ):
104+ self .recur_then_switch ()
105+
106+ def recur_then_switch (self , depth = _MAX_DEPTH ):
107+ if not depth :
108+ self .other .switch ()
109+ else :
110+ self .recur_then_switch (depth - 1 )
111+
112+ begin = pyperf .perf_counter ()
113+
63114 for _ in range (loops ):
64115 gl1 = G ()
65116 gl2 = G ()
117+ gl1 .gr_frames_always_exposed = EXPOSE_FRAMES
118+ gl2 .gr_frames_always_exposed = EXPOSE_FRAMES
66119 gl1 .other = gl2
67120 gl2 .other = gl1
68121 gl1 .switch ()
122+
123+ gl1 .switch ()
124+ gl2 .switch ()
125+ gl1 .other = gl2 .other = None
126+ assert gl1 .dead
127+ assert gl2 .dead
128+
69129 end = pyperf .perf_counter ()
70130 return end - begin
71131
132+ def bm_switch_deeper (loops ):
133+ return bm_switch_deep (loops , 400 )
134+
135+
72136CREATE_INNER_LOOPS = 10
73137def bm_create (loops ):
74138 gl = greenlet .greenlet
@@ -87,20 +151,81 @@ def bm_create(loops):
87151 end = pyperf .perf_counter ()
88152 return end - begin
89153
154+
155+
156+
157+ def _bm_recur_frame (loops , RECUR_DEPTH ):
158+
159+ def recur (depth ):
160+ if not depth :
161+ return greenlet .getcurrent ().parent .switch (greenlet .getcurrent ())
162+ return recur (depth - 1 )
163+
164+
165+ begin = pyperf .perf_counter ()
166+ for _ in range (loops ):
167+
168+ for _ in range (CHAIN_GREENLET_COUNT ):
169+ g = greenlet .greenlet (recur )
170+ g .gr_frames_always_exposed = EXPOSE_FRAMES
171+ g2 = g .switch (RECUR_DEPTH )
172+ assert g2 is g , (g2 , g )
173+ f = g2 .gr_frame
174+ assert f is not None , "frame is none"
175+ count = 0
176+ while f :
177+ count += 1
178+ f = f .f_back
179+ # This assertion fails with the released versions of greenlet
180+ # on Python 3.12
181+ #assert count == RECUR_DEPTH + 1, (count, RECUR_DEPTH)
182+ # Switch back so it can be collected; otherwise they build
183+ # up forever.
184+ g .switch ()
185+ # fall off the end of it and back to us.
186+ del g
187+ del g2
188+ del f
189+
190+
191+ end = pyperf .perf_counter ()
192+ return end - begin
193+
194+ def bm_recur_frame_2 (loops ):
195+ return _bm_recur_frame (loops , 2 )
196+
197+ def bm_recur_frame_20 (loops ):
198+ return _bm_recur_frame (loops , 20 )
199+
200+ def bm_recur_frame_200 (loops ):
201+ return _bm_recur_frame (loops , 200 )
202+
90203if __name__ == '__main__' :
91204 runner = pyperf .Runner ()
205+
92206 runner .bench_time_func (
93207 'create a greenlet' ,
94208 bm_create ,
95209 inner_loops = CREATE_INNER_LOOPS
96210 )
97211
98212 runner .bench_time_func (
99- 'switch between two greenlets' ,
100- bm_switch ,
213+ 'switch between two greenlets (shallow) ' ,
214+ bm_switch_shallow ,
101215 inner_loops = SWITCH_INNER_LOOPS
102216 )
103217
218+ runner .bench_time_func (
219+ 'switch between two greenlets (deep)' ,
220+ bm_switch_deep ,
221+ inner_loops = SWITCH_INNER_LOOPS
222+ )
223+
224+ runner .bench_time_func (
225+ 'switch between two greenlets (deeper)' ,
226+ bm_switch_deeper ,
227+ inner_loops = SWITCH_INNER_LOOPS
228+ )
104229 runner .bench_time_func (
105230 'getcurrent single thread' ,
106231 bm_getcurrent ,
@@ -110,3 +235,17 @@ def bm_create(loops):
110235 'chain(%s)' % CHAIN_GREENLET_COUNT ,
111236 bm_chain ,
112237 )
238+
239+ runner .bench_time_func (
240+ 'read 2 nested frames' ,
241+ bm_recur_frame_2 ,
242+ )
243+
244+ runner .bench_time_func (
245+ 'read 20 nested frames' ,
246+ bm_recur_frame_20 ,
247+ )
248+ runner .bench_time_func (
249+ 'read 200 nested frames' ,
250+ bm_recur_frame_200 ,
251+ )
0 commit comments