File: chain.py

package info (click to toggle)
python-greenlet 3.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,032 kB
  • sloc: cpp: 5,045; python: 3,160; ansic: 1,125; makefile: 155; asm: 120; sh: 42
file content (251 lines) | stat: -rwxr-xr-x 6,417 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
#!/usr/bin/env python
"""
Create a chain of coroutines and pass a value from one end to the
other, where each coroutine will increment the value before passing it
along.
"""

import os
import pyperf
import greenlet

# This is obsolete now, we always expose frames for Python 3.12.
# See https://github.com/python-greenlet/greenlet/pull/393/
# for a complete discussion of performance.
EXPOSE_FRAMES = 'EXPOSE_FRAMES' in os.environ

# Exposing
# 100 frames Mean +- std dev: 5.62 us +- 0.10 us
# 200 frames Mean +- std dev: 14.0 us +- 0.6 us
# 300 frames Mean +- std dev: 22.7 us +- 0.4 us
#
# Non-exposing
# 100 frames Mean +- std dev: 3.64 us +- 0.06 us -> 1.54/1.98us
# 200 frames Mean +- std dev: 9.49 us +- 0.13 us -> 1.47/4.51us
# 300 frames Mean +- std dev: 15.7 us +- 0.3 us  -> 1.45/7us

def link(next_greenlet):
    value = greenlet.getcurrent().parent.switch()
    next_greenlet.switch(value + 1)


CHAIN_GREENLET_COUNT = 100000

def bm_chain(loops):
    begin = pyperf.perf_counter()
    for _ in range(loops):
        start_node = greenlet.getcurrent()
        for _ in range(CHAIN_GREENLET_COUNT):
            g = greenlet.greenlet(link)
            g.gr_frames_always_exposed = EXPOSE_FRAMES
            g.switch(start_node)
            start_node = g
        x = start_node.switch(0)
        assert x == CHAIN_GREENLET_COUNT
    end = pyperf.perf_counter()
    return end - begin

GETCURRENT_INNER_LOOPS = 10
def bm_getcurrent(loops):
    getcurrent = greenlet.getcurrent
    getcurrent() # Factor out the overhead of creating the initial main greenlet
    begin = pyperf.perf_counter()
    for _ in range(loops):
        # Manual unroll
        getcurrent()
        getcurrent()
        getcurrent()
        getcurrent()
        getcurrent()
        getcurrent()
        getcurrent()
        getcurrent()
        getcurrent()
        getcurrent()
    end = pyperf.perf_counter()
    return end - begin

SWITCH_INNER_LOOPS = 10000
def bm_switch_shallow(loops):
    # pylint:disable=attribute-defined-outside-init
    class G(greenlet.greenlet):
        other = None
        def run(self):
            o = self.other
            for _ in range(SWITCH_INNER_LOOPS):
                o.switch()

    begin = pyperf.perf_counter()

    for _ in range(loops):
        gl1 = G()
        gl2 = G()
        gl1.gr_frames_always_exposed = EXPOSE_FRAMES
        gl2.gr_frames_always_exposed = EXPOSE_FRAMES
        gl1.other = gl2
        gl2.other = gl1
        gl1.switch()

        gl1.switch()
        gl2.switch()
        gl1.other = gl2.other = None
        assert gl1.dead
        assert gl2.dead

    end = pyperf.perf_counter()
    return end - begin

def bm_switch_deep(loops, _MAX_DEPTH=200):
    # pylint:disable=attribute-defined-outside-init
    class G(greenlet.greenlet):
        other = None
        def run(self):
            for _ in range(SWITCH_INNER_LOOPS):
                self.recur_then_switch()

        def recur_then_switch(self, depth=_MAX_DEPTH):
            if not depth:
                self.other.switch()
            else:
                self.recur_then_switch(depth - 1)

    begin = pyperf.perf_counter()

    for _ in range(loops):
        gl1 = G()
        gl2 = G()
        gl1.gr_frames_always_exposed = EXPOSE_FRAMES
        gl2.gr_frames_always_exposed = EXPOSE_FRAMES
        gl1.other = gl2
        gl2.other = gl1
        gl1.switch()

        gl1.switch()
        gl2.switch()
        gl1.other = gl2.other = None
        assert gl1.dead
        assert gl2.dead

    end = pyperf.perf_counter()
    return end - begin

def bm_switch_deeper(loops):
    return bm_switch_deep(loops, 400)


CREATE_INNER_LOOPS = 10
def bm_create(loops):
    gl = greenlet.greenlet
    begin = pyperf.perf_counter()
    for _ in range(loops):
        gl()
        gl()
        gl()
        gl()
        gl()
        gl()
        gl()
        gl()
        gl()
        gl()
    end = pyperf.perf_counter()
    return end - begin




def _bm_recur_frame(loops, RECUR_DEPTH):

    def recur(depth):
        if not depth:
            return greenlet.getcurrent().parent.switch(greenlet.getcurrent())
        return recur(depth - 1)


    begin = pyperf.perf_counter()
    for _ in range(loops):

        for _ in range(CHAIN_GREENLET_COUNT):
            g = greenlet.greenlet(recur)
            g.gr_frames_always_exposed = EXPOSE_FRAMES
            g2 = g.switch(RECUR_DEPTH)
            assert g2 is g, (g2, g)
            f = g2.gr_frame
            assert f is not None, "frame is none"
            count = 0
            while f:
                count += 1
                f = f.f_back
            # This assertion fails with the released versions of greenlet
            # on Python 3.12
            #assert count == RECUR_DEPTH + 1, (count, RECUR_DEPTH)
            # Switch back so it can be collected; otherwise they build
            # up forever.
            g.switch()
            # fall off the end of it and back to us.
            del g
            del g2
            del f


    end = pyperf.perf_counter()
    return end - begin

def bm_recur_frame_2(loops):
    return _bm_recur_frame(loops, 2)

def bm_recur_frame_20(loops):
    return _bm_recur_frame(loops, 20)

def bm_recur_frame_200(loops):
    return _bm_recur_frame(loops, 200)

if __name__ == '__main__':
    runner = pyperf.Runner()

    runner.bench_time_func(
        'create a greenlet',
        bm_create,
        inner_loops=CREATE_INNER_LOOPS
    )

    runner.bench_time_func(
        'switch between two greenlets (shallow)',
        bm_switch_shallow,
        inner_loops=SWITCH_INNER_LOOPS
    )

    runner.bench_time_func(
        'switch between two greenlets (deep)',
        bm_switch_deep,
        inner_loops=SWITCH_INNER_LOOPS
    )

    runner.bench_time_func(
        'switch between two greenlets (deeper)',
        bm_switch_deeper,
        inner_loops=SWITCH_INNER_LOOPS
    )
    runner.bench_time_func(
        'getcurrent single thread',
        bm_getcurrent,
        inner_loops=GETCURRENT_INNER_LOOPS
    )
    runner.bench_time_func(
        'chain(%s)' % CHAIN_GREENLET_COUNT,
        bm_chain,
    )

    runner.bench_time_func(
        'read 2 nested frames',
        bm_recur_frame_2,
    )

    runner.bench_time_func(
        'read 20 nested frames',
        bm_recur_frame_20,
    )
    runner.bench_time_func(
        'read 200 nested frames',
        bm_recur_frame_200,
    )