[compiler] finalize compile jobs asap when compiling on main thread
Previously, we finalize all compile jobs at once. This keeps the zone memory in every compile job alive until the end. This contributes to a high peak memory when many functions are compiled eagerly, for example when producing cache data for the ServiceWorker cache. Memory tracked by the AccountingAllocator in bytes, prior to this change in the test case: peak memory after init: 8192 peak memory after lazy compile: 41200 peak memory after lazy compile: 41200 peak memory after eager compile: 164256 With this change, if we are compiling on the main thread, we finalize every compile job as soon as it is done and dispose the compile job and its zone memory. After this change: peak memory after init: 8192 peak memory after lazy compile: 41200 peak memory after lazy compile: 41200 peak memory after eager compile: 41376 R=leszeks@chromium.org, rmcilroy@chromium.org Bug: chromium:901329 Change-Id: Iae0c89396c89692c4ecdeec3970d3c62031d2bce Reviewed-on: https://chromium-review.googlesource.com/c/1322949 Commit-Queue: Yang Guo <yangguo@chromium.org> Reviewed-by: Toon Verwaest <verwaest@chromium.org> Reviewed-by: Leszek Swirski <leszeks@chromium.org> Cr-Commit-Position: refs/heads/master@{#57340}
This commit is contained in:
parent
54ba9dee35
commit
0a7e08ef26
131
src/compiler.cc
131
src/compiler.cc
@ -6,6 +6,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
|
||||
#include "src/api-inl.h"
|
||||
#include "src/asmjs/asm-js.h"
|
||||
@ -497,6 +498,73 @@ std::unique_ptr<UnoptimizedCompilationJob> GenerateUnoptimizedCode(
|
||||
return outer_function_job;
|
||||
}
|
||||
|
||||
MaybeHandle<SharedFunctionInfo> GenerateUnoptimizedCodeForToplevel(
|
||||
Isolate* isolate, ParseInfo* parse_info, AccountingAllocator* allocator) {
|
||||
EnsureSharedFunctionInfosArrayOnScript(parse_info, isolate);
|
||||
parse_info->ast_value_factory()->Internalize(isolate);
|
||||
|
||||
if (!Compiler::Analyze(parse_info)) return MaybeHandle<SharedFunctionInfo>();
|
||||
DeclarationScope::AllocateScopeInfos(parse_info, isolate);
|
||||
|
||||
// Prepare and execute compilation of the outer-most function.
|
||||
// Create the SharedFunctionInfo and add it to the script's list.
|
||||
Handle<Script> script = parse_info->script();
|
||||
Handle<SharedFunctionInfo> top_level =
|
||||
isolate->factory()->NewSharedFunctionInfoForLiteral(parse_info->literal(),
|
||||
script, true);
|
||||
|
||||
std::queue<FunctionLiteral*> queue;
|
||||
queue.push(parse_info->literal());
|
||||
|
||||
while (!queue.empty()) {
|
||||
FunctionLiteral* literal = queue.front();
|
||||
queue.pop();
|
||||
Handle<SharedFunctionInfo> shared_info =
|
||||
Compiler::GetSharedFunctionInfo(literal, script, isolate);
|
||||
// TODO(rmcilroy): Fix this and DCHECK !is_compiled() once Full-Codegen dies
|
||||
if (shared_info->is_compiled()) continue;
|
||||
if (UseAsmWasm(literal, parse_info->is_asm_wasm_broken())) {
|
||||
std::unique_ptr<UnoptimizedCompilationJob> asm_job(
|
||||
AsmJs::NewCompilationJob(parse_info, literal, allocator));
|
||||
if (asm_job->ExecuteJob() == CompilationJob::SUCCEEDED &&
|
||||
FinalizeUnoptimizedCompilationJob(asm_job.get(), shared_info,
|
||||
isolate) ==
|
||||
CompilationJob::SUCCEEDED) {
|
||||
continue;
|
||||
}
|
||||
// asm.js validation failed, fall through to standard unoptimized compile.
|
||||
// Note: we rely on the fact that AsmJs jobs have done all validation in
|
||||
// the PrepareJob and ExecuteJob phases and can't fail in FinalizeJob with
|
||||
// with a validation error or another error that could be solve by falling
|
||||
// through to standard unoptimized compile.
|
||||
}
|
||||
|
||||
ZoneVector<FunctionLiteral*> eager_inner_literals(0, parse_info->zone());
|
||||
{
|
||||
std::unique_ptr<UnoptimizedCompilationJob> job(
|
||||
interpreter::Interpreter::NewCompilationJob(
|
||||
parse_info, literal, allocator, &eager_inner_literals));
|
||||
|
||||
if (job->ExecuteJob() == CompilationJob::FAILED ||
|
||||
FinalizeUnoptimizedCompilationJob(job.get(), shared_info, isolate) ==
|
||||
CompilationJob::FAILED) {
|
||||
return MaybeHandle<SharedFunctionInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
// Add eagerly compiled inner literals to the queue. The compilation order
|
||||
// is not important, so breadth-first traversal is not a requirement.
|
||||
for (FunctionLiteral* inner_literal : eager_inner_literals) {
|
||||
queue.push(inner_literal);
|
||||
}
|
||||
}
|
||||
|
||||
// Character stream shouldn't be used again.
|
||||
parse_info->ResetCharacterStream();
|
||||
|
||||
return top_level;
|
||||
}
|
||||
|
||||
bool FinalizeUnoptimizedCode(
|
||||
ParseInfo* parse_info, Isolate* isolate,
|
||||
Handle<SharedFunctionInfo> shared_info,
|
||||
@ -791,12 +859,32 @@ bool FailWithPendingException(Isolate* isolate, ParseInfo* parse_info,
|
||||
return false;
|
||||
}
|
||||
|
||||
void FinalizeScriptCompilation(Isolate* isolate, ParseInfo* parse_info) {
|
||||
Handle<Script> script = parse_info->script();
|
||||
script->set_compilation_state(Script::COMPILATION_STATE_COMPILED);
|
||||
|
||||
// Register any pending parallel tasks with the associated SFI.
|
||||
if (parse_info->parallel_tasks()) {
|
||||
CompilerDispatcher* dispatcher = parse_info->parallel_tasks()->dispatcher();
|
||||
for (auto& it : *parse_info->parallel_tasks()) {
|
||||
FunctionLiteral* literal = it.first;
|
||||
CompilerDispatcher::JobId job_id = it.second;
|
||||
MaybeHandle<SharedFunctionInfo> maybe_shared_for_task =
|
||||
script->FindSharedFunctionInfo(isolate, literal);
|
||||
Handle<SharedFunctionInfo> shared_for_task;
|
||||
if (maybe_shared_for_task.ToHandle(&shared_for_task)) {
|
||||
dispatcher->RegisterSharedFunctionInfo(job_id, *shared_for_task);
|
||||
} else {
|
||||
dispatcher->AbortJob(job_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaybeHandle<SharedFunctionInfo> FinalizeTopLevel(
|
||||
ParseInfo* parse_info, Isolate* isolate,
|
||||
UnoptimizedCompilationJob* outer_function_job,
|
||||
UnoptimizedCompilationJobList* inner_function_jobs) {
|
||||
Handle<Script> script = parse_info->script();
|
||||
|
||||
// Internalize ast values onto the heap.
|
||||
parse_info->ast_value_factory()->Internalize(isolate);
|
||||
|
||||
@ -817,26 +905,7 @@ MaybeHandle<SharedFunctionInfo> FinalizeTopLevel(
|
||||
return MaybeHandle<SharedFunctionInfo>();
|
||||
}
|
||||
|
||||
if (!script.is_null()) {
|
||||
script->set_compilation_state(Script::COMPILATION_STATE_COMPILED);
|
||||
}
|
||||
|
||||
// Register any pending parallel tasks with the associated SFI.
|
||||
if (parse_info->parallel_tasks()) {
|
||||
CompilerDispatcher* dispatcher = parse_info->parallel_tasks()->dispatcher();
|
||||
for (auto& it : *parse_info->parallel_tasks()) {
|
||||
FunctionLiteral* literal = it.first;
|
||||
CompilerDispatcher::JobId job_id = it.second;
|
||||
MaybeHandle<SharedFunctionInfo> maybe_shared_for_task =
|
||||
script->FindSharedFunctionInfo(isolate, literal);
|
||||
Handle<SharedFunctionInfo> shared_for_task;
|
||||
if (maybe_shared_for_task.ToHandle(&shared_for_task)) {
|
||||
dispatcher->RegisterSharedFunctionInfo(job_id, *shared_for_task);
|
||||
} else {
|
||||
dispatcher->AbortJob(job_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
FinalizeScriptCompilation(isolate, parse_info);
|
||||
|
||||
return shared_info;
|
||||
}
|
||||
@ -868,18 +937,17 @@ MaybeHandle<SharedFunctionInfo> CompileToplevel(ParseInfo* parse_info,
|
||||
parse_info->is_eval() ? "V8.CompileEval" : "V8.Compile");
|
||||
|
||||
// Generate the unoptimized bytecode or asm-js data.
|
||||
UnoptimizedCompilationJobList inner_function_jobs;
|
||||
std::unique_ptr<UnoptimizedCompilationJob> outer_function_job(
|
||||
GenerateUnoptimizedCode(parse_info, isolate->allocator(),
|
||||
&inner_function_jobs));
|
||||
if (!outer_function_job) {
|
||||
MaybeHandle<SharedFunctionInfo> shared_info =
|
||||
GenerateUnoptimizedCodeForToplevel(isolate, parse_info,
|
||||
isolate->allocator());
|
||||
if (shared_info.is_null()) {
|
||||
FailWithPendingException(isolate, parse_info,
|
||||
Compiler::ClearExceptionFlag::KEEP_EXCEPTION);
|
||||
return MaybeHandle<SharedFunctionInfo>();
|
||||
}
|
||||
|
||||
return FinalizeTopLevel(parse_info, isolate, outer_function_job.get(),
|
||||
&inner_function_jobs);
|
||||
FinalizeScriptCompilation(isolate, parse_info);
|
||||
return shared_info;
|
||||
}
|
||||
|
||||
std::unique_ptr<UnoptimizedCompilationJob> CompileOnBackgroundThread(
|
||||
@ -1875,10 +1943,7 @@ Handle<SharedFunctionInfo> Compiler::GetSharedFunctionInfo(
|
||||
|
||||
// If we found an existing shared function info, return it.
|
||||
Handle<SharedFunctionInfo> existing;
|
||||
if (maybe_existing.ToHandle(&existing)) {
|
||||
DCHECK(!existing->is_toplevel());
|
||||
return existing;
|
||||
}
|
||||
if (maybe_existing.ToHandle(&existing)) return existing;
|
||||
|
||||
// Allocate a shared function info object which will be compiled lazily.
|
||||
Handle<SharedFunctionInfo> result =
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "src/v8.h"
|
||||
|
||||
#include "src/api-inl.h"
|
||||
#include "src/compilation-cache.h"
|
||||
#include "src/compiler.h"
|
||||
#include "src/disasm.h"
|
||||
#include "src/heap/factory.h"
|
||||
@ -842,5 +843,73 @@ TEST(DeepEagerCompilation) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DeepEagerCompilationPeakMemory) {
|
||||
i::FLAG_always_opt = false;
|
||||
CcTest::InitializeVM();
|
||||
LocalContext env;
|
||||
v8::HandleScope scope(CcTest::isolate());
|
||||
v8::Local<v8::String> source = v8_str(
|
||||
"function f() {"
|
||||
" function g1() {"
|
||||
" function h1() {"
|
||||
" function i1() {}"
|
||||
" function i2() {}"
|
||||
" }"
|
||||
" function h2() {"
|
||||
" function i1() {}"
|
||||
" function i2() {}"
|
||||
" }"
|
||||
" }"
|
||||
" function g2() {"
|
||||
" function h1() {"
|
||||
" function i1() {}"
|
||||
" function i2() {}"
|
||||
" }"
|
||||
" function h2() {"
|
||||
" function i1() {}"
|
||||
" function i2() {}"
|
||||
" }"
|
||||
" }"
|
||||
"}");
|
||||
v8::ScriptCompiler::Source script_source(source);
|
||||
CcTest::i_isolate()->compilation_cache()->Disable();
|
||||
|
||||
v8::HeapStatistics heap_statistics;
|
||||
CcTest::isolate()->GetHeapStatistics(&heap_statistics);
|
||||
size_t peak_mem_1 = heap_statistics.peak_malloced_memory();
|
||||
printf("peak memory after init: %8zu\n", peak_mem_1);
|
||||
|
||||
v8::ScriptCompiler::Compile(env.local(), &script_source,
|
||||
v8::ScriptCompiler::kNoCompileOptions)
|
||||
.ToLocalChecked();
|
||||
|
||||
CcTest::isolate()->GetHeapStatistics(&heap_statistics);
|
||||
size_t peak_mem_2 = heap_statistics.peak_malloced_memory();
|
||||
printf("peak memory after lazy compile: %8zu\n", peak_mem_2);
|
||||
|
||||
v8::ScriptCompiler::Compile(env.local(), &script_source,
|
||||
v8::ScriptCompiler::kNoCompileOptions)
|
||||
.ToLocalChecked();
|
||||
|
||||
CcTest::isolate()->GetHeapStatistics(&heap_statistics);
|
||||
size_t peak_mem_3 = heap_statistics.peak_malloced_memory();
|
||||
printf("peak memory after lazy compile: %8zu\n", peak_mem_3);
|
||||
|
||||
v8::ScriptCompiler::Compile(env.local(), &script_source,
|
||||
v8::ScriptCompiler::kEagerCompile)
|
||||
.ToLocalChecked();
|
||||
|
||||
CcTest::isolate()->GetHeapStatistics(&heap_statistics);
|
||||
size_t peak_mem_4 = heap_statistics.peak_malloced_memory();
|
||||
printf("peak memory after eager compile: %8zu\n", peak_mem_4);
|
||||
|
||||
CHECK_LE(peak_mem_1, peak_mem_2);
|
||||
CHECK_EQ(peak_mem_2, peak_mem_3);
|
||||
CHECK_LE(peak_mem_3, peak_mem_4);
|
||||
// Check that eager compilation does not cause significantly higher (+25%)
|
||||
// peak memory than lazy compilation.
|
||||
CHECK_LE(peak_mem_4 - peak_mem_3, peak_mem_3 / 4);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
Loading…
Reference in New Issue
Block a user