1
- # Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
1
+ # Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
2
2
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3
3
#
4
4
# The Universal Permissive License (UPL), Version 1.0
37
37
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38
38
# SOFTWARE.
39
39
import marshal
40
- import os
41
- import sys
42
-
43
-
44
- IS_GRAAL = sys .implementation .name == "graalpython"
45
- DIR0 = os .path .dirname (__file__ )
46
-
47
-
48
- if IS_GRAAL :
49
- get_code = lambda n ,s : __graalpython__ .compile (s , n , "pyc" )
50
- else :
51
- def get_code (n ,s ):
52
- c = compile (s ,n ,"exec" )
53
- import dis
54
- dis .dis (c )
55
- return c
56
-
40
+ import pydoc_data .topics
57
41
58
- CODE = get_code ("bench.py" , """
42
+ # pollute the profile of the bytecode loop
43
+ exec ("""
59
44
import sys
60
- def foo():
61
- pass
45
+ def foo(): pass
62
46
len(sys.__name__)
63
47
print(sys, flush=False)
64
48
pass
65
49
""" )
66
50
67
- # pollute the profile of the bytecode loop
68
- exec (CODE )
69
-
70
-
71
- # We're making sure that each code object is a separate call target. This is to
72
- # simulate we are loading SIMULATED_FILECOUNT modules, because each module is
73
- # potentially a lot of code but only gets executed once
74
- RETURN_NONE = [100 , 0 , 83 , 0 ]
75
- BYTECODE_COUNT = 200
76
- SIMULATED_FILECOUNT = 1000
77
- CODE = []
78
-
79
-
80
- def generate_code (name , code_to_repeat , ** kwargs ):
81
- filename = os .path .join (DIR0 , name )
82
- if not IS_GRAAL and False :
83
- # generate our code files only with cpython
84
- def foo (): pass
85
- code = foo .__code__
86
- cnt = int (2 * BYTECODE_COUNT / len (code_to_repeat ))
87
- newcode = code .replace (co_code = bytes (bytearray (code_to_repeat * cnt + RETURN_NONE )), ** kwargs )
88
- with open (filename , "wb" ) as f :
89
- marshal .dump (newcode , f )
90
- # pollute the profile for the bytecode loop
91
- with open (filename , "rb" ) as f :
92
- exec (marshal .load (f ))
93
- return filename
94
-
95
-
96
- BYTECODE_FILES = [
97
- generate_code ("nop" , [9 , 0 ]),
98
- generate_code ("pushpop" , [100 , 0 , 1 , 0 ]),
99
- generate_code ("negative_one" , [100 , 1 , 11 , 0 , 1 , 0 ], co_consts = (None , 1 ,)),
100
- generate_code ("load_fast" , [
101
- 100 , 0 , # load None
102
- 125 , 0 , # store fast 0
103
- 124 , 0 , # load fast 0
104
- 1 , 0 , # pop top
105
- ], co_varnames = ('x' ,)),
106
- ]
107
-
108
-
109
-
110
- for _ in range (0 , SIMULATED_FILECOUNT , len (BYTECODE_FILES )):
111
- for filename in BYTECODE_FILES :
112
- with open (filename , "rb" ) as f :
113
- CODE .append (marshal .load (f ))
114
-
115
-
116
51
CODESTR1 = "\n " .join (["""
117
52
# import sys
118
53
def foo(): pass
119
54
len(foo.__name__)
120
55
""" ] * 100 )
56
+
121
57
CODESTR2 = "\n " .join (["""
122
58
def bar(): pass
123
59
x = None
124
60
len([])
125
61
y = x
126
62
""" ] * 100 )
127
- import pydoc_data . topics
63
+
128
64
with open (pydoc_data .topics .__file__ , "r" ) as f :
129
65
CODESTR3 = f .read ()
130
- TEST_WITH_BYTECODE = False # False to test with AST
131
- if IS_GRAAL and TEST_WITH_BYTECODE :
132
- JUST_PYC_1 = __graalpython__ .compile (CODESTR1 , "1" , "pyc-nocompile" )
133
- JUST_PYC_2 = __graalpython__ .compile (CODESTR1 , "2" , "pyc-nocompile" )
134
- JUST_PYC_3 = __graalpython__ .compile (CODESTR3 , pydoc_data .topics .__file__ , "pyc-nocompile" )
135
- else :
136
- JUST_PYC_1 = marshal .dumps (compile (CODESTR1 , "1" , "exec" ))
137
- JUST_PYC_2 = marshal .dumps (compile (CODESTR2 , "2" , "exec" ))
138
- JUST_PYC_3 = marshal .dumps (compile (CODESTR3 , pydoc_data .topics .__file__ , "exec" ))
139
66
67
+ JUST_PYC_1 = marshal .dumps (compile (CODESTR1 , "1" , "exec" ))
68
+ JUST_PYC_2 = marshal .dumps (compile (CODESTR2 , "2" , "exec" ))
69
+ JUST_PYC_3 = marshal .dumps (compile (CODESTR3 , pydoc_data .topics .__file__ , "exec" ))
140
70
141
- MORE_CODEOBJECTS = []
71
+ CODEOBJECTS = []
142
72
143
73
144
74
def __setup__ (num ):
145
75
__cleanup__ (num )
146
76
147
77
148
78
def __cleanup__ (num ):
149
- import time
150
- s = time .time ()
151
- MORE_CODEOBJECTS .clear ()
79
+ CODEOBJECTS .clear ()
152
80
for _ in range (0 , num , 3 ):
153
- MORE_CODEOBJECTS .append (marshal .loads (JUST_PYC_1 ))
154
- MORE_CODEOBJECTS .append (marshal .loads (JUST_PYC_2 ))
155
- MORE_CODEOBJECTS .append (marshal .loads (JUST_PYC_3 ))
156
- print (f"Resetting { len (MORE_CODEOBJECTS )} took { time .time () - s } seconds" )
157
-
158
-
159
- BYTECODE_FILE_DATA = []
160
- for filename in BYTECODE_FILES :
161
- with open (filename , "rb" ) as f :
162
- BYTECODE_FILE_DATA .append (f .read ())
81
+ CODEOBJECTS .append (marshal .loads (JUST_PYC_1 ))
82
+ CODEOBJECTS .append (marshal .loads (JUST_PYC_2 ))
83
+ CODEOBJECTS .append (marshal .loads (JUST_PYC_3 ))
163
84
164
85
165
86
def measure (num ):
166
87
for i in range (num ):
167
- # Enable this to benchmark GraalPython code deserialization SST vs
168
- # bytecode. Switch out the mode in the global setup above
169
- # marshal.loads(JUST_PYC_1); marshal.loads(JUST_PYC_2); marshal.loads(JUST_PYC_3)
170
-
171
- # Enable this to measure executing different modules in AST vs bytecode
172
- exec (MORE_CODEOBJECTS [i ])
88
+ exec (CODEOBJECTS [i ])
173
89
174
- # Enable this to measure just unmarshalling
175
- # marshal.loads(BYTECODE_FILE_DATA[i % len(BYTECODE_FILE_DATA)])
176
90
177
- # Enable this to measure just loading file data
178
- # with open(BYTECODE_FILES[i % len(BYTECODE_FILES)], "rb") as f:
179
- # f.read()
180
-
181
- # Enable this to measure loading all the modules
182
- # with open(BYTECODE_FILES[i % len(BYTECODE_FILES)], "rb") as f:
183
- # marshal.loads(f.read())
184
-
185
- # Enable this to measure executing different modules
186
- # exec(CODE[i % len(CODE)])
187
-
188
- # Enable this to measure unmarshalling and executing code
189
- # exec(marshal.loads(BYTECODE_FILE_DATA[i % len(BYTECODE_FILE_DATA)]))
190
-
191
- # Enable this to measure all three, reading file, loading data, and executing
192
- # with open(BYTECODE_FILES[i % len(BYTECODE_FILES)], "rb") as f:
193
- # exec(marshal.loads(f.read()))
194
-
195
-
196
- def __benchmark__ (num = 10_000 ):
91
+ def __benchmark__ (num = 2000 ):
197
92
measure (num )
198
93
199
-
200
- print ("Benchmark file loaded" )
201
-
202
-
203
94
# I've written the bytecode benchmark to simulate loading thousands of modules. I
204
- # always execute 10_000 iterations, to make it somewhat like loading a big
95
+ # always execute thousands of iterations, to make it somewhat like loading a big
205
96
# project with many pyc files, but not so large that the compiler would have time
206
97
# to compile many of the operations involved in loading code.
207
98
#
208
- # If we compare unmarshaling a code object from a bytes that has bytecode vs one
99
+ # If we compare unmarshalling a code object from a bytes that has bytecode vs one
209
100
# that has SST, we see that unmarshalling 10_000 bytecode code objects is 2-3x
210
101
# faster than unmarshalling the SST. This is for two bits of code with 300 lines
211
102
# of statements (creating functions, imports, assignments, calling
@@ -227,7 +118,7 @@ def __benchmark__(num=10_000):
227
118
# Opening the files *and* loading the bytecode data yields that CPython is ~14x
228
119
# faster than us.
229
120
#
230
- # Now, if we preload that artificial code before the benchmark and just execut
121
+ # Now, if we preload that artificial code before the benchmark and just execute
231
122
# those code objects 10_000 times, CPython is a whopping 20-30x faster than we
232
123
# are. But the numbers are so small, it's hard to say (CPython 0.006-0.008s,
233
124
# Graal 0.16-0.19s). OTOH, it's hard to argue that we would ever load more
0 commit comments