amuleweb: O(N^2) -> O(N) array_push_back via cached scan hint (#666) - #689
Merged
mrjimenez merged 1 commit intoMay 23, 2026
Merged
Conversation
PHP's array_push_back() linearly scans i = 0..0xffff every call,
looking for the first integer key whose value is PHP_VAL_NONE. On
a fresh array built by N successive push_backs the scan finds the
hole at i = N-1 each time, doing N-1 map lookups for push N, so
building an N-element array costs O(N^2). On a 43k-shared-files
amuleweb-main-shared.php this is ~950M std::map<string, *> lookups
plus snprintf("%d") per lookup, wedging amuleweb at 100% CPU for
the entire page render (issue amule-project#666 secondary wedge).
Cache the next-push starting index on PHP_ARRAY_TYPE and resume
the scan from there, advancing on success. amuleweb's PHP code
only ever push_backs (no unset/array_pop/array_shift across the
default templates or the PHP engine glue), so the hint is always
accurate and each push_back collapses to O(1). For correctness
against future mixed insert+delete callers a fallback low-range
scan keeps the original semantics.
Switch the typedef'd anonymous struct to a tagged struct so the
new default-member-init doesn't trip -Wnon-c-typedef-for-linkage.
This was referenced May 23, 2026
Closed
Closed
ngosang
pushed a commit
to ngosang/amule
that referenced
this pull request
Jul 29, 2026
…project#689) Closes amule-project#617. Downloads: a "Total queue size: X" field, right-aligned in the File sources row, summing the files currently visible (selected category + text filter). Kept live the same way as the file count -- reset in bulk by RebuildVisibleList(), adjusted by GetFileSize() as ShowFile() adds or removes a row -- so there is no per-tick cost. Shared: the combined size of the visible shared files, shown as "Total size of Shared Files: X" (reusing the existing string) in the statistics box, in place of the "Percent of total files" label -- which was misleading, since the gauges beside it show a session/all-time ratio per selected file, not a percentage of files. The session/all-time gauges stay. Both auto-scale units via CastItoXBytes and work in amuleGUI too: file sizes are already EC-streamed, so no core/protocol change is needed. The "Total queue size: %s" string is new; the shared label reuses an existing one. Catalogs regenerated.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related to #666 — secondary amuleweb-side wedge surfaced by @Stoatwblr's latest gdb on amuleweb after #687 unblocked the amuled side.
Root cause
array_push_backin the PHP engine does:On a freshly-pushed array of size N, the scan walks
i = 0..N-1(each iteration does ansnprintf("%d")+std::map<string, *>::count()) before finding the empty slot at i = N. Building the array via N push_backs costs O(N²).amule_load_shared→amule_obj_array_create<SharedFileInfo, SharedFile>callsarray_push_backonce per shared file. On @Stoatwblr's 43585-file install that's ~950M map lookups + string conversions per amuleweb-main-shared.php render, pegging the amuleweb process at 100% CPU for the entire page load.Backtrace top:
Fix
Cache the next-push starting index on
PHP_ARRAY_TYPEand resume the scan from there, advancing on success. amuleweb's PHP code (engine glue + the default*.phptemplates) only ever push_backs — nounset,array_pop,array_shift, orarray_splicecallers — so the hint is always accurate and each push_back collapses to O(1).For correctness against any future caller that does mixed insert+delete, a fallback low-range scan preserves the original "fill the first hole" semantics if the hint missed.
The anonymous typedef'd struct becomes a tagged struct so the new default-member-init doesn't trip
-Wnon-c-typedef-for-linkage.Test
Local macOS build of
amule amuled amulegui amuleweb— clean, no warnings. The bottleneck is purely amuleweb-side, so combined with #687 (amuled-side) the fullamuleweb-main-shared.phprender path should be free of N² scans end-to-end.