Profiling pcapkit.extract() by self time rather than cumulative time puts the largest single cost in aenum.extend_enum, not in dispatch or imports. Filed so the next performance attempt starts from the measurement rather than from a guess.
Credit: this came out of profiling prompted by @Ts-Boom's #551 and #563. Both proposed optimisations that measurement did not support — struct.calcsize is already memoised inside CPython, and the import path's self time is under 1% — but the profiling those PRs prompted is what surfaced the figures below, and the suggestion to profile a real extract() run came from the #551 discussion.
The measurement
cProfile on main at 8cfd6ab01, http.pcap, 1117 frames, 2.09 s total, interpreter 3.14.7:
| function |
calls |
tottime (self) |
cumtime |
_import_next_layer |
3351 |
0.0121 s (0.58%) |
1.888 s (90.2%) |
_lookup_next_layer |
4695 |
0.0028 s (0.14%) |
0.005 s (0.2%) |
_lookup_registry |
8490 |
0.0019 s (0.09%) |
0.002 s (0.1%) |
Top self-time consumers, same run: aenum.extend_enum at 16.7% — the single largest item in the whole profile — then schema unpack, getattr at 976k calls, schema __new__, isinstance/abc.__instancecheck__, and field unpack.
The lesson for anyone reading a profile of this library: _import_next_layer is a recursive-descent dispatcher, so it sits in the call chain of every nested protocol layer and cumulative time attributes the entire parse beneath it to that one frame. Reading its 90% cumulative figure as a hotspot is the mistake #563's ~40% claim most plausibly came from.
Why extend_enum is plausibly addressable
It is called when a constant lookup meets a value the enumeration does not define and extends the enumeration at runtime — the _missing_ path. On a capture with many unrecognised codes that happens repeatedly. Whether the right answer is memoising the extension, avoiding it for values that will never be reused, or something else is exactly the design work this issue is for; it should not be guessed at.
Worth pairing with the getattr count: 976k calls on a 1117-frame capture is roughly 874 per frame, which suggests attribute access in a hot loop that may be hoistable.
What would make a PR here convincing
Self-time figures before and after on at least two captures — one ordinary such as http.pcap, one exercising the unrecognised-code path heavily such as many_interfaces.pcapng — plus wall-clock extract() timings with the repetition count and host load stated. Note that on a shared host single-digit-millisecond variance swamps microsecond-scale changes, so a claimed improvement smaller than the noise should be reported as unmeasurable rather than as a number. Byte-identical output across the sample captures, as #420 and #427 did.
Profiling
pcapkit.extract()by self time rather than cumulative time puts the largest single cost inaenum.extend_enum, not in dispatch or imports. Filed so the next performance attempt starts from the measurement rather than from a guess.Credit: this came out of profiling prompted by @Ts-Boom's #551 and #563. Both proposed optimisations that measurement did not support —
struct.calcsizeis already memoised inside CPython, and the import path's self time is under 1% — but the profiling those PRs prompted is what surfaced the figures below, and the suggestion to profile a realextract()run came from the #551 discussion.The measurement
cProfileonmainat8cfd6ab01,http.pcap, 1117 frames, 2.09 s total, interpreter 3.14.7:_import_next_layer_lookup_next_layer_lookup_registryTop self-time consumers, same run:
aenum.extend_enumat 16.7% — the single largest item in the whole profile — then schemaunpack,getattrat 976k calls, schema__new__,isinstance/abc.__instancecheck__, and fieldunpack.The lesson for anyone reading a profile of this library:
_import_next_layeris a recursive-descent dispatcher, so it sits in the call chain of every nested protocol layer and cumulative time attributes the entire parse beneath it to that one frame. Reading its 90% cumulative figure as a hotspot is the mistake #563's ~40% claim most plausibly came from.Why
extend_enumis plausibly addressableIt is called when a constant lookup meets a value the enumeration does not define and extends the enumeration at runtime — the
_missing_path. On a capture with many unrecognised codes that happens repeatedly. Whether the right answer is memoising the extension, avoiding it for values that will never be reused, or something else is exactly the design work this issue is for; it should not be guessed at.Worth pairing with the
getattrcount: 976k calls on a 1117-frame capture is roughly 874 per frame, which suggests attribute access in a hot loop that may be hoistable.What would make a PR here convincing
Self-time figures before and after on at least two captures — one ordinary such as
http.pcap, one exercising the unrecognised-code path heavily such asmany_interfaces.pcapng— plus wall-clockextract()timings with the repetition count and host load stated. Note that on a shared host single-digit-millisecond variance swamps microsecond-scale changes, so a claimed improvement smaller than the noise should be reported as unmeasurable rather than as a number. Byte-identical output across the sample captures, as #420 and #427 did.