Closed
Bug 33390
Opened 25 years ago
Closed 25 years ago
memory exhausted with javascript array
Categories
(Core :: JavaScript Engine, defect, P1)
Core
JavaScript Engine
Tracking
()
RESOLVED
FIXED
mozilla0.8
People
(Reporter: wrobell, Assigned: brendan)
References
(Blocks 1 open bug)
Details
(Keywords: js1.5)
Attachments
(14 files)
|
301.93 KB,
application/octet-stream
|
Details | |
|
203 bytes,
text/plain
|
Details | |
|
189 bytes,
text/plain
|
Details | |
|
182 bytes,
text/plain
|
Details | |
|
50.42 KB,
patch
|
Details | Diff | Splinter Review | |
|
56.47 KB,
patch
|
Details | Diff | Splinter Review | |
|
57.70 KB,
patch
|
Details | Diff | Splinter Review | |
|
66.16 KB,
patch
|
Details | Diff | Splinter Review | |
|
66.01 KB,
patch
|
Details | Diff | Splinter Review | |
|
57.69 KB,
patch
|
Details | Diff | Splinter Review | |
|
71.23 KB,
patch
|
Details | Diff | Splinter Review | |
|
68.82 KB,
patch
|
Details | Diff | Splinter Review | |
|
66.31 KB,
patch
|
Details | Diff | Splinter Review | |
|
65.79 KB,
patch
|
Details | Diff | Splinter Review |
From Bugzilla Helper:
User-Agent: Mozilla/4.71 [en] (X11; I; Linux 2.3.47 i586; Nav)
BuildID: 2000032309
Script below generates HTML file with JavaScript script,
which creates table with $j*$i elements. When $j equals
to 25 (at $i=256) then mozilla grows to about 48MB.
Increasing $j to ie. 255 causes mozilla to exhaust
system memory (if limits are unlimited of course).
The same thing is with netscape.
I know about this bug from my friend Robert Kurowski.
---------------------------------------
#!/usr/bin/perl
$j=256;
print <<END;
<html>
<head>
<title>JavaScript Test</title>
<script type="text/javascript"><!--
END
while ($j--) {
$i=256;
while ($i--) {
print "x[$j][$i]='".$i*$j."';\n";
}
}
print <<END;
</script>
</head>
<body>
<p>
OK!
</p>
</body>
</html>
END
-------------------------------------
Reproducible: Always
Steps to Reproduce:
Look at description field.
Actual Results: Memory system exhausted.
Expected Results: Show "OK" string.
I get this too (linuxppc buildid 2000041919). Memory usage went all the way up
to 135Megs then I ran out and the system locked up. Netscape on the other hand
gets by with a svelte 13Megs no sweat. This looks pretty bad. Someone with
enough priveledges should change platform/OS to "all" and confirm it. I'm not
gonna attach the generated file because it's way too big (1.2megs) although I
will attach a zipp'ed version for those of you without perl (I pity you... :) ).
change Platform and OS to "All" (bug was found on windows at all)
OS: Linux → All
Hardware: PC → All
Comment 5•25 years ago
|
||
Confirming, as several people have seen it.
Gerv
Status: UNCONFIRMED → NEW
Ever confirmed: true
Comment 7•25 years ago
|
||
Phil, can you see if this is still happening? I know brendan's done some work in
this area...
Assignee: rogerl → pschwartau
Comment 9•25 years ago
|
||
I don't see memory increase as before. Using the the linux build from
08/14/2000.
Comment 10•25 years ago
|
||
Comment 11•25 years ago
|
||
Changing component to Browser-General (see comments below)
Assignee: pschwartau → asa
Component: Javascript Engine → Browser-General
QA Contact: pschwartau → doronr
Comment 12•25 years ago
|
||
Using commercial binary 2000092320 on WinNT; comparing to
optimized JS shell built from source 2000-09-21 on WinNT.
If I run the standalone JS shell testcase above, here is what I find.
Beginning with memory usage of 91M/259M, my memory usage proceeded:
91M ---> 115M ---> 91M --> 94M (linearly)
The downturn in memory usage happened when the outer loop variable
had decremented from 256 to approximately 50. Suddenly I could see
memory being freed and going down to 91M again, and then increasing.
With the browser, however, the memory usage was like this:
103M ---------------------------------> 259M (non-linearly)
Memory usage climbed without pause, and accelerated in a non-linear way.
The page never finished loading, and I got a warning from the Windows OS
that memory had been exhausted.
These results show, I believe, that this bug is a problem not of the
JS engine itself, but of the browser embedding of the engine.
Therefore reassigning to Warren for further analysis - perhaps this bug
is even a duplicate of another; I was unable to find it, however.
Assignee: asa → warren
Comment 14•25 years ago
|
||
No no no.
This has nothing to do with large arrays. No arrays get created. The first line
of the test case causes a JS error.
The problem here is that the compiler (in the JS engine) is choking on the
content of the 64k lines of input. It is deep in jsemit.c allocing like crazy
when it passes the 200meg mark.
Phil, you misinterpreted the initial code sample. This was perl code to generate
JS source. It was not intended to be translated into JS, but run to generate JS
code. See the zipped html file that Gwyn Judd attached. Strip off the html parts
at the front and back and run that in jsshell and see what you get! I ran it in
xpcshell and got the same memory explosion as in the browser.
Brendan & Roger, do we consider this something that ought to be fixable, or just
a "don't do that!" sort of thing? I'm wondering things like: Can the parse tree
be ripped up as we go? (currently all in one arena, right?) AND Isn't this a
pretty darn uniform pattern of code that ought to be rather optimizable down to
a much smaller bytecode array?
Assignee: jband → brendan
Component: Browser-General → Javascript Engine
Comment 15•25 years ago
|
||
jband is correct. When I run Gwyn's testcase at 2000-04-27 01:00 above,
I run out of virtual memory in Mozilla and in the standalone JS shell.
The N4.7 browser, however, handles it without any apparent problems.
| Assignee | ||
Updated•25 years ago
|
| Assignee | ||
Updated•25 years ago
|
Target Milestone: mozilla0.9 → mozilla0.8
| Assignee | ||
Updated•25 years ago
|
Keywords: mozilla0.9 → mozilla0.8
Comment 17•25 years ago
|
||
Looks like this person ran into the same problem...
http://www.deja.com/%5BST_rn=ps%5D/getdoc.xp?AN=706292446
He's seeing a crash with...
Data = new Array (
(new Array("2197","745185","34")),
(new Array("6591","1858354","34")),
// and so on for something like 2500 lines
);
| Assignee | ||
Comment 18•25 years ago
|
||
Several bad things: jsemit.c has code that grows two kinds of arena-based
allocations (bytecode, srcnotes) by a constant increment, resulting in O(n**2)
space growth; jsparse.c didn't recycle parsenode so as to avoid growing its arenas.
Patch coming up, but first, I'll attach my perl testcase generators, the first
based on wrobell's perl, the second an improvement of it that results in fewer
than half the bytecodes as the first.
/be
| Assignee | ||
Comment 19•25 years ago
|
||
| Assignee | ||
Comment 20•25 years ago
|
||
| Assignee | ||
Comment 21•25 years ago
|
||
Here comes the patch that reduces memory for the output of the first attached
testcase generator (the one that uses new Array and x[i][j] = ...) as follows:
Before patch:
Parser growth: 26480640 (526086 nodes, 23147784 bytes)
Code-gen growth: 299008 (984842 bytecodes, 329222 srcnotes)
After patch:
Parser growth: 315392 (526086 nodes, 9 max, 1 unrecycled)
Code-gen growth: 0 (984842 bytecodes, 329221 srcnotes)
The growth is bytes measured by sbrk(0); the "Before patch" line for Parser
growth shows the total node count and consequent size (net of arena header and
rounding), given 44 bytes per JSParseNode.
Here is the output of a patched js shell given input generated by the second
(array initializer) testcase-generator patch:
Parser growth: 3620864 (65797 nodes, 65797 max, 1 unrecycled)
Code-gen growth: 0 (460297 bytecodes, 66308 srcnotes)
I didn't do a "Before patch" measurement because there isn't be much improvement
in Parser growth, as the giant array initializer is one big expression, and I
have not yet taught the compiler to emit code incrementally and recycle nodes
within a single expression statement. I'm not sure I will go that far, because
3.6M growth given a 587K .js file (~6x blowup from source to in-memory data) is
not as terrible as the problem this bug reports.
Jband, I'm not at all sure what is causing the nested Array constructor call
case you found in that deja.com posting. I need a testcase. Anyone have one?
/be
| Assignee | ||
Comment 22•25 years ago
|
||
| Assignee | ||
Comment 23•25 years ago
|
||
Proposed fix's cvs commit log message:
- Optimize compile (parse+emit) operation to generate code for each top-level
statement or function in turn, recycling JSParseNodes as we go for greatly
reduced footprint.
- Fix O(n**2) growth problems in bytecode and srcnote generation.
- Add js_ParseTokenStream entry point to compiler, for tree-generation without
code-generation. Move JSOP_EVAL instruction selection from code-generator to
parser, to match other such specializations and enable js_ParseTokenStream.
- Clean up bracing, multi-line conditions, and overlong lines.
/be
Comment 24•25 years ago
|
||
r=mccabe.
jsemit.c:2741
* accomodate either the first or second byte of additional storage
* required by this 3-byte offset.
*/
- if ((cg->noteCount + 1) % SNINCR <= 1) {
+ if (((cg->noteCount + 1) & cg->noteMask) <= 1) {
if (!GrowSrcNotes(cx, cg))
return JS_FALSE;
sn = cg->notes + index;
could this be a check against 0 with a different bit operation?
Does 'top level statement' mean top level within the js script? If some
function contains many statements, do we still bloat?
I've long wondered if something like this wouldn't help Rhino compile speed.
| Assignee | ||
Comment 25•25 years ago
|
||
Oops, my first attachment
(http://bugzilla.mozilla.org/showattachment.cgi?attach_id=15683) has a 'var'
before x, which results in one extra unrecycled node. Just delete that var,
defined METER_PARSENODES in jsparse.c, and you should get numbers about the same
as the ones I posted.
mccabe writes:
>- if ((cg->noteCount + 1) % SNINCR <= 1) {
>+ if (((cg->noteCount + 1) & cg->noteMask) <= 1) {
> if (!GrowSrcNotes(cx, cg))
> return JS_FALSE;
> sn = cg->notes + index;
>
>could this be a check against 0 with a different bit operation?
it can't without signed modulus, which requires an extra branch of a full-on
divmod instruction to handle the negative case. We must handle the case where
cg->noteCount is 63, cg->noteMask is 63, and we need to grow cg->notes to fit
two more bytes (at indexes 63 and 64, resulting in a noteCount of 65). Note
that we must use a relational op, equality can't do the range test we need.
Note also how adding two and testing <= 2 won't work: it will wrongly grow the
notes vector when noteCount is 62.
I'm still not entirely happy with the way this code now allocates power-of-two
sized pieces from an arena: it seems too easy to overflow an arena, waste cycles
on a copy, and leave the old piece unusable. I think malloc/realloc are likely
to work better than arena-pools here, for any power of two > 32 or so. More
measurements in a bit.
/be
| Assignee | ||
Comment 26•25 years ago
|
||
Yup, JS_ARENA_GROW was evil: it grew "in place" (found more to accomodate the
new size in the nominally pool->arenasize arena) only about 1 in 6 calls. This
makes for yet another O(n**2) space explosion. I fixed it to realloc the arena
if the allocation being grown consumed the arena's entire payload (minus slop
for alignment). Now the numbers look really good, with arena stats dumped after
(JS_ARENAMETER defined and JS_DumpArenaStats(stdout) called from jsparse.c after
the last METER_PARSENODES printf):
Parser growth: 40960 (526086 nodes, 9 max, 1 unrecycled)
Code-gen growth: 0 (984842 bytecodes, 329221 srcnotes)
temp allocation statistics:
number of arenas: 16
number of allocations: 533
number of free arena reclaims: 0
number of malloc calls: 16
number of deallocations: 0
number of allocation growths: 0
number of in-place growths: 0
number of extending growths: 0
number of released allocations: 0
number of fast releases: 0
total bytes allocated: 18112
mean allocation size: 33.9812
standard deviation: 206.291
maximum allocation size: 4096
note allocation statistics:
number of arenas: 1
number of allocations: 1
number of free arena reclaims: 0
number of malloc calls: 1
number of deallocations: 0
number of allocation growths: 13
number of in-place growths: 2
number of extending growths: 11
number of released allocations: 0
number of fast releases: 0
total bytes allocated: 524288
mean allocation size: 524288
standard deviation: 524288
maximum allocation size: 524288
code allocation statistics:
number of arenas: 1
number of allocations: 1
number of free arena reclaims: 0
number of malloc calls: 1
number of deallocations: 0
number of allocation growths: 12
number of in-place growths: 2
number of extending growths: 10
number of released allocations: 0
number of fast releases: 0
total bytes allocated: 1048576
mean allocation size: 1.04858e+06
standard deviation: 1.04858e+06
maximum allocation size: 1048576
stack allocation statistics:
number of arenas: 0
number of allocations: 0
number of free arena reclaims: 0
number of malloc calls: 0
number of deallocations: 0
number of allocation growths: 0
number of in-place growths: 0
number of extending growths: 0
number of released allocations: 0
number of fast releases: 0
total bytes allocated: 0
mean allocation size: 0
standard deviation: 0
maximum allocation size: 0
gc-arena allocation statistics:
number of arenas: 1
number of allocations: 748
number of free arena reclaims: 0
number of malloc calls: 1
number of deallocations: 0
number of allocation growths: 0
number of in-place growths: 0
number of extending growths: 0
number of released allocations: 0
number of fast releases: 0
total bytes allocated: 5984
mean allocation size: 8
standard deviation: 0
maximum allocation size: 8
gc-arena allocation statistics:
number of arenas: 0
number of allocations: 1007
number of free arena reclaims: 0
number of malloc calls: 1
number of deallocations: 0
number of allocation growths: 0
number of in-place growths: 0
number of extending growths: 0
number of released allocations: 0
number of fast releases: 0
total bytes allocated: 8056
mean allocation size: 8
standard deviation: 0
maximum allocation size: 8
Note that now, all non-in-place growths are "extending" growths, which means
realloc calls. That frees the old arena in the case that in-place growth in the
malloc heap is not possible, and malloc then recycles the old arena's space
quite well, as the "Parser Growth" figure shows (41K, down from 315K!). Even
the second testcase-generator's output (using array initializers) improves from
3.62M to 3.48M due to better malloc recycling of the evacuated realloc space.
The "extends" case really makes an arena pool useful as a malloc/realloc device
where the pool keeps track of all allocations and can free them _en masse_ or
LIFO. It's not the cheapest way to so track memory, at 16 bytes per arena
header, but it's cheap enough and it unifies mechanism to fix JS_ARENA_GROW to
handle this "extends" case and not purvey an O(n**2) hazard.
Revised patch coming up.
/be
| Assignee | ||
Comment 27•25 years ago
|
||
| Assignee | ||
Comment 28•25 years ago
|
||
| Assignee | ||
Comment 29•25 years ago
|
||
mccabe also wrote:
>Does 'top level statement' mean top level within the js script? If some
>function contains many statements, do we still bloat?
No bloat -- top-level statements in a function body are emitted one at a time.
The top-level test is whether the statement-info stack is empty (wherefore the
removal of the redundant TCF_TOP_LEVEL flag, whose bit was reclaimed by the new
TCF_COMPILING flag in JSTreeContext -- this new flag tells us we can safely
downcast tc to (JSCodeGenerator *) and emit as we go).
I think the last patch is "it", for now. If someone thinks that we really need
finer-grained code-gen/parser interleaving, a separate bug with testcase should
be filed. Looking for fresh r= and sr= from someone (shaver or jband).
/be
| Assignee | ||
Comment 30•25 years ago
|
||
Fear not: I just removed this line
+#define METER_PARSENODES /* XXXbe don't check me in! */
from jsparse.c. Sorry for leaving it in the patches.
/be
| Assignee | ||
Comment 31•25 years ago
|
||
Oops, I lied: functions containing gazillions of statements will parse into
whole trees, which are then walked by the code generator. This is required for
now by several data dependencies in jsemit.c (e.g., pn_tryCount), but I think I
can fix those to cope with TCF_COMPILING. Might do one more patch, please feel
free to r= anyway.
/be
| Assignee | ||
Comment 32•25 years ago
|
||
| Assignee | ||
Comment 33•25 years ago
|
||
| Assignee | ||
Comment 34•25 years ago
|
||
Comment 35•25 years ago
|
||
I think something's slightly busted about the patch; I get
} else { \
p = (type) JS_ArenaGrow(pool, p, size, incr); \
} \
} else { \
p = (type) JS_ArenaGrow(pool, p, size, incr); \
} \
in jsarena.h
Comment 36•25 years ago
|
||
n/m, I see how the curlies line up. Pesky tabs.
| Assignee | ||
Comment 37•25 years ago
|
||
Yeah -- I expanded tabs in the macro bodies I touched, just to keep my sanity.
See last two patches.
/be
Comment 38•25 years ago
|
||
r=mccabe
JS_ArenaExtend and JS_ArenaGrow sound a little too synonymous; maybe a comment?
Or are there more descriptive names?
I'm not sure I understand the new js_PushStatement pairs near TOK_TRY and
TOK_FINALLY.
| Assignee | ||
Comment 39•25 years ago
|
||
Those JS_Arena* functions are not to be called directly, but I'll noodle on
better names.
The missing Push/PopStatement brackets meant that those calls to Statements from
top-level try and finally blocks thought they were at top level (!tc->topStmt),
which means when TCF_COMPILING is set, emit code as we go -- which scrambles the
try block's code with its JSOP_TRY (which comes out after instead of before the
try block code), e.g.
/be
| Assignee | ||
Comment 40•25 years ago
|
||
Still craving that ever-lovin' sr=jband or sr=shaver.
New patch coming up, trivial change to keep track of previously declared consts
(or functions or vars, in case strict and for the TCF_FUN_VS_VAR deoptimization
required by ECMA) without holding onto unrecyclable JSParseNodes.
The previous patches know in RecycleTree to return early after recycling a
function's body, or a variable list, to avoid recycling the function node or var
list node itself. By keeping a JSOp in the tc->decls atom-list, instead of the
whole darn JSParseNode, we can recycle node trees uniformly, without special
cases, recursively calling RecycleTree according to node arity.
/be
| Assignee | ||
Comment 41•25 years ago
|
||
| Assignee | ||
Comment 42•25 years ago
|
||
Ok, *now* I'm done. Reviewers please review!
/be
Comment 43•25 years ago
|
||
I'm guessing nsnull isn't defined in .c code. This is cool reading.
+ if (nallocs != 0) {
is there a reason not to use if (nallocs) [i'm guessing some wierd clarity]
if (((uintN)index & cg->noteMask) == 0) {
if (!left || !right)
which is more expensive, that or if (!(left && right)) ?
does the cost change for longer expressions:
if (!fp || fp->varobj != chain || fp->scopeChain != chain)
| Assignee | ||
Comment 44•25 years ago
|
||
>+ if (nallocs != 0) {
>is there a reason not to use if (nallocs) [i'm guessing some wierd clarity]
Usually I don't test scalars against zero implicitly, but sometimes I'm lazy.
In this case I'm being explicit, as nallocs is neither boolean nor a pointer.
> if (((uintN)index & cg->noteMask) == 0) {
Did you have a question about this line?
> if (!left || !right)
>which is more expensive, that or if (!(left && right)) ?
Let the optimizing compiler decide. It can do De Morgan's theorem as well as we
can, and it knows the instruction schedule and instruction costs better.
>does the cost change for longer expressions:
>if (!fp || fp->varobj != chain || fp->scopeChain != chain)
Only in terms of what you think is the most likely to be true (which
short-circuits || -- or false for short-circuiting &&). The most likely to be
true clauses should be at the front, if performance is critical. Usually (as is
the case here) it's not critical, but clarity and readability are.
/be
Comment 45•25 years ago
|
||
Applied patch (id=22207) on Linux and ran the test suite against both
the debug and optimized JS shells. No regressions were introduced -
| Assignee | ||
Comment 46•25 years ago
|
||
| Assignee | ||
Comment 47•25 years ago
|
||
| Assignee | ||
Comment 48•25 years ago
|
||
Comment 49•25 years ago
|
||
OK, finally finished reading a patch before /be posted a new one. =)
sr=shaver, now that I understand the js_ArenaRealloc caller-reports-OOM
behaviour.
Out of curiosity, how much of a win is this?
Keywords: review
| Assignee | ||
Comment 50•25 years ago
|
||
shaver: my comments dated 2000-12-27 16:13 show the win: from 27MB process data
segment growth down to ~315K, for the big bad array init testcase.
/be
| Assignee | ||
Comment 51•25 years ago
|
||
Fix checked in, thanks to reviewers.
/be
Status: ASSIGNED → RESOLVED
Closed: 25 years ago
Resolution: --- → FIXED
Updated•25 years ago
|
Keywords: mozilla0.8
You need to log in
before you can comment on or make changes to this bug.
Description
•