1d726111ab
This is an implementation of https://bit.ly/v8-faster-microtask-queues step 2. This CL overhauls MicrotaskQueue class, the previous one is on V8 heap, and the new one is on C++ heap. Benchmark: This CL improves a benchmark score around promise by 5~23%. https://github.com/v8/promise-performance-tests https://docs.google.com/spreadsheets/d/1HtwZGzUAGJYg87VmYhV9hLdvfddlCtC6Oz0iOj-WwQA/edit#gid=1952666737 Bug: chromium:887920, v8:7253 Change-Id: I1f26e02c45ae60ae39d1ccc168daa98bca4663d9 Reviewed-on: https://chromium-review.googlesource.com/c/1290751 Commit-Queue: Taiju Tsuiki <tzik@chromium.org> Reviewed-by: Yang Guo <yangguo@chromium.org> Reviewed-by: Adam Klein <adamk@chromium.org> Reviewed-by: Benedikt Meurer <bmeurer@chromium.org> Reviewed-by: Ulan Degenbaev <ulan@chromium.org> Cr-Commit-Position: refs/heads/master@{#57681}
176 lines
5.8 KiB
C++
176 lines
5.8 KiB
C++
// Copyright 2018 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "src/microtask-queue.h"
|
|
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#include "src/heap/factory.h"
|
|
#include "src/visitors.h"
|
|
#include "test/unittests/test-utils.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
|
|
using Closure = std::function<void()>;
|
|
|
|
void RunStdFunction(void* data) {
|
|
std::unique_ptr<Closure> f(static_cast<Closure*>(data));
|
|
(*f)();
|
|
}
|
|
|
|
class MicrotaskQueueTest : public TestWithNativeContext {
|
|
public:
|
|
template <typename F>
|
|
Handle<Microtask> NewMicrotask(F&& f) {
|
|
Handle<Foreign> runner =
|
|
factory()->NewForeign(reinterpret_cast<Address>(&RunStdFunction));
|
|
Handle<Foreign> data = factory()->NewForeign(
|
|
reinterpret_cast<Address>(new Closure(std::forward<F>(f))));
|
|
return factory()->NewCallbackTask(runner, data);
|
|
}
|
|
|
|
void TearDown() override {
|
|
isolate()->default_microtask_queue()->RunMicrotasks(isolate());
|
|
}
|
|
};
|
|
|
|
class RecordingVisitor : public RootVisitor {
|
|
public:
|
|
RecordingVisitor() = default;
|
|
~RecordingVisitor() override = default;
|
|
|
|
void VisitRootPointers(Root root, const char* description, ObjectSlot start,
|
|
ObjectSlot end) override {
|
|
for (ObjectSlot current = start; current != end; ++current) {
|
|
visited_.push_back(*current);
|
|
}
|
|
}
|
|
|
|
const std::vector<Object*>& visited() const { return visited_; }
|
|
|
|
private:
|
|
std::vector<Object*> visited_;
|
|
};
|
|
|
|
// Sanity check. Ensure a microtask is stored in a queue and run.
|
|
TEST_F(MicrotaskQueueTest, EnqueueAndRun) {
|
|
MicrotaskQueue* microtask_queue = isolate()->default_microtask_queue();
|
|
ASSERT_TRUE(microtask_queue);
|
|
|
|
bool ran = false;
|
|
EXPECT_EQ(0, microtask_queue->capacity());
|
|
EXPECT_EQ(0, microtask_queue->size());
|
|
microtask_queue->EnqueueMicrotask(*NewMicrotask([&ran] {
|
|
EXPECT_FALSE(ran);
|
|
ran = true;
|
|
}));
|
|
EXPECT_EQ(MicrotaskQueue::kMinimumCapacity, microtask_queue->capacity());
|
|
EXPECT_EQ(1, microtask_queue->size());
|
|
microtask_queue->RunMicrotasks(isolate());
|
|
EXPECT_TRUE(ran);
|
|
EXPECT_EQ(0, microtask_queue->size());
|
|
}
|
|
|
|
// Check for a buffer growth.
|
|
TEST_F(MicrotaskQueueTest, BufferGrowth) {
|
|
MicrotaskQueue* microtask_queue = isolate()->default_microtask_queue();
|
|
ASSERT_TRUE(microtask_queue);
|
|
|
|
int count = 0;
|
|
|
|
// Enqueue and flush the queue first to have non-zero |start_|.
|
|
microtask_queue->EnqueueMicrotask(
|
|
*NewMicrotask([&count] { EXPECT_EQ(0, count++); }));
|
|
microtask_queue->RunMicrotasks(isolate());
|
|
|
|
EXPECT_LT(0, microtask_queue->capacity());
|
|
EXPECT_EQ(0, microtask_queue->size());
|
|
EXPECT_EQ(1, microtask_queue->start());
|
|
|
|
// Fill the queue with Microtasks.
|
|
for (int i = 1; i <= MicrotaskQueue::kMinimumCapacity; ++i) {
|
|
microtask_queue->EnqueueMicrotask(
|
|
*NewMicrotask([&count, i] { EXPECT_EQ(i, count++); }));
|
|
}
|
|
EXPECT_EQ(MicrotaskQueue::kMinimumCapacity, microtask_queue->capacity());
|
|
EXPECT_EQ(MicrotaskQueue::kMinimumCapacity, microtask_queue->size());
|
|
|
|
// Add another to grow the ring buffer.
|
|
microtask_queue->EnqueueMicrotask(*NewMicrotask(
|
|
[&] { EXPECT_EQ(MicrotaskQueue::kMinimumCapacity + 1, count++); }));
|
|
|
|
EXPECT_LT(MicrotaskQueue::kMinimumCapacity, microtask_queue->capacity());
|
|
EXPECT_EQ(MicrotaskQueue::kMinimumCapacity + 1, microtask_queue->size());
|
|
|
|
// Run all pending Microtasks to ensure they run in the proper order.
|
|
microtask_queue->RunMicrotasks(isolate());
|
|
EXPECT_EQ(MicrotaskQueue::kMinimumCapacity + 2, count);
|
|
}
|
|
|
|
// MicrotaskQueue instances form a doubly linked list.
|
|
TEST_F(MicrotaskQueueTest, InstanceChain) {
|
|
MicrotaskQueue* default_mtq = isolate()->default_microtask_queue();
|
|
ASSERT_TRUE(default_mtq);
|
|
EXPECT_EQ(default_mtq, default_mtq->next());
|
|
EXPECT_EQ(default_mtq, default_mtq->prev());
|
|
|
|
// Create two instances, and check their connection.
|
|
// The list contains all instances in the creation order, and the next of the
|
|
// last instance is the first instance:
|
|
// default_mtq -> mtq1 -> mtq2 -> default_mtq.
|
|
std::unique_ptr<MicrotaskQueue> mtq1 = MicrotaskQueue::New(isolate());
|
|
std::unique_ptr<MicrotaskQueue> mtq2 = MicrotaskQueue::New(isolate());
|
|
EXPECT_EQ(default_mtq->next(), mtq1.get());
|
|
EXPECT_EQ(mtq1->next(), mtq2.get());
|
|
EXPECT_EQ(mtq2->next(), default_mtq);
|
|
EXPECT_EQ(default_mtq, mtq1->prev());
|
|
EXPECT_EQ(mtq1.get(), mtq2->prev());
|
|
EXPECT_EQ(mtq2.get(), default_mtq->prev());
|
|
|
|
// Deleted item should be also removed from the list.
|
|
mtq1 = nullptr;
|
|
EXPECT_EQ(default_mtq->next(), mtq2.get());
|
|
EXPECT_EQ(mtq2->next(), default_mtq);
|
|
EXPECT_EQ(default_mtq, mtq2->prev());
|
|
EXPECT_EQ(mtq2.get(), default_mtq->prev());
|
|
}
|
|
|
|
// Pending Microtasks in MicrotaskQueues are strong roots. Ensure they are
|
|
// visited exactly once.
|
|
TEST_F(MicrotaskQueueTest, VisitRoot) {
|
|
MicrotaskQueue* microtask_queue = isolate()->default_microtask_queue();
|
|
ASSERT_TRUE(microtask_queue);
|
|
|
|
// Ensure that the ring buffer has separate in-use region.
|
|
for (int i = 0; i < MicrotaskQueue::kMinimumCapacity / 2 + 1; ++i) {
|
|
microtask_queue->EnqueueMicrotask(*NewMicrotask([] {}));
|
|
}
|
|
microtask_queue->RunMicrotasks(isolate());
|
|
|
|
std::vector<Object*> expected;
|
|
for (int i = 0; i < MicrotaskQueue::kMinimumCapacity / 2 + 1; ++i) {
|
|
Handle<Microtask> microtask = NewMicrotask([] {});
|
|
expected.push_back(*microtask);
|
|
microtask_queue->EnqueueMicrotask(*microtask);
|
|
}
|
|
EXPECT_GT(microtask_queue->start() + microtask_queue->size(),
|
|
microtask_queue->capacity());
|
|
|
|
RecordingVisitor visitor;
|
|
microtask_queue->IterateMicrotasks(&visitor);
|
|
|
|
std::vector<Object*> actual = visitor.visited();
|
|
std::sort(expected.begin(), expected.end());
|
|
std::sort(actual.begin(), actual.end());
|
|
EXPECT_EQ(expected, actual);
|
|
}
|
|
|
|
} // namespace internal
|
|
} // namespace v8
|