Bug 539215 - Chunk long tree op queue flushes into multiple runnables in the HTML5 parser. r=bnewman.

--HG--
extra : rebase_source : 31f125d0e833aaea2c1bd4755c16d5e7fd5f2e83
This commit is contained in:
Henri Sivonen 2010-01-14 15:58:27 +02:00
Родитель 76f524a22a
Коммит cf81aa1f24
6 изменённых файлов: 138 добавлений и 43 удалений

Просмотреть файл

@ -2827,7 +2827,7 @@ pref("geo.enabled", true);
// Enable/Disable HTML5 parser
pref("html5.enable", false);
// Toggle which thread the HTML5 parser uses for streama parsing
// Toggle which thread the HTML5 parser uses for stream parsing
pref("html5.offmainthread", true);
// Time in milliseconds between the start of the network stream and the
// first time the flush timer fires in the off-the-main-thread HTML5 parser.
@ -2838,6 +2838,14 @@ pref("html5.flushtimer.continuedelay", 150);
// Time in milliseconds between timer firings once the timer has starting
// firing.
pref("html5.flushtimer.interval", 100);
// Initial max length for number of tree ops in on flush.
pref("html5.opqueue.initiallengthlimit", 200);
// Maximum time in milliseconds to spend flushing the tree op queue when not forced to completion
pref("html5.opqueue.maxtime", 100);
// Minimun number of tree ops to flush regardless of time (takes precedence over the maxtime pref)
pref("html5.opqueue.minlength", 100);
// Maximum number of tree ops to flush regardless of time (takes precedence over the maxtime pref)
pref("html5.opqueue.maxlength", 4500); // most top sites stay under this value
// Push/Pop/Replace State prefs
pref("browser.history.allowPushState", true);

Просмотреть файл

@ -72,6 +72,7 @@ nsHtml5Module::InitializeStatics()
nsHtml5TreeBuilder::initializeStatics();
nsHtml5UTF16Buffer::initializeStatics();
nsHtml5StreamParser::InitializeStatics();
nsHtml5TreeOpExecutor::InitializeStatics();
#ifdef DEBUG
sNsHtml5ModuleInitialized = PR_TRUE;
#endif

Просмотреть файл

@ -381,7 +381,7 @@ nsHtml5Parser::Parse(const nsAString& aSourceBuffer,
if (mTreeBuilder->HasScript()) {
// No need to flush characters, because an end tag was tokenized last
mTreeBuilder->Flush(); // Move ops to the executor
mExecutor->Flush(); // run the ops
mExecutor->Flush(PR_TRUE); // run the ops
}
if (mBlocked) {
// mExecutor->WillInterrupt();
@ -397,7 +397,7 @@ nsHtml5Parser::Parse(const nsAString& aSourceBuffer,
// Scripting semantics require a forced tree builder flush here
mTreeBuilder->flushCharacters(); // Flush trailing characters
mTreeBuilder->Flush(); // Move ops to the executor
mExecutor->Flush(); // run the ops
mExecutor->Flush(PR_TRUE); // run the ops
}
return NS_OK;
@ -492,7 +492,7 @@ nsHtml5Parser::ParseFragment(const nsAString& aSourceBuffer,
mTokenizer->eof();
mTreeBuilder->StreamEnded();
mTreeBuilder->Flush();
mExecutor->Flush();
mExecutor->Flush(PR_TRUE);
mTokenizer->end();
mExecutor->DropParserAndPerfHint();
mAtomTable.Clear();
@ -600,7 +600,7 @@ nsHtml5Parser::ParseUntilBlocked()
mTokenizer->eof();
mTreeBuilder->StreamEnded();
mTreeBuilder->Flush();
mExecutor->Flush();
mExecutor->Flush(PR_TRUE);
mTokenizer->end();
return;
} else {
@ -645,7 +645,7 @@ nsHtml5Parser::ParseUntilBlocked()
}
if (mTreeBuilder->HasScript()) {
mTreeBuilder->Flush();
mExecutor->Flush();
mExecutor->Flush(PR_TRUE);
}
if (mBlocked) {
// mExecutor->WillInterrupt();

Просмотреть файл

@ -133,7 +133,7 @@ class nsHtml5ExecutorFlusher : public nsRunnable
{}
NS_IMETHODIMP Run()
{
mExecutor->Flush();
mExecutor->Flush(PR_FALSE);
return NS_OK;
}
};

Просмотреть файл

@ -57,10 +57,6 @@
#include "nsHtml5TreeBuilder.h"
#include "nsHtml5StreamParser.h"
#define NS_HTML5_TREE_OP_EXECUTOR_MAX_QUEUE_TIME 3000UL // milliseconds
#define NS_HTML5_TREE_OP_EXECUTOR_DEFAULT_QUEUE_LENGTH 200
#define NS_HTML5_TREE_OP_EXECUTOR_MIN_QUEUE_LENGTH 100
NS_IMPL_CYCLE_COLLECTION_CLASS(nsHtml5TreeOpExecutor)
NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHtml5TreeOpExecutor)
@ -80,6 +76,41 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsHtml5TreeOpExecutor, nsContent
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mOwnedElements)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
PRInt32 nsHtml5TreeOpExecutor::sTreeOpQueueLengthLimit = 200;
PRInt32 nsHtml5TreeOpExecutor::sTreeOpQueueMaxTime = 100; // milliseconds
PRInt32 nsHtml5TreeOpExecutor::sTreeOpQueueMinLength = 100;
PRInt32 nsHtml5TreeOpExecutor::sTreeOpQueueMaxLength = 4500;
// static
void
nsHtml5TreeOpExecutor::InitializeStatics()
{
// Changes to the initial max length pref are intentionally allowed
// to reset the run-time-calibrated value.
nsContentUtils::AddIntPrefVarCache("html5.opqueue.initiallengthlimit",
&sTreeOpQueueLengthLimit);
nsContentUtils::AddIntPrefVarCache("html5.opqueue.maxtime",
&sTreeOpQueueMaxTime);
nsContentUtils::AddIntPrefVarCache("html5.opqueue.minlength",
&sTreeOpQueueMinLength);
nsContentUtils::AddIntPrefVarCache("html5.opqueue.maxlength",
&sTreeOpQueueMaxLength);
// Now do some sanity checking to prevent breaking the app via
// about:config so badly that it can't be recovered via about:config.
if (sTreeOpQueueMinLength <= 0) {
sTreeOpQueueMinLength = 200;
}
if (sTreeOpQueueLengthLimit < sTreeOpQueueMinLength) {
sTreeOpQueueLengthLimit = sTreeOpQueueMinLength;
}
if (sTreeOpQueueMaxLength < sTreeOpQueueMinLength) {
sTreeOpQueueMaxLength = sTreeOpQueueMinLength;
}
if (sTreeOpQueueMaxTime <= 0) {
sTreeOpQueueMaxTime = 200;
}
}
nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor()
{
// zeroing operator new for everything
@ -135,8 +166,7 @@ nsHtml5TreeOpExecutor::DidBuildModel(PRBool aTerminated)
printf("TOKENIZER-SAFE SCRIPTS: %d\n", sTokenSafeDocWrites);
printf("TREEBUILDER-SAFE SCRIPTS: %d\n", sTreeSafeDocWrites);
#endif
#ifdef DEBUG_hsivonen
printf("MAX INSERTION BATCH LEN: %d\n", sInsertionBatchMaxLength);
#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
printf("MAX NOTIFICATION BATCH LEN: %d\n", sAppendBatchMaxSize);
if (sAppendBatchExaminations != 0) {
printf("AVERAGE SLOTS EXAMINED: %d\n", sAppendBatchSlotsExamined / sAppendBatchExaminations);
@ -269,8 +299,23 @@ nsHtml5TreeOpExecutor::UpdateStyleSheet(nsIContent* aElement)
BeginDocUpdate();
}
class nsHtml5ExecutorReflusher : public nsRunnable
{
private:
nsRefPtr<nsHtml5TreeOpExecutor> mExecutor;
public:
nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor)
: mExecutor(aExecutor)
{}
NS_IMETHODIMP Run()
{
mExecutor->Flush(PR_FALSE);
return NS_OK;
}
};
void
nsHtml5TreeOpExecutor::Flush()
nsHtml5TreeOpExecutor::Flush(PRBool aForceWholeQueue)
{
if (!mParser) {
mOpQueue.Clear(); // clear in order to be able to assert in destructor
@ -294,14 +339,35 @@ nsHtml5TreeOpExecutor::Flush()
BeginDocUpdate();
PRIntervalTime flushStart = 0;
PRUint32 opQueueLength = mOpQueue.Length();
if (opQueueLength > NS_HTML5_TREE_OP_EXECUTOR_MIN_QUEUE_LENGTH) { // avoid computing averages with too few ops
flushStart = PR_IntervalNow();
PRUint32 numberOfOpsToFlush = mOpQueue.Length();
PRBool reflushNeeded = PR_FALSE;
#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
if (numberOfOpsToFlush > sOpQueueMaxLength) {
sOpQueueMaxLength = numberOfOpsToFlush;
}
mElementsSeenInThisAppendBatch.SetCapacity(opQueueLength * 2);
// XXX alloc failure
printf("QUEUE LENGTH: %d\n", numberOfOpsToFlush);
printf("MAX QUEUE LENGTH: %d\n", sOpQueueMaxLength);
#endif
if (aForceWholeQueue) {
if (numberOfOpsToFlush > (PRUint32)sTreeOpQueueMinLength) {
flushStart = PR_IntervalNow(); // compute averages only if enough ops
}
} else {
if (numberOfOpsToFlush > (PRUint32)sTreeOpQueueMinLength) {
flushStart = PR_IntervalNow(); // compute averages only if enough ops
if (numberOfOpsToFlush > (PRUint32)sTreeOpQueueLengthLimit) {
numberOfOpsToFlush = (PRUint32)sTreeOpQueueLengthLimit;
reflushNeeded = PR_TRUE;
}
}
}
mElementsSeenInThisAppendBatch.SetCapacity(numberOfOpsToFlush * 2);
const nsHtml5TreeOperation* start = mOpQueue.Elements();
const nsHtml5TreeOperation* end = start + opQueueLength;
const nsHtml5TreeOperation* end = start + numberOfOpsToFlush;
for (nsHtml5TreeOperation* iter = (nsHtml5TreeOperation*)start; iter < end; ++iter) {
if (NS_UNLIKELY(!mParser)) {
// The previous tree op caused a call to nsIParser::Terminate();
@ -311,22 +377,29 @@ nsHtml5TreeOpExecutor::Flush()
iter->Perform(this, &scriptElement);
}
#ifdef DEBUG_hsivonen
if (mOpQueue.Length() > sInsertionBatchMaxLength) {
sInsertionBatchMaxLength = opQueueLength;
if (NS_LIKELY(mParser)) {
mOpQueue.RemoveElementsAt(0, numberOfOpsToFlush);
} else {
mOpQueue.Clear(); // only for diagnostics in the destructor of this class
}
#endif
mOpQueue.Clear();
if (flushStart) {
PRUint32 delta = PR_IntervalToMilliseconds(PR_IntervalNow() - flushStart);
sTreeOpQueueMaxLength = delta ?
(PRUint32)((NS_HTML5_TREE_OP_EXECUTOR_MAX_QUEUE_TIME * (PRUint64)opQueueLength) / delta) :
0;
if (sTreeOpQueueMaxLength < NS_HTML5_TREE_OP_EXECUTOR_MIN_QUEUE_LENGTH) {
sTreeOpQueueMaxLength = NS_HTML5_TREE_OP_EXECUTOR_MIN_QUEUE_LENGTH;
sTreeOpQueueLengthLimit = delta ?
(PRUint32)(((PRUint64)sTreeOpQueueMaxTime * (PRUint64)numberOfOpsToFlush)
/ delta) :
sTreeOpQueueMaxLength; // if the delta is less than one ms, use max
if (sTreeOpQueueLengthLimit < sTreeOpQueueMinLength) {
// both are signed and sTreeOpQueueMinLength is always positive, so this
// also takes care of the theoretical overflow of sTreeOpQueueLengthLimit
sTreeOpQueueLengthLimit = sTreeOpQueueMinLength;
}
#ifdef DEBUG_hsivonen
printf("QUEUE MAX LENGTH: %d\n", sTreeOpQueueMaxLength);
if (sTreeOpQueueLengthLimit > sTreeOpQueueMaxLength) {
sTreeOpQueueLengthLimit = sTreeOpQueueMaxLength;
}
#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
printf("FLUSH DURATION (millis): %d\n", delta);
printf("QUEUE NEW MAX LENGTH: %d\n", sTreeOpQueueLengthLimit);
#endif
}
@ -334,12 +407,21 @@ nsHtml5TreeOpExecutor::Flush()
mFlushState = eNotFlushing;
if (!mParser) {
if (NS_UNLIKELY(!mParser)) {
return;
}
if (scriptElement) {
NS_ASSERTION(!reflushNeeded, "Got scriptElement when queue not fully flushed.");
RunScript(scriptElement); // must be tail call when mFlushState is eNotFlushing
} else if (reflushNeeded) {
#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
printf("REFLUSH SCHEDULED.\n");
#endif
nsCOMPtr<nsIRunnable> flusher = new nsHtml5ExecutorReflusher(this);
if (NS_FAILED(NS_DispatchToMainThread(flusher))) {
NS_WARNING("failed to dispatch executor flush event");
}
}
}
@ -579,9 +661,8 @@ nsHtml5TreeOpExecutor::InitializeDocWriteParserState(nsAHtml5TreeBuilderState* a
static_cast<nsHtml5Parser*> (mParser.get())->InitializeDocWriteParserState(aState, aLine);
}
PRUint32 nsHtml5TreeOpExecutor::sTreeOpQueueMaxLength = NS_HTML5_TREE_OP_EXECUTOR_DEFAULT_QUEUE_LENGTH;
#ifdef DEBUG_hsivonen
PRUint32 nsHtml5TreeOpExecutor::sInsertionBatchMaxLength = 0;
#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
PRUint32 nsHtml5TreeOpExecutor::sOpQueueMaxLength = 0;
PRUint32 nsHtml5TreeOpExecutor::sAppendBatchMaxSize = 0;
PRUint32 nsHtml5TreeOpExecutor::sAppendBatchSlotsExamined = 0;
PRUint32 nsHtml5TreeOpExecutor::sAppendBatchExaminations = 0;

Просмотреть файл

@ -78,14 +78,19 @@ class nsHtml5TreeOpExecutor : public nsContentSink,
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
static void InitializeStatics();
private:
#ifdef DEBUG_hsivonen
static PRUint32 sInsertionBatchMaxLength;
#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
static PRUint32 sOpQueueMaxLength;
static PRUint32 sAppendBatchMaxSize;
static PRUint32 sAppendBatchSlotsExamined;
static PRUint32 sAppendBatchExaminations;
#endif
static PRUint32 sTreeOpQueueMaxLength;
static PRInt32 sTreeOpQueueLengthLimit;
static PRInt32 sTreeOpQueueMaxTime;
static PRInt32 sTreeOpQueueMinLength;
static PRInt32 sTreeOpQueueMaxLength;
/**
* Whether EOF needs to be suppressed
@ -241,7 +246,7 @@ class nsHtml5TreeOpExecutor : public nsContentSink,
const nsIContentPtr* first = mElementsSeenInThisAppendBatch.Elements();
const nsIContentPtr* last = first + mElementsSeenInThisAppendBatch.Length() - 1;
for (const nsIContentPtr* iter = last; iter >= first; --iter) {
#ifdef DEBUG_hsivonen
#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
sAppendBatchSlotsExamined++;
#endif
if (*iter == aParent) {
@ -256,7 +261,7 @@ class nsHtml5TreeOpExecutor : public nsContentSink,
if (newParent) {
mPendingNotifications.AppendElement(aParent);
}
#ifdef DEBUG_hsivonen
#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
sAppendBatchExaminations++;
#endif
}
@ -270,7 +275,7 @@ class nsHtml5TreeOpExecutor : public nsContentSink,
iter->Fire();
}
mPendingNotifications.Clear();
#ifdef DEBUG_hsivonen
#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
if (mElementsSeenInThisAppendBatch.Length() > sAppendBatchMaxSize) {
sAppendBatchMaxSize = mElementsSeenInThisAppendBatch.Length();
}
@ -311,7 +316,7 @@ class nsHtml5TreeOpExecutor : public nsContentSink,
nsresult Init(nsIDocument* aDoc, nsIURI* aURI,
nsISupports* aContainer, nsIChannel* aChannel);
void Flush();
void Flush(PRBool aForceWholeQueue);
void MaybeSuspend();