зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1774796 - Add largest-first delazification strategy. r=arai
This patch adds a new off-thread delazification strategy, which adds a bit of overhead to queue function with a priority order, which put the largest function as the first functions to be delazified. The intent of this new strategy is to limit the usage of main-thread delazification to the smallest function, in case where the main-thread would win the delazification race. Differential Revision: https://phabricator.services.mozilla.com/D149651
This commit is contained in:
Родитель
8c721d23bb
Коммит
7d50cd5850
|
@ -1701,6 +1701,10 @@ nsresult ScriptLoader::AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest,
|
|||
TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
|
||||
"delazification_concurrent_depth_first");
|
||||
break;
|
||||
case JS::DelazificationOption::ConcurrentLargeFirst:
|
||||
TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
|
||||
"delazification_concurrent_large_first");
|
||||
break;
|
||||
case JS::DelazificationOption::ParseEverythingEagerly:
|
||||
TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(),
|
||||
"delazification_parse_everything_eagerly");
|
||||
|
|
|
@ -88,6 +88,11 @@ enum class AsmJSOption : uint8_t {
|
|||
*/ \
|
||||
_(ConcurrentDepthFirst) \
|
||||
\
|
||||
/* \
|
||||
* Delazifiy functions strating with the largest function first. \
|
||||
*/ \
|
||||
_(ConcurrentLargeFirst) \
|
||||
\
|
||||
/* \
|
||||
* Parse everything eagerly, from the first parse. \
|
||||
* \
|
||||
|
@ -266,12 +271,14 @@ class JS_PUBLIC_API TransitiveCompileOptions {
|
|||
bool forceStrictMode() const { return forceStrictMode_; }
|
||||
bool consumeDelazificationCache() const {
|
||||
return eagerDelazificationIsOneOf<
|
||||
DelazificationOption::ConcurrentDepthFirst>();
|
||||
DelazificationOption::ConcurrentDepthFirst,
|
||||
DelazificationOption::ConcurrentLargeFirst>();
|
||||
}
|
||||
bool populateDelazificationCache() const {
|
||||
return eagerDelazificationIsOneOf<
|
||||
DelazificationOption::CheckConcurrentWithOnDemand,
|
||||
DelazificationOption::ConcurrentDepthFirst>();
|
||||
DelazificationOption::ConcurrentDepthFirst,
|
||||
DelazificationOption::ConcurrentLargeFirst>();
|
||||
}
|
||||
bool waitForDelazificationCache() const {
|
||||
return eagerDelazificationIsOneOf<
|
||||
|
|
|
@ -16,6 +16,12 @@ const optionsLazyCache = {
|
|||
eagerDelazificationStrategy: "ConcurrentDepthFirst",
|
||||
};
|
||||
|
||||
const optionsLazyCache2 = {
|
||||
fileName: "compileToStencil-DATA.js",
|
||||
lineNumber: 1,
|
||||
eagerDelazificationStrategy: "ConcurrentLargeFirst",
|
||||
};
|
||||
|
||||
let result = 0;
|
||||
|
||||
function testMainThread(script_str) {
|
||||
|
@ -47,6 +53,18 @@ function testMainThreadCacheAll(script_str) {
|
|||
assertEq(result, 1);
|
||||
}
|
||||
|
||||
function testMainThreadCacheAll2(script_str) {
|
||||
if (isLcovEnabled() || helperThreadCount() === 0) {
|
||||
// Code-coverage implies forceFullParse = true, and as such it cannot be
|
||||
// used while testing to incrementally delazify.
|
||||
// Similarly, concurrent delazification requires off-threads processing.
|
||||
return;
|
||||
}
|
||||
const stencil = compileToStencil(script_str, optionsLazyCache2);
|
||||
result = evalStencil(stencil, optionsLazyCache2);
|
||||
assertEq(result, 1);
|
||||
}
|
||||
|
||||
function testOffThread(script_str) {
|
||||
const job = offThreadCompileToStencil(script_str, optionsFull);
|
||||
const stencil = finishOffThreadCompileToStencil(job);
|
||||
|
@ -109,6 +127,7 @@ for (let s = 0; s < 3000; s++) {
|
|||
testMainThread(code);
|
||||
testMainThreadDelazifyAll(code);
|
||||
testMainThreadCacheAll(code);
|
||||
testMainThreadCacheAll2(code);
|
||||
if (helperThreadCount() > 0) {
|
||||
testOffThread(code);
|
||||
}
|
||||
|
|
|
@ -43,6 +43,10 @@ struct FreeDelazifyTask;
|
|||
struct PromiseHelperTask;
|
||||
class PromiseObject;
|
||||
|
||||
namespace frontend {
|
||||
struct ScriptStencilRef;
|
||||
}
|
||||
|
||||
namespace jit {
|
||||
class IonCompileTask;
|
||||
class IonFreeTask;
|
||||
|
@ -609,6 +613,13 @@ struct DelazifyStrategy {
|
|||
// after this call.
|
||||
virtual void clear() = 0;
|
||||
|
||||
// Insert an index in the container of the delazification strategy. A strategy
|
||||
// can choose to ignore the insertion of an index in its queue of function to
|
||||
// delazify. Return false only in case of errors while inserting, and true
|
||||
// otherwise.
|
||||
[[nodiscard]] virtual bool insert(ScriptIndex index,
|
||||
frontend::ScriptStencilRef& ref) = 0;
|
||||
|
||||
// Add the inner functions of a delazified function. This function should only
|
||||
// be called with a function which has some bytecode associated with it, and
|
||||
// register functions which parent are already delazified.
|
||||
|
@ -616,9 +627,9 @@ struct DelazifyStrategy {
|
|||
// This function is called with the script index of:
|
||||
// - top-level script, when starting the off-thread delazification.
|
||||
// - functions added by `add` and delazified by `DelazifyTask`.
|
||||
[[nodiscard]] virtual bool add(JSContext* cx,
|
||||
const frontend::CompilationStencil& stencil,
|
||||
ScriptIndex index) = 0;
|
||||
[[nodiscard]] bool add(JSContext* cx,
|
||||
const frontend::CompilationStencil& stencil,
|
||||
ScriptIndex index);
|
||||
};
|
||||
|
||||
// Delazify all functions using a Depth First traversal of the function-tree
|
||||
|
@ -638,9 +649,23 @@ struct DepthFirstDelazification final : public DelazifyStrategy {
|
|||
bool done() const override { return stack.empty(); }
|
||||
ScriptIndex next() override { return stack.popCopy(); }
|
||||
void clear() override { return stack.clear(); }
|
||||
[[nodiscard]] bool add(JSContext* cx,
|
||||
const frontend::CompilationStencil& stencil,
|
||||
ScriptIndex index) override;
|
||||
bool insert(ScriptIndex index, frontend::ScriptStencilRef&) override {
|
||||
return stack.append(index);
|
||||
}
|
||||
};
|
||||
|
||||
// Delazify all functions using a traversal which select the largest function
|
||||
// first. The intent being that if the main thread races with the helper thread,
|
||||
// then the main thread should only have to parse small functions instead of the
|
||||
// large ones which would be prioritized by this delazification strategy.
|
||||
struct LargeFirstDelazification final : public DelazifyStrategy {
|
||||
using SourceSize = uint32_t;
|
||||
Vector<std::pair<SourceSize, ScriptIndex>, 0, SystemAllocPolicy> heap;
|
||||
|
||||
bool done() const override { return heap.empty(); }
|
||||
ScriptIndex next() override;
|
||||
void clear() override { return heap.clear(); }
|
||||
bool insert(ScriptIndex, frontend::ScriptStencilRef&) override;
|
||||
};
|
||||
|
||||
// Eagerly delazify functions, and send the result back to the runtime which
|
||||
|
|
|
@ -873,9 +873,9 @@ bool js::StartOffThreadDelazification(
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DepthFirstDelazification::add(JSContext* cx,
|
||||
const frontend::CompilationStencil& stencil,
|
||||
ScriptIndex index) {
|
||||
bool DelazifyStrategy::add(JSContext* cx,
|
||||
const frontend::CompilationStencil& stencil,
|
||||
ScriptIndex index) {
|
||||
using namespace js::frontend;
|
||||
ScriptStencilRef scriptRef{stencil, index};
|
||||
|
||||
|
@ -909,7 +909,8 @@ bool DepthFirstDelazification::add(JSContext* cx,
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!stack.append(innerScriptIndex)) {
|
||||
// Maybe insert the new script index in the queue of functions to delazify.
|
||||
if (!insert(innerScriptIndex, innerScriptRef)) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
@ -918,6 +919,75 @@ bool DepthFirstDelazification::add(JSContext* cx,
|
|||
return true;
|
||||
}
|
||||
|
||||
DelazifyStrategy::ScriptIndex LargeFirstDelazification::next() {
|
||||
std::swap(heap.back(), heap[0]);
|
||||
ScriptIndex result = heap.popCopy().second;
|
||||
|
||||
// NOTE: These are a heap indexes offseted by 1, such that we can manipulate
|
||||
// the tree of heap-sorted values which bubble up the largest values towards
|
||||
// the root of the tree.
|
||||
size_t len = heap.length();
|
||||
size_t i = 1;
|
||||
while (true) {
|
||||
// NOTE: We write (n + 1) - 1, instead of n, to explicit that the
|
||||
// manipualted indexes are all offseted by 1.
|
||||
size_t n = 2 * i;
|
||||
size_t largest;
|
||||
if (n + 1 <= len && heap[(n + 1) - 1].first > heap[n - 1].first) {
|
||||
largest = n + 1;
|
||||
} else if (n <= len) {
|
||||
// The condition is n <= len in case n + 1 is out of the heap vector, but
|
||||
// not n, in which case we still want to check if the last element of the
|
||||
// heap vector should be swapped. Otherwise heap[n - 1] represents a
|
||||
// larger function than heap[(n + 1) - 1].
|
||||
largest = n;
|
||||
} else {
|
||||
// n is out-side the heap vector, thus our element is already in a leaf
|
||||
// position and would not be moved any more.
|
||||
break;
|
||||
}
|
||||
|
||||
if (heap[i - 1].first < heap[largest - 1].first) {
|
||||
// We found a function which has a larger body as a child of the current
|
||||
// element. we swap it with the current element, such that the largest
|
||||
// element is closer to the root of the tree.
|
||||
std::swap(heap[i - 1], heap[largest - 1]);
|
||||
i = largest;
|
||||
} else {
|
||||
// The largest function found as a child of the current node is smaller
|
||||
// than the current node's function size. The heap tree is now organized
|
||||
// as expected.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LargeFirstDelazification::insert(ScriptIndex index,
|
||||
frontend::ScriptStencilRef& ref) {
|
||||
const frontend::ScriptStencilExtra& extra = ref.scriptExtra();
|
||||
SourceSize size = extra.extent.sourceEnd - extra.extent.sourceStart;
|
||||
if (!heap.append(std::pair(size, index))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOTE: These are a heap indexes offseted by 1, such that we can manipulate
|
||||
// the tree of heap-sorted values which bubble up the largest values towards
|
||||
// the root of the tree.
|
||||
size_t i = heap.length();
|
||||
while (i > 1) {
|
||||
if (heap[i - 1].first <= heap[(i / 2) - 1].first) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::swap(heap[i - 1], heap[(i / 2) - 1]);
|
||||
i /= 2;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
UniquePtr<DelazifyTask> DelazifyTask::Create(
|
||||
JSContext* cx, JSRuntime* runtime, const JS::ContextOptions& contextOptions,
|
||||
const JS::ReadOnlyCompileOptions& options,
|
||||
|
@ -989,6 +1059,11 @@ bool DelazifyTask::init(
|
|||
// inner functions before the siblings functions.
|
||||
strategy = cx->make_unique<DepthFirstDelazification>();
|
||||
break;
|
||||
case JS::DelazificationOption::ConcurrentLargeFirst:
|
||||
// ConcurrentLargeFirst visit all functions to be delazified, visiting the
|
||||
// largest function first.
|
||||
strategy = cx->make_unique<LargeFirstDelazification>();
|
||||
break;
|
||||
case JS::DelazificationOption::ParseEverythingEagerly:
|
||||
// ParseEverythingEagerly parse all functions eagerly, thus leaving no
|
||||
// functions to be parsed on demand.
|
||||
|
|
|
@ -3231,6 +3231,11 @@
|
|||
# 2: Depth-first. Delazify all functions off-thread in the order of appearance
|
||||
# in the source.
|
||||
#
|
||||
# 3: Large-first. Delazify all functions off-thread starting with the largest
|
||||
# functions first, and the smallest as the last one to be delazified, where
|
||||
# the size of function is measured in bytes between the start to the end of
|
||||
# the function.
|
||||
#
|
||||
# 255: Parse everything eagerly, from the first parse. All functions are parsed
|
||||
# at the same time as the top-level of a file.
|
||||
- name: dom.script_loader.delazification.strategy
|
||||
|
|
Загрузка…
Ссылка в новой задаче