[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:
Yang Guo 2018-11-08 07:51:53 +01:00 committed by Commit Bot
parent 54ba9dee35
commit 0a7e08ef26
2 changed files with 167 additions and 33 deletions

View File

@ -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 =

View File

@ -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