Closed Bug 174341 Opened 22 years ago Closed 22 years ago

JS string concatenation perf problem, memory jumps from 16 MB to 250+ MB

Categories

(Core :: JavaScript Engine, defect, P1)

defect

Tracking

()

VERIFIED FIXED
mozilla1.4alpha

People

(Reporter: lhirlimann, Assigned: brendan)

References

()

Details

(Keywords: js1.5, perf, qawanted)

Attachments

(3 files, 3 obsolete files)

Build ID is 2002101308 on w2k server sp3. When going to http://www.ericsson.fr the loading bar goes approximatly to it's half. When looking memory useage it VM use jumps from 16 Mb to 250 Mb very rapidly. This is also true with Linux (Woody install) and build id 2002061503. Kernel 2.4.18, libc6 2.2.5. When launched load goes from 1 to 6.
On w2k when going to debug->Leak Detector->Dump memory link. And trying to load the page after wards , the page loads and VM goes back to normal.
Changing sev and subject because the page loads if you wait long enough. Memory goes back to normal too but for at least two minutes the machine becomes unuseable. Adding perf keyword, loading the site is a real pain.
Severity: critical → normal
Keywords: perf
Summary: Memory goes from 16Mb to 250 Mb in less then a second → Perf problem, memory jumps from 16 MB to 250+ MB
This is still true with build ID 2003021008. Adding qawanted in order to have this bug changed from unconfirmed to NEW.
Keywords: qawanted
ccing Perf and footprint people.
Even on Mac OS X.....the same...Mozilla 13b
Hardware: PC → All
Some sampling shows lots of time spent in JS -- js_FoldConstants is being called recursively.
Status: UNCONFIRMED → NEW
Ever confirmed: true
The page document.writes a huge list of string literals into a <style> tag.
Where's a reduced testcase? /be
coming right up...
Attached file Testcase (deleted) —
Attached file Testcase (JS shell version) (obsolete) (deleted) —
Using current trunk on WinNT4.0, 500MHz CPU, 128M RAM. The reduced testcases only contain JS string concatenation, no document.write() or other DOM methods. My box cannot load either the HTML testcase nor the JS testcase. All available RAM is used up. In each case, the Windows alertbox comes up, "Your system is running low on memory...". With the HTML testcase, I have to kill Mozilla to prevent the box from crashing. The JS testcase terminates with an "out of memory" error in the JS shell. By contrast, IE6 loads the HTML testcase quickly and without using up much RAM. When I loaded the testcase, the WinNT Task Manager showed my available RAM decreasing from 51K to 49K. The load took just a couple seconds or so. This bug is probably a duplicate of one already on file for JS string concatenation issues. I will look into that; in the meantime, let me reassign this to the JS Engine component.
Assignee: asa → khanson
Component: Browser-General → JavaScript Engine
QA Contact: asa → pschwartau
Summary: Perf problem, memory jumps from 16 MB to 250+ MB → JS string concatenation perf problem, memory jumps from 16 MB to 250+ MB
This same issue was raised in bug 56940, "O(n**2) and O(n**3) growth too easy with JS string concat" However, that bug was fixed and verified a long time ago. Therefore, I'm holding this bug open and adding it as a dependency to the meta-bug for JS performance, bug 117611. The reporter in bug 56940 noted, "If you use the += operator, the string concatenation is actually rather quick. However, I don't see this method being used very often in web sites." And indeed, the website given in this bug, http://www.ericsson.fr, doesn't use that method. Instead, it does x = a + b + c + d + ... See bug 56940 comment 9 for Brendan's comments on this issue -
Blocks: js-perf
Oh, this is easily diagnosed, but perhaps harder to fix: js_FoldConstants tries to use the dependent/growable goodness introduced with the fix for bug 56940, but then frustrates itself by atomizing the result of each "foo" + "bar" literal concatenation (atomizing makes the resulting string immutable -- not growable as well as independent). The constant folder needs to defer atomizing string constants until it knows they can't be combined via a growable string. I'll attempt a patch. /be
Assignee: khanson → brendan
Keywords: js1.5
Priority: -- → P1
Target Milestone: --- → mozilla1.4alpha
Status: NEW → ASSIGNED
Attached file Testcase (working JS shell version) (deleted) —
navigator needed an appName property (I used value 'Netscape' ;-), and wanted to be an object initializer. /be
Attachment #115559 - Attachment is obsolete: true
Attached patch proposed fix (obsolete) (deleted) — Splinter Review
This patch looks bigger than it is only because the "Flatten a left-associative (left-heavy) tree"-commented code moved from the bottom of js_FoldConstants (way late) to NewBinary (conserve JSParseNodes early and often!), and because the in-line FoldBinaryNumeric code is now out-of-line in that subroutine. /be
Comment on attachment 115702 [details] [diff] [review] proposed fix I hope shaver has time to review this. Phil, my new RH8.0 seemed to disagree with the js testsuite on js1_5/Regress/regress-192465.js -- not sure why, but the process was chewing stack *slowly* -- it should run out soon enough, but I got tired of waiting. Can you run this patch through a full testsuite pass? Thanks. /be
Attachment #115702 - Flags: review?(shaver)
Comment on attachment 115702 [details] [diff] [review] proposed fix > + /* Any one string literal operand means this is a concatenation. */ > + JS_ASSERT(pn->pn_count > 2); > + for (pn2 = pn1; pn2; pn2 = pn2->pn_next) { > + if (pn2->pn_type == TOK_STRING) > + break; > + } Is it possible for us to track this state as we build the list in NewBinary, rather than iterating over it again? Maybe that's not a big problem, since our lists will tend to be small, but I thought I'd ask. Other than that, it looks good, but I want to read the patched version more closely before I stamp.
Attached patch alterna-patch per shaver's request (obsolete) (deleted) — Splinter Review
Shaver: like this? I aim to please :-). /be
Comment on attachment 115717 [details] [diff] [review] alterna-patch per shaver's request That's what I'm talking about, right there. r=shaver.
Attachment #115717 - Flags: review+
Attachment #115702 - Flags: review?(shaver)
I made a pn_strcat alias for pn_extra and commented the heck out of it, and commented the left-assoc-binary-tree => list optimization, in jsparse.h. Shaver, can you re-stamp? Thanks, /be
Attachment #115702 - Attachment is obsolete: true
Attachment #115717 - Attachment is obsolete: true
Attachment #115777 - Flags: review?(shaver)
The latest patch (Comment #21) passes the JS testsuite both on my Linux box (RedHat7.2) and my WinNT box. I ran the tests against both the debug and optimized JS shells, with the patch applied. No test regressions were observed. Furthermore, there is a spectacular improvement on the (corrected) JS shell testcase for this bug (Comment #15). It now completes in split-second time, and hardly uses any memory. Brendan: I don't know why js1_5/Regress/regress-192465.js bogs down for you on RedHat 8. I wonder if Perl is partly to blame? I wonder what happens if you run that test by hand, as in: [ ]./js -f /d/JS_TRUNK/mozilla/js/tests/js1_5/shell.js -f /d/JS_TRUNK/mozilla/js/tests/js1_5/Regress/regress-192465.js I get a segfault, almost immediately: Segmentation fault (core dumped) By contrast, when I allow the Perl test driver to run this test, I get no indication of failure. For some reason, my Perl process does not get informed of the segfault. I have to run this test by hand to get the true picture.
Comment on attachment 115777 [details] [diff] [review] better pn_extra alias and jsparse.h comments Comments! Descriptive aliases! It's like Christmas in February. Once more, with feeling: r=shaver.
Attachment #115777 - Flags: review?(shaver) → review+
Phil and I found the cause of the (unrelated to this bug's patch) RH7.2 vs. RH8 anomaly seen running the testsuite (comment 22 second half): under RH7.2, which Phil runs, ulimit -s says 8192; under RH8, I get "unlimited". The test at js1_5/Regress/regress-192465.js nests objects up to a million deep, and with an unlimited stack, the Object.prototype.toSource native implementation is going to run for a long, long time. Thanks, shaver -- checking in now with this essay as cvs log message: - Move left-associative binary tree flattening from the post-order position in js_FoldConstants where it was added (suboptimally: it worked, but it ran so late that js_FoldConstants recursion was not reduced, only js_EmitTree recursion), to NewBinary, where it avoids JSParseNode allocations up front and reduces recursion in all parse-tree walking. - This change enables js_FoldConstants to see a very long concatenation of string literals as a PN_LIST node, so it can quickly concatenate without running afoul of O(n^2) problems inherent in js_ConcatStrings applied to two atomized strings (the old way of folding string concatenations, still used for any pairwise string literal concatenation). - Further optimize the first change for the second by having NewBinary set a new pn_strcat flag (overlaying the pn_extra field) of the PN_LIST arm of the JSParseNode.pn_u union, whenever it sees at least one string literal in a concatenation that might be folded (whose operands might all be constants of string or number type). - Notes: - only string and number constants are folded (not boolean or null constants); - only all-constant left-associated binary expression chains are folded, so 2 * foo * 3 is not folded using commutativity of * over numbers, nor is "hi" + " there" + foo folded to "hi there" + foo. - gcc3.2 -O and objdump -x say I added 708 bytes of instructions with this change. I tried to keep it down to what was necessary for common script; I don't think JS needs an optimizing-compiler-strength constant folder, and I don't think this 1K bloat is too high a price to pay for this fix. But I'll certainly work on reducing footprint elsewhere, if I can. - Bug 174341, r=shaver. /be
Status: ASSIGNED → RESOLVED
Closed: 22 years ago
Resolution: --- → FIXED
Brendan had a good idea: try to make the JS test driver limit the JS stack size to 8192, so that systems like RedHat8 won't churn forever on testcases like js1_5/Regress/regress-192465.js. However, there doesn't seem to be a direct interface between Perl and the |ulimit| command. The usual backticks don't invoke this command successfully from Perl. Apparently there is a workaround, but it would require all users of jsDriver.pl to download a non-standard Perl module called BSD::Resource. In the meantime, I will add an early return to that test until I can find the best solution to this problem - REFERENCES: How do you set ulimit -n 5000 in Perl? http://www.experts-exchange.com/Programming/Programming_Languages/Perl/Q_10224927.html Reply: http://www.experts-exchange.com/Programming/Programming_Languages/Perl/Q_10224927.html#1
There may be a way to detect the presence of a module with Perl. If so, we could add a --stacksize argument to jsDriver.pl that is only valid if BSD::Resource is installed. Existing users wouldn't be affected at all. Unfortunately I don't know how to recover from a "use" failure. |use Dave || die "Dave's not here";| throws a syntax error.
Verified FIXED. In addition to the verifications in the JS shell (Comment #22), I tested today's Mozilla builds on WinNT, Linux, and Mac OSX. I was able to load the HTML testcase quickly, and also the URL for this bug, http://www.ericsson.fr. The load time was roughly the same as with IE6.
Status: RESOLVED → VERIFIED
This bug's patch regressed bug 196290. :-( /be
Flags: testcase+
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: