Skip to content

Commit 4ac5dcc

Browse files
committed
Merge commit '9cab1333a0b3aedb922b0b3b31c25cebc879b6fd' into release/graal-vm/1.0
2 parents b828885 + 9cab133 commit 4ac5dcc

File tree

222 files changed

+12213
-2105
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

222 files changed

+12213
-2105
lines changed

3rd_party_licenses.txt

Lines changed: 188 additions & 174 deletions
Large diffs are not rendered by default.

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,23 @@
33
This changelog summarizes major changes between GraalVM versions of the Python
44
language runtime. The main focus is on user-observable behavior of the engine.
55

6+
## Version 1.0.0 RC10
7+
8+
* Improve performance of C API upcalls
9+
* Improve performance of classmethods, staticmethods, `globals()`, and `locals()`
10+
* Improve performance of various string and bytes operations
11+
* Initial support for the `_thread` builtin module (actual multi-threading is still disabled, the API defaults to a dummy implementation)
12+
* Implement the `zipimporter` module
13+
* Support assignment to `object.__class__`
14+
* Use the new Truffle filesystem API to get/set the current working directory
15+
* Attempt our best to report side-effects in KEY_INFO
16+
* The KEYS message now responds with attributes and methods, never dict keys
17+
* Support the `input` builtin
18+
* Add DEBUG launcher options for performance debugging
19+
* Ensure context isolation for file descriptors and child PIDs
20+
* Fix passing custom locals and globals through `exec` and `eval`
21+
* Fixes to builtin `help`
22+
623
## Version 1.0.0 RC9
724

825
* Support `help` in the builtin Python shell

ci.jsonnet

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
overlay: "2b63e24614bc48b008ea15460ab22129f4a2c442",
2+
overlay: "ffde557d62fff381069838b745c31b79c90e6e2a",
33

44
// ======================================================================================================
55
//
@@ -70,7 +70,7 @@
7070
"git": ">=1.8.3",
7171
"mercurial": ">=3.2.4",
7272
"gcc": "==4.9.1",
73-
"llvm": ">=4.0",
73+
"llvm": "==4.0.1",
7474
"python": "==3.4.1",
7575
"libffi": ">=3.2.1",
7676
"bzip2": ">=1.0.6",

doc/INTEROP.md

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,12 @@ Returns true for None only.
145145

146146
###### HAS_SIZE
147147
According to the Truffle interop contract answering `true` to `HAS_SIZE` implies
148-
that indexed element access is available. Thus, we answer `true` here only for
149-
(sub-)instances of `tuple`, `list`, `array.array`, `bytearray`, `bytes`, `str`,
150-
and `range`.
148+
that indexed element access is available. However, we cannot fully guarantee
149+
this. We may answer `true` here when the object has both a `__len__` field and a
150+
`__getitem__` field. If the object's length is reported >0, we also try to read
151+
the item `0` and if that fails, we answer `false`. If the object reports it's
152+
empty, we cannot know if a read with an index will actually work, but we'll
153+
report `true`.
151154

152155
###### GET_SIZE
153156
Calls `__len__`. Just because `GET_SIZE` returns something positive does not
@@ -159,27 +162,35 @@ knowing what the `__getitem__` method does with an integer argument. Use
159162
Returns true for those values that can be unboxed using the `UNBOX` message.
160163

161164
###### KEY_INFO
162-
This will lookup the key using `READ`, assume it is readable and writable, and
163-
check `IS_EXECUTABLE`.
165+
This will lookup the key using the Python MRO. It will check if it's readable
166+
and writable, and also check if it has side-effects based on wether it is an
167+
inherited descriptor (i.e., an object with `__get__`, `__set__`, and/or
168+
`__delete__`). If the owner of the key is mutable (the owner being the class the
169+
key is inherited from or the object itself) then `REMOVABLE` and `MODIFABLE` are
170+
true. If the object itself is mutable, `INSERTABLE` will also be true. Finally,
171+
if the attribute is a function or it is *not* a descriptor and has a `__call__`,
172+
we declare it `INOCABLE`. We don't do this for descriptors, because we would
173+
have to run the `__get__` method and this message should not have side-effects.
164174

165175
###### HAS_KEYS
166-
Returns true for any boxed Python object, so small integers, booleans, or floats
167-
usually don't return true.
176+
Always returns true.
168177

169178
###### KEYS
170-
This returns the direct attributes of the receiver object, which would usually
171-
be available through `__getattribute__`.
172-
173-
The `KEYS` message requires the returned object to have only `java.lang.String`
174-
items. If the object responds to `keys`, `values`, `items`, and `__getitem__`,
175-
we assume it is Mapping, and we present the result of the `keys` method in
176-
combination with the attributes if, and only if, all keys are strings. This is
177-
roughly parallel to how `READ` and `WRITE` would be handled for string keys.
179+
This returns the all attributes of the receiver object that would usually be
180+
available through `__getattribute__`, i.e., both inherited and direct
181+
attributes.
182+
183+
If the object responds to `keys`, `values`, `items`, and `__getitem__`, we
184+
assume it is Mapping, and we present the String result of the `keys` method in
185+
combination with the attributes, prefixed with `[` if, and only if, the request
186+
asked for _internal_ keys also. The `KEYS` message requires the returned object
187+
to have only `java.lang.String` items, so inlo String keys are added to the
188+
result set. The `[` prefix ensures that in our handling of `READ` and `WRITE`
189+
messages we also treat them as mapping entries, not attributes.
178190

179191
It's still possible that none of the keys can be `READ`: the `READ` message uses
180192
Python semantics for lookup, which means that an inherited descriptor with a
181-
`__get__` method may still come before the object's keys and do anything
182-
(including raising an `AttributeError`).
193+
`__get__` method or the `__getitem__` method may still intercept actual access.
183194

184195
###### IS_POINTER
185196
Returns true if the object is a Python function defined in a Python C extension

graalpython/benchmarks/src/harness.py

Lines changed: 156 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,109 @@
5555
ATTR_TEARDOWN = '__teardown__'
5656

5757

58+
# ----------------------------------------------------------------------------------------------------------------------
59+
#
60+
# the CUSUM method adapted for warmup detection within a given threshold (initial iterations)
61+
#
62+
# ----------------------------------------------------------------------------------------------------------------------
63+
def zeros(n):
64+
return [0 for _ in range(n)]
65+
66+
67+
def append(arr, val):
68+
if isinstance(arr, list):
69+
return arr + [val]
70+
else:
71+
return [val] + arr
72+
73+
74+
def cusum(values, threshold=1.0, drift=0.0):
75+
csum_pos, csum_neg = zeros(len(values)), zeros(len(values))
76+
change_points = []
77+
for i in range(1, len(values)):
78+
diff = values[i] - values[i - 1]
79+
csum_pos[i] = csum_pos[i-1] + diff - drift
80+
csum_neg[i] = csum_neg[i-1] - diff - drift
81+
82+
if csum_pos[i] < 0:
83+
csum_pos[i] = 0
84+
if csum_neg[i] < 0:
85+
csum_neg[i] = 0
86+
87+
if csum_pos[i] > threshold or csum_neg[i] > threshold:
88+
change_points = append(change_points, i)
89+
csum_pos[i], csum_neg[i] = 0, 0
90+
91+
return change_points
92+
93+
94+
def avg(values):
95+
return float(sum(values)) / len(values)
96+
97+
98+
def norm(values):
99+
_max, _min = max(values), min(values)
100+
return [float(v - _min) / (_max - _min) * 100.0 for v in values]
101+
102+
103+
def pairwise_slopes(values, cp):
104+
return [abs(float(values[i+1] - values[i]) / float(cp[i+1] - cp[i])) for i in range(len(values)-1)]
105+
106+
107+
def last_n_percent_runs(values, n=0.1):
108+
assert 0.0 < n <= 1.0
109+
end_runs_idx = len(values) - int(len(values) * n)
110+
end_runs_idx = len(values) - 1 if end_runs_idx >= len(values) else end_runs_idx
111+
return values[end_runs_idx:], list(range(end_runs_idx, len(values)))
112+
113+
114+
def first_n_percent_runs(values, n=0.1):
115+
assert 0.0 < n <= 1.0
116+
first_run_idx = int(len(values) * n)
117+
return first_run_idx -1 if first_run_idx == len(values) else first_run_idx
118+
119+
120+
def detect_warmup(values, cp_threshold=0.03, stability_slope_grade=0.01):
121+
"""
122+
detect the point of warmup point (iteration / run)
123+
124+
:param values: the durations for each run
125+
:param cp_threshold: the percent in value difference for a point to be considered a change point (percentage)
126+
:param stability_slope_grade: the slope grade (percentage). A grade of 1% corresponds to a slope of 0.5 degrees
127+
:return: the change point or -1 if not detected
128+
"""
129+
# normalize all
130+
stability_slope_grade *= 100.0
131+
cp_threshold *= 100
132+
values = norm(values)
133+
134+
try:
135+
cp = cusum(values, threshold=cp_threshold)
136+
rolling_avg = [avg(values[i:]) for i in cp]
137+
138+
def warmup(cp_index):
139+
val_idx = cp[cp_index] + 1
140+
return val_idx if val_idx < len(values) else -1
141+
142+
# find the point where the duration avg is below the cp threshold
143+
for i, d in enumerate(rolling_avg):
144+
if d <= cp_threshold:
145+
return warmup(i)
146+
147+
# could not find something below the CP threshold (noise in the data), use the stabilisation of slopes
148+
last_n_vals, last_n_idx = last_n_percent_runs(values, 0.1)
149+
slopes = pairwise_slopes(rolling_avg + last_n_vals, cp + last_n_idx)
150+
151+
for i, d in enumerate(slopes):
152+
if d <= stability_slope_grade:
153+
return warmup(i)
154+
155+
return -1
156+
except Exception as e:
157+
print("exception occurred while detecting warmup: %s" % e)
158+
return -1
159+
160+
58161
def ccompile(name, code):
59162
from importlib import invalidate_caches
60163
from distutils.core import setup, Extension
@@ -89,7 +192,14 @@ def _as_int(value):
89192

90193

91194
class BenchRunner(object):
92-
def __init__(self, bench_file, bench_args=None, iterations=1, warmup=0):
195+
def __init__(self, bench_file, bench_args=None, iterations=1, warmup=-1, warmup_runs=0):
196+
assert isinstance(iterations, int), \
197+
"BenchRunner iterations argument must be an int, got %s instead" % iterations
198+
assert isinstance(warmup, int), \
199+
"BenchRunner warmup argument must be an int, got %s instead" % warmup
200+
assert isinstance(warmup_runs, int), \
201+
"BenchRunner warmup_runs argument must be an int, got %s instead" % warmup_runs
202+
93203
if bench_args is None:
94204
bench_args = []
95205
self.bench_module = BenchRunner.get_bench_module(bench_file)
@@ -98,10 +208,8 @@ def __init__(self, bench_file, bench_args=None, iterations=1, warmup=0):
98208
_iterations = _as_int(iterations)
99209
self._run_once = _iterations <= 1
100210
self.iterations = 1 if self._run_once else _iterations
101-
102-
assert isinstance(self.iterations, int)
103-
self.warmup = _as_int(warmup)
104-
assert isinstance(self.warmup, int)
211+
self.warmup_runs = warmup_runs if warmup_runs > 0 else 0
212+
self.warmup = warmup if warmup > 0 else -1
105213

106214
@staticmethod
107215
def get_bench_module(bench_file):
@@ -139,9 +247,10 @@ def _call_attr(self, attr_name, *args):
139247

140248
def run(self):
141249
if self._run_once:
142-
print("### %s, exactly one iteration (no warmup curves)" % (self.bench_module.__name__))
250+
print("### %s, exactly one iteration (no warmup curves)" % self.bench_module.__name__)
143251
else:
144-
print("### %s, %s warmup iterations, %s bench iterations " % (self.bench_module.__name__, self.warmup, self.iterations))
252+
print("### %s, %s warmup iterations, %s bench iterations " % (self.bench_module.__name__,
253+
self.warmup_runs, self.iterations))
145254

146255
# process the args if the processor function is defined
147256
args = self._call_attr(ATTR_PROCESS_ARGS, *self.bench_args)
@@ -159,9 +268,9 @@ def run(self):
159268
bench_func = self._get_attr(ATTR_BENCHMARK)
160269
durations = []
161270
if bench_func and hasattr(bench_func, '__call__'):
162-
if self.warmup:
163-
print("### warming up for %s iterations ... " % self.warmup)
164-
for _ in range(self.warmup):
271+
if self.warmup_runs:
272+
print("### (pre)warming up for %s iterations ... " % self.warmup_runs)
273+
for _ in range(self.warmup_runs):
165274
bench_func(*args)
166275

167276
for iteration in range(self.iterations):
@@ -173,21 +282,46 @@ def run(self):
173282
if self._run_once:
174283
print("@@@ name=%s, duration=%s" % (self.bench_module.__name__, duration_str))
175284
else:
176-
print("### iteration=%s, name=%s, duration=%s" % (iteration, self.bench_module.__name__, duration_str))
285+
print("### iteration=%s, name=%s, duration=%s" % (iteration, self.bench_module.__name__,
286+
duration_str))
177287

178288
print(_HRULE)
179289
print("### teardown ... ")
180290
self._call_attr(ATTR_TEARDOWN)
181291
print("### benchmark complete")
182292
print(_HRULE)
183-
print("### BEST duration: %.3f s" % min(durations))
184-
print("### WORST duration: %.3f s" % max(durations))
185-
print("### AVG duration: %.3f" % (sum(durations) / len(durations)))
293+
294+
# summary
295+
if self._run_once:
296+
print("### SINGLE RUN duration: %.3f s" % durations[0])
297+
else:
298+
print("### BEST duration: %.3f s" % min(durations))
299+
print("### WORST duration: %.3f s" % max(durations))
300+
print("### AVG (all runs) duration: %.3f s" % (sum(durations) / len(durations)))
301+
warmup_iter = self.warmup if self.warmup > 0 else detect_warmup(durations)
302+
# if we cannot detect a warmup starting point but we performed some pre runs, we take a starting point
303+
# after the 10% of the first runs ...
304+
if warmup_iter < 0 and self.warmup_runs > 0:
305+
print("### warmup could not be detected, but %s pre-runs were executed.\n"
306+
"### we assume the benchmark is warmed up and pick an iteration "
307+
"in the first 10%% of the runs" % self.warmup_runs)
308+
warmup_iter = first_n_percent_runs(durations, 0.1)
309+
310+
if warmup_iter > 0:
311+
print("### WARMUP %s at iteration: %d" % ("specified" if self.warmup > 0 else "detected", warmup_iter))
312+
no_warmup_durations = durations[warmup_iter:]
313+
print("### AVG (no warmup) duration: %.3f s" % (sum(no_warmup_durations) / len(no_warmup_durations)))
314+
else:
315+
print("### WARMUP iteration not specified or could not be detected")
316+
317+
print(_HRULE)
318+
print("### RAW DURATIONS: %s" % str(durations))
186319
print(_HRULE)
187320

188321

189322
def run_benchmark(args):
190-
warmup = 0
323+
warmup = -1
324+
warmup_runs = 0
191325
iterations = 1
192326
bench_file = None
193327
bench_args = []
@@ -208,6 +342,12 @@ def run_benchmark(args):
208342
elif arg.startswith("--warmup"):
209343
warmup = _as_int(arg.split("=")[1])
210344

345+
elif arg == '-r':
346+
i += 1
347+
warmup_runs = _as_int(args[i])
348+
elif arg.startswith("--warmup-runs"):
349+
warmup_runs = _as_int(arg.split("=")[1])
350+
211351
elif arg == '-p':
212352
i += 1
213353
paths = args[i].split(",")
@@ -229,7 +369,7 @@ def run_benchmark(args):
229369
else:
230370
print("### no extra module search paths specified")
231371

232-
BenchRunner(bench_file, bench_args=bench_args, iterations=iterations, warmup=warmup).run()
372+
BenchRunner(bench_file, bench_args=bench_args, iterations=iterations, warmup=warmup, warmup_runs=warmup_runs).run()
233373

234374

235375
if __name__ == '__main__':

graalpython/com.oracle.graal.python.cext/include/truffle.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ void *truffle_assign_managed(void *dst, void *managed);
5454
void *truffle_deref_handle_for_managed(void *managed);
5555
bool truffle_cannot_be_handle(void *nativeHandle);
5656

57+
// wrapping functions
58+
void *truffle_decorate_function(void *function, void *wrapper);
59+
5760
/*
5861
* All function below here are deprecated and will be removed in a future release.
5962
* Use the equivalent functions from <polyglot.h> instead.

0 commit comments

Comments
 (0)