Skip to content

Commit 9971f4f

Browse files
committed
Cython-code fix for the issue + optimization
line_profiler/_line_profiler.pyx::LineProfiler.add_function() - Now making sure that ALL functions passed to the method will be NOP-bytecode-padded, instead of from the 2nd one onwards - No longer re-padding already NOP-bytecode-padded code objects (could be by another profiler instance or even the same one); this probably eliminates the need for keeping track on profiler instances and leaves room for further optimizations tests/test_line_profiler.py ::test_aggregate_profiling_data_between_code_versions() - Updated test body to no longer test for bytecode alteration (because there is no longer any) - Added note in docstring stating the original context of the test and how it is no longer relevant (or should we just delete this test?)
1 parent 4a4c4e8 commit 9971f4f

2 files changed

Lines changed: 33 additions & 21 deletions

File tree

line_profiler/_line_profiler.pyx

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,14 +1139,35 @@ datamodel.html#user-defined-functions
11391139
code_hashes = []
11401140
if any(co_code): # Normal Python functions
11411141
# Figure out how much padding we need and strip the bytecode
1142+
# Notes:
1143+
# - Profiled function are always padded, so as to
1144+
# distinguish between them and unprofiled bytecode twins
1145+
# - `npad` is strictly increasing, except when the function
1146+
# has already been padded -- we assume that is solely
1147+
# because a (could be the same) profiler instance has seen
1148+
# it
11421149
base_co_code: bytes
1143-
npad_code: int
1144-
base_co_code, npad_code = multibyte_rstrip(co_code)
1145-
try:
1146-
npad = self._all_paddings[base_co_code]
1147-
except KeyError:
1148-
npad = 0
1149-
self._all_paddings[base_co_code] = max(npad, npad_code) + 1
1150+
npad: int
1151+
is_padded: int
1152+
base_co_code, is_padded = multibyte_rstrip(co_code)
1153+
if not is_padded:
1154+
try:
1155+
npad = self._all_paddings[base_co_code]
1156+
except KeyError:
1157+
npad = 1
1158+
self._all_paddings[base_co_code] = npad + 1
1159+
# Code hash already exists, so there must be a duplicate
1160+
# function (on some instance);
1161+
# (re-)pad with no-op
1162+
co_code = base_co_code + NOP_BYTES * npad
1163+
code = _code_replace(func, co_code)
1164+
try:
1165+
func.__code__ = code
1166+
except AttributeError as e:
1167+
func.__func__.__code__ = code
1168+
# XXX: if we no longer re-pad the bytecode, do we still need
1169+
# to do bookkeeping on which profiler instances follow which
1170+
# function objects?
11501171
try:
11511172
profilers_to_update = self._all_instances_by_funcs[func_id]
11521173
profilers_to_update.add(self)
@@ -1158,18 +1179,6 @@ datamodel.html#user-defined-functions
11581179
self.dupes_map[base_co_code].append(code)
11591180
except KeyError:
11601181
self.dupes_map[base_co_code] = [code]
1161-
if npad > npad_code:
1162-
# Code hash already exists, so there must be a duplicate
1163-
# function (on some instance);
1164-
# (re-)pad with no-op
1165-
co_code = base_co_code + NOP_BYTES * npad
1166-
code = _code_replace(func, co_code)
1167-
try:
1168-
func.__code__ = code
1169-
except AttributeError as e:
1170-
func.__func__.__code__ = code
1171-
else: # No re-padding -> no need to update the other profs
1172-
profilers_to_update = {self}
11731182
# TODO: Since each line can be many bytecodes, this is kinda
11741183
# inefficient
11751184
# See if this can be sped up by not needing to iterate over

tests/test_line_profiler.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,11 @@ def test_aggregate_profiling_data_between_code_versions():
12481248
Test that profiling data from previous versions of the code object
12491249
are preserved when another profiler causes the code object of a
12501250
function to be overwritten.
1251+
1252+
Note
1253+
----
1254+
Now obsolete because we no longer double-pad/overwrite the function
1255+
bytecode if it has already been seen by another profiler instance.
12511256
"""
12521257

12531258
def func(n):
@@ -1262,10 +1267,8 @@ def func(n):
12621267
# Gather data with `@prof1`
12631268
wrapper1 = prof1(func)
12641269
assert wrapper1(10) == 10 * 11 // 2
1265-
code = func.__code__
12661270
# Gather data with `@prof2`; the code object is overwritten here
12671271
wrapper2 = prof2(wrapper1)
1268-
assert func.__code__ != code
12691272
assert wrapper2(15) == 15 * 16 // 2
12701273
# Despite the overwrite of the code object, the old data should
12711274
# still remain, and be aggregated with the new data when calling

0 commit comments

Comments
 (0)