|
|
||
|
We are planning on creating an application that dynamically (without refreshing the page) creates and removes many domapi objects (buttons, listgrids, elms, and probably other objects). Mem usage VM size Blank page 16068 7400 Load button test page 23380 11432 Add 100 buttons 27308 14844 Remove 100 buttons 28304 15836 Add 100 buttons 31340 18868 Remove 100 buttons 32296 19820 Add 100 buttons 35120 22784 Remove 100 buttons 36092 23756 Add 100 buttons 39044 26696 Remove 100 buttons 39956 27604 Add 100 buttons 42992 30636 Remove 100 buttons 43980 31636 Add 100 buttons 47072 34716 Remove 100 buttons 48020 35660 Add 100 buttons 51032 38680 Remove 100 buttons 51996 39640 Add 100 buttons 55040 42680 Remove 100 buttons 55964 43632 Add 100 buttons 58956 46596 Remove 100 buttons 59964 47596 Add 100 buttons 62948 50600 Remove 100 buttons 63876 51582 Add 100 buttons 66988 54644 Remove 100 buttons 67868 55532 Currently to remove buttons I call: buttonToRemove.free(); buttonContainer.removeChild(buttonToRemove); buttonToRemove.remove(); Am I doing anything wrong? Is there anything else I can do to reduce memory usage? Here's my test page: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Button Example</title> <script type="text/javascript" src="src/domapi.js?styleBody=true&skin=true&theme=system"></script> <script type="text/javascript" src="tests/bin/examples.js"></script> <script type="text/javascript"> var buttonNumber = 1; domapi.loadUnit("button"); onload = function(){ exampleInit(); } function checkBags() { var freedBags = 0; for(var i =0; i < domapi.bags.elms.length;i++) { if(domapi.bags.elms[i].freed == true) {freedBags++;} } alert("total bags = " + domapi.bags.elms.length + ". freed bags = " + freedBags); } function add100Buttons() { for(var i = 0; i < 100; i++) { add1Button(); } } function delete100Buttons() { for(var i = 0; i < 100; i++) { delete1Button(); } } function deleteAllButtons() { var buttonContainer = document.getElementById("buttonContainer"); if(buttonContainer && buttonContainer.childNodes.length > 0) { while(buttonContainer.childNodes.length > 0) { var buttonToRemove = buttonContainer.childNodes[buttonContainer.childNodes.length - 1]; buttonToRemove.free(); buttonContainer.removeChild(buttonToRemove); buttonToRemove.remove(); } } } function delete1Button() { var buttonContainer = document.getElementById("buttonContainer"); if(buttonContainer && buttonContainer.childNodes.length > 0) { var buttonToRemove = buttonContainer.childNodes[buttonContainer.childNodes.length - 1]; buttonContainer.removeChild(buttonToRemove); buttonToRemove.free(); buttonToRemove.remove(); } } function add1Button() { var buttonContainer = document.getElementById("buttonContainer"); var button = domapi.Button({parent:buttonContainer,text: ("button" + buttonNumber)}); buttonNumber++; } </script> </head> <body> <script type="text/javascript">printHeader("Button")</script> <A href="javascript:void(null);" onclick="add1Button(); return false;">Add 1 button</a> <A href="javascript:void(null);" onclick="delete1Button(); return false;">Delete 1 button</a> |
||
|
|
|
|
||
|
I should have mentioned that I'm using IE6. |
||
|
|
|
|
||||
|
We're going to have to come up with a better version of free(). Currently DomAPI doesn't do any IE memory mop-up until after the page unloads. It simply isn't geared to creating and dropping lots of components during a session right now. I'm currently mulling this over, and what the consequences would be. It'll be probably be an optional switch somewhere so as to not hamper performance during normal usage. (normal usage being defined as building a UI during onload, and keeping it around until the page unloads.)
|
||||
|
|
|
|
||
|
I'd suggest using drip (http://www.outofhanwell.com/ieleak/) to track and then manually clean up any leaks. |
||
|
|
|
|
||
|
I'm a bit new to this - could you give me an example of one or two things in domapi that are preventing IE from releasing this memory, and why? I'm hoping to improve my ability to write memory-efficient javascript... |
||
|
|
|
|
||
|
Before getting to your issue, you also need to take into consideration that you don't have control on how the browser do its garbage collection. Even if you free/remove objects carefully, the memory may not go down instantly. For FF1.5, it won't go down at all until you exit the process. (FF IS A MEMORY HOG!!!) |
||
|
|
|
|
||
|
Thanks for your suggestions!
buttonContainer.removeChild(buttonToRemove);
buttonToRemove.free();
buttonToRemove.remove();
to be these lines:
buttonContainer.removeChild(buttonToRemove);
domapi._freeComponent(buttonToRemove);
buttonToRemove.remove();
then drip does not report any memory leaks. Perhaps the _free methods of each component should call _freeComponent/_freeElm to clean up any memory that they made when creating components/elms. Henry, I'm very interested in the purge function. Do you think that if I did this for each elm/component that I am done with that there should be no memory leaks? If so, what's to stop domapi from doing this everytime we call .free()? I think that in my case it will be much faster to have the javascript correctly free up old components using purge than it would be to reload the entire page. I understand that I have no control over when the garbage collector decides to run. As long as I have set everything up so that it can do its job I'll be happy for now. Later I'll investigate if there are some points in our application during which we could do a full page refresh without taking much of a performance hit. The reason we would like to reduce/eliminate full page rendering is that we can reduce network traffic, server load, and client load by responding with small XML messages. The client caches xml that describes the application's static or initial state and we just send field values and dynamic attributes that override the initial state. I reviewed the _unload method and I don't see how freeing/removing stuff as I go would disrupt this mechanism. Am I missing something? I should get a chance to try out the purge function tomorrow and post new numbers. |
||
|
|
|
|
||||
|
FYI, DomAPI was optimized against drip *long* ago. It shouldn't find any leaks outside of the weird ones we already know about (i.e Window).
|
||||
|
|
|
|
||
|
not too long, you just did the last fix for _iframeshield couple weeks ago *run* ... |
||
|
|
|
|
||
|
Adding and removing buttons from my first example causes leaks in drip. Load the test page, add a button, remove a button, click [Check Leaks] and you'll get 2 objects. To fix it I had to call domapi._freeComponent. I think that since we should only need to call .free(), that it's a leak worth fixing. |
||
|
|
|
|
||
|
Well, my first attempt at using purge doesn't look good. Here are some new numbers:
Mem usage VM size
Blank page 16284 7388
Load button test page 23560 11476
Add 100 buttons 27416 14852
Remove 100 buttons 35336 22752
Add 100 buttons 38232 25644
Remove 100 buttons 46072 33468
Add 100 buttons 48640 36184
Remove 100 buttons 56548 44080
Add 100 buttons 59436 46980
Remove 100 buttons 67164 54704
As you can see, freeing with purge causes IE to take up significantly more memory (at least until the page is unloaded). After talking with Henry on IRC it seems like the best solution is to keep parts of the page (the ones in which we plan on adding and removing components) in iframes. When we remove the iframes not only can domapi perform it's cleanup, but IE can perform its own cleanup. No worrying about IE's garbage collector not firing when we want it to because it seems to happen when closing a page. Thanks for your help everyone! |
||
|
|
|
|
||
|
If you're interested, this is the code change that I made to use purge:
function purge(d) {
var a = d.attributes, i, l, n;
if (a) {
l = a.length;
for (i = 0; i < l; i += 1) {
n = a[i].name;
if (typeof d[n] === 'function') {
d[n] = null;
}
}
}
a = d.childNodes;
if (a) {
l = a.length;
for (i = 0; i < l; i += 1) {
purge(d.childNodes[i]);
}
}
}
function delete1Button()
{
var buttonContainer = document.getElementById("buttonContainer");
if(buttonContainer && buttonContainer.childNodes.length > 0)
{
var buttonToRemove = buttonContainer.childNodes[buttonContainer.childNodes.length - 1];
purge(buttonToRemove);
buttonContainer.removeChild(buttonToRemove);
buttonToRemove.free();
buttonToRemove.remove();
}
}
|
||
|
|
