Commit 8e8c6250 authored by Benoît Lizé's avatar Benoît Lizé Committed by Commit Bot

blink/bindings: Keep compressed ParkableStrings when unparking, and reuse it.

When a ParkableString is unparked, keep the compressed data around, allowing to
drop the uncompressed data at no cost later. As decompression is much (>10x)
cheaper than compression and compressed data smaller, this is usually a good
tradeoff. This also allows to recover memory when a string is unparked once, and
its content is not touched again soon.

Concretely, we introduce:
- "synchronous parking" when parking is merely dropping the decompressed
  representation.
- "synchronous only" string parking in ParkableStringManager
- Drop all parkable strings when receiving a OnPurgeMemory() notification.

Also, improve and simplify testing, with fewer "friend" function and tighter
compression test.

Bug: 877044
Change-Id: I4b66ca5ddc67b2a6e49bfd1790e1788870642b04
Reviewed-on: https://chromium-review.googlesource.com/c/1329975Reviewed-by: default avatarBenoit L <lizeb@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Commit-Queue: Benoit L <lizeb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#610024}
parent a9a27f6e
...@@ -11,6 +11,7 @@ include_rules = [ ...@@ -11,6 +11,7 @@ include_rules = [
"+third_party/blink/renderer/platform/heap", "+third_party/blink/renderer/platform/heap",
"+third_party/blink/renderer/platform/instance_counters.h", "+third_party/blink/renderer/platform/instance_counters.h",
"+third_party/blink/renderer/platform/instrumentation", "+third_party/blink/renderer/platform/instrumentation",
"+third_party/blink/renderer/platform/memory_coordinator.h",
"+third_party/blink/renderer/platform/platform_export.h", "+third_party/blink/renderer/platform/platform_export.h",
"+third_party/blink/renderer/platform/runtime_enabled_features.h", "+third_party/blink/renderer/platform/runtime_enabled_features.h",
"+third_party/blink/renderer/platform/scheduler", "+third_party/blink/renderer/platform/scheduler",
......
...@@ -199,21 +199,32 @@ unsigned ParkableStringImpl::CharactersSizeInBytes() const { ...@@ -199,21 +199,32 @@ unsigned ParkableStringImpl::CharactersSizeInBytes() const {
return length_ * (is_8bit() ? sizeof(LChar) : sizeof(UChar)); return length_ * (is_8bit() ? sizeof(LChar) : sizeof(UChar));
} }
bool ParkableStringImpl::Park() { bool ParkableStringImpl::Park(ParkingMode mode) {
AssertOnValidThread(); AssertOnValidThread();
MutexLocker locker(mutex_); MutexLocker locker(mutex_);
DCHECK(may_be_parked_); DCHECK(may_be_parked_);
if (state_ == State::kUnparked && CanParkNow()) { if (state_ == State::kUnparked && CanParkNow()) {
// |string_|'s data should not be touched except in the compression task. // Parking can proceed synchronously.
AsanPoisonString(string_); if (has_compressed_data()) {
// |params| keeps |this| alive until |OnParkingCompleteOnMainThread()|. RecordParkingAction(ParkingAction::kParkedInBackground);
auto params = std::make_unique<CompressionTaskParams>( state_ = State::kParked;
this, string_.Bytes(), string_.CharactersSizeInBytes(), ParkableStringManager::Instance().OnParked(this, string_.Impl());
Thread::Current()->GetTaskRunner());
background_scheduler::PostOnBackgroundThread( // Must unpoison the memory before releasing it.
FROM_HERE, CrossThreadBind(&ParkableStringImpl::CompressInBackground, AsanUnpoisonString(string_);
WTF::Passed(std::move(params)))); string_ = String();
state_ = State::kParkingInProgress; } else if (mode == ParkingMode::kAlways) {
// |string_|'s data should not be touched except in the compression task.
AsanPoisonString(string_);
// |params| keeps |this| alive until |OnParkingCompleteOnMainThread()|.
auto params = std::make_unique<CompressionTaskParams>(
this, string_.Bytes(), string_.CharactersSizeInBytes(),
Thread::Current()->GetTaskRunner());
background_scheduler::PostOnBackgroundThread(
FROM_HERE, CrossThreadBind(&ParkableStringImpl::CompressInBackground,
WTF::Passed(std::move(params))));
state_ = State::kParkingInProgress;
}
} }
return state_ == State::kParked || state_ == State::kParkingInProgress; return state_ == State::kParked || state_ == State::kParkingInProgress;
...@@ -271,7 +282,6 @@ void ParkableStringImpl::Unpark() { ...@@ -271,7 +282,6 @@ void ParkableStringImpl::Unpark() {
// the string we need, nothing else to do than to abort. // the string we need, nothing else to do than to abort.
CHECK(compression::GzipUncompress(compressed_string_piece, CHECK(compression::GzipUncompress(compressed_string_piece,
uncompressed_string_piece)); uncompressed_string_piece));
compressed_ = nullptr;
string_ = uncompressed; string_ = uncompressed;
state_ = State::kUnparked; state_ = State::kUnparked;
ParkableStringManager::Instance().OnUnparked(this, string_.Impl()); ParkableStringManager::Instance().OnUnparked(this, string_.Impl());
...@@ -303,9 +313,9 @@ void ParkableStringImpl::OnParkingCompleteOnMainThread( ...@@ -303,9 +313,9 @@ void ParkableStringImpl::OnParkingCompleteOnMainThread(
if (CanParkNow() && compressed) { if (CanParkNow() && compressed) {
RecordParkingAction(ParkingAction::kParkedInBackground); RecordParkingAction(ParkingAction::kParkedInBackground);
state_ = State::kParked; state_ = State::kParked;
compressed_ = std::move(compressed);
ParkableStringManager::Instance().OnParked(this, string_.Impl()); ParkableStringManager::Instance().OnParked(this, string_.Impl());
compressed_ = std::move(compressed);
// Must unpoison the memory before releasing it. // Must unpoison the memory before releasing it.
AsanUnpoisonString(string_); AsanUnpoisonString(string_);
string_ = String(); string_ = String();
......
...@@ -49,6 +49,7 @@ class PLATFORM_EXPORT ParkableStringImpl final ...@@ -49,6 +49,7 @@ class PLATFORM_EXPORT ParkableStringImpl final
}; };
enum class ParkableState { kParkable, kNotParkable }; enum class ParkableState { kParkable, kNotParkable };
enum class ParkingMode { kIfCompressedDataExists, kAlways };
// Not all parkable strings can actually be parked. If |parkable| is // Not all parkable strings can actually be parked. If |parkable| is
// kNotParkable, then one cannot call |Park()|, and the underlying StringImpl // kNotParkable, then one cannot call |Park()|, and the underlying StringImpl
...@@ -69,19 +70,26 @@ class PLATFORM_EXPORT ParkableStringImpl final ...@@ -69,19 +70,26 @@ class PLATFORM_EXPORT ParkableStringImpl final
unsigned CharactersSizeInBytes() const; unsigned CharactersSizeInBytes() const;
// A parked string cannot be accessed until it has been |Unpark()|-ed. // A parked string cannot be accessed until it has been |Unpark()|-ed.
// Returns true iff the string has been parked. //
bool Park(); // Parking may be synchronous, and will be if compressed data is already
// available. If |mode| is |kIfCompressedDataExists|, then parking will always
// be synchronous.
//
// Returns true if the string is being parked or has been parked.
bool Park(ParkingMode mode);
// Returns true iff the string can be parked. This does not mean that the // Returns true iff the string can be parked. This does not mean that the
// string can be parked now, merely that it is eligible to be parked at some // string can be parked now, merely that it is eligible to be parked at some
// point. // point.
bool may_be_parked() const { return may_be_parked_; } bool may_be_parked() const { return may_be_parked_; }
// Returns true if the string is parked. // Returns true if the string is parked.
bool is_parked() const; bool is_parked() const;
// Returns the compressed size, must not be called unless the string is // Returns whether synchronous parking is possible, that is the string was
// compressed. // parked in the past.
bool has_compressed_data() const { return !!compressed_; }
// Returns the compressed size, must not be called unless the string has a
// compressed representation.
size_t compressed_size() const { size_t compressed_size() const {
DCHECK(is_parked()); DCHECK(has_compressed_data());
DCHECK(compressed_);
return compressed_->size(); return compressed_->size();
} }
...@@ -124,15 +132,8 @@ class PLATFORM_EXPORT ParkableStringImpl final ...@@ -124,15 +132,8 @@ class PLATFORM_EXPORT ParkableStringImpl final
#endif #endif
} }
FRIEND_TEST_ALL_PREFIXES(ParkableStringTest, Park);
FRIEND_TEST_ALL_PREFIXES(ParkableStringTest, AbortParking);
FRIEND_TEST_ALL_PREFIXES(ParkableStringTest, Unpark);
FRIEND_TEST_ALL_PREFIXES(ParkableStringTest, LockUnlock); FRIEND_TEST_ALL_PREFIXES(ParkableStringTest, LockUnlock);
FRIEND_TEST_ALL_PREFIXES(ParkableStringTest, LockParkedString); FRIEND_TEST_ALL_PREFIXES(ParkableStringTest, LockParkedString);
FRIEND_TEST_ALL_PREFIXES(ParkableStringTest, TableSimple);
FRIEND_TEST_ALL_PREFIXES(ParkableStringTest, TableMultiple);
FRIEND_TEST_ALL_PREFIXES(ParkableStringTest, AsanPoisoning);
FRIEND_TEST_ALL_PREFIXES(ParkableStringTest, Compression);
DISALLOW_COPY_AND_ASSIGN(ParkableStringImpl); DISALLOW_COPY_AND_ASSIGN(ParkableStringImpl);
}; };
......
...@@ -12,22 +12,35 @@ ...@@ -12,22 +12,35 @@
#include "base/single_thread_task_runner.h" #include "base/single_thread_task_runner.h"
#include "third_party/blink/public/platform/platform.h" #include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/platform/bindings/parkable_string.h" #include "third_party/blink/renderer/platform/bindings/parkable_string.h"
#include "third_party/blink/renderer/platform/memory_coordinator.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h" #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h" #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
#include "third_party/blink/renderer/platform/wtf/wtf.h" #include "third_party/blink/renderer/platform/wtf/wtf.h"
namespace blink { namespace blink {
class OnPurgeMemoryListener : public GarbageCollected<OnPurgeMemoryListener>,
public MemoryCoordinatorClient {
USING_GARBAGE_COLLECTED_MIXIN(OnPurgeMemoryListener);
void OnPurgeMemory() override {
ParkableStringManager::Instance().ParkAllIfRendererBackgrounded(
ParkableStringImpl::ParkingMode::kAlways);
}
};
ParkableStringManager& ParkableStringManager::Instance() { ParkableStringManager& ParkableStringManager::Instance() {
DCHECK(IsMainThread()); DCHECK(IsMainThread());
DEFINE_STATIC_LOCAL(ParkableStringManager, instance, ()); DEFINE_STATIC_LOCAL(ParkableStringManager, instance, ());
return instance; return instance;
} }
ParkableStringManager::~ParkableStringManager() {} ParkableStringManager::~ParkableStringManager() = default;
void ParkableStringManager::SetRendererBackgrounded(bool backgrounded) { void ParkableStringManager::SetRendererBackgrounded(bool backgrounded) {
DCHECK(IsMainThread());
backgrounded_ = backgrounded; backgrounded_ = backgrounded;
if (backgrounded_) { if (backgrounded_) {
...@@ -37,7 +50,8 @@ void ParkableStringManager::SetRendererBackgrounded(bool backgrounded) { ...@@ -37,7 +50,8 @@ void ParkableStringManager::SetRendererBackgrounded(bool backgrounded) {
task_runner->PostDelayedTask( task_runner->PostDelayedTask(
FROM_HERE, FROM_HERE,
base::BindOnce(&ParkableStringManager::ParkAllIfRendererBackgrounded, base::BindOnce(&ParkableStringManager::ParkAllIfRendererBackgrounded,
base::Unretained(this)), base::Unretained(this),
ParkableStringImpl::ParkingMode::kAlways),
base::TimeDelta::FromSeconds(kParkingDelayInSeconds)); base::TimeDelta::FromSeconds(kParkingDelayInSeconds));
// We only want to record statistics in the following case: a foreground tab // We only want to record statistics in the following case: a foreground tab
// goes to background, and stays in background until the stats are recorded, // goes to background, and stays in background until the stats are recorded,
...@@ -46,11 +60,15 @@ void ParkableStringManager::SetRendererBackgrounded(bool backgrounded) { ...@@ -46,11 +60,15 @@ void ParkableStringManager::SetRendererBackgrounded(bool backgrounded) {
// To that end: // To that end:
// 1. Don't post a recording task if one has been posted and hasn't run yet. // 1. Don't post a recording task if one has been posted and hasn't run yet.
// 2. Any background -> foreground transition between now and the // 2. Any background -> foreground transition between now and the
// recording task running cancels stats recording. // recording task running cancels the task.
//
// Also drop strings that can be dropped cheaply in this task, to prevent
// used-once strings from increasing memory usage.
if (!waiting_to_record_stats_) { if (!waiting_to_record_stats_) {
task_runner->PostDelayedTask( task_runner->PostDelayedTask(
FROM_HERE, FROM_HERE,
base::BindOnce(&ParkableStringManager::RecordStatistics, base::BindOnce(&ParkableStringManager::
DropStringsWithCompressedDataAndRecordStatistics,
base::Unretained(this)), base::Unretained(this)),
base::TimeDelta::FromSeconds(kParkingDelayInSeconds + base::TimeDelta::FromSeconds(kParkingDelayInSeconds +
kStatisticsRecordingDelayInSeconds)); kStatisticsRecordingDelayInSeconds));
...@@ -66,6 +84,7 @@ void ParkableStringManager::SetRendererBackgrounded(bool backgrounded) { ...@@ -66,6 +84,7 @@ void ParkableStringManager::SetRendererBackgrounded(bool backgrounded) {
} }
bool ParkableStringManager::IsRendererBackgrounded() const { bool ParkableStringManager::IsRendererBackgrounded() const {
DCHECK(IsMainThread());
return backgrounded_; return backgrounded_;
} }
...@@ -79,6 +98,7 @@ bool ParkableStringManager::ShouldPark(const StringImpl& string) { ...@@ -79,6 +98,7 @@ bool ParkableStringManager::ShouldPark(const StringImpl& string) {
scoped_refptr<ParkableStringImpl> ParkableStringManager::Add( scoped_refptr<ParkableStringImpl> ParkableStringManager::Add(
scoped_refptr<StringImpl>&& string) { scoped_refptr<StringImpl>&& string) {
DCHECK(IsMainThread());
StringImpl* raw_ptr = string.get(); StringImpl* raw_ptr = string.get();
auto it = unparked_strings_.find(raw_ptr); auto it = unparked_strings_.find(raw_ptr);
if (it != unparked_strings_.end()) if (it != unparked_strings_.end())
...@@ -125,8 +145,10 @@ void ParkableStringManager::OnUnparked(ParkableStringImpl* was_parked_string, ...@@ -125,8 +145,10 @@ void ParkableStringManager::OnUnparked(ParkableStringImpl* was_parked_string,
unparked_strings_.insert(new_unparked_string, was_parked_string); unparked_strings_.insert(new_unparked_string, was_parked_string);
} }
void ParkableStringManager::ParkAllIfRendererBackgrounded() { void ParkableStringManager::ParkAllIfRendererBackgrounded(
ParkableStringImpl::ParkingMode mode) {
DCHECK(IsMainThread()); DCHECK(IsMainThread());
if (!IsRendererBackgrounded()) if (!IsRendererBackgrounded())
return; return;
...@@ -137,22 +159,39 @@ void ParkableStringManager::ParkAllIfRendererBackgrounded() { ...@@ -137,22 +159,39 @@ void ParkableStringManager::ParkAllIfRendererBackgrounded() {
for (ParkableStringImpl* str : parked_strings_) for (ParkableStringImpl* str : parked_strings_)
total_size += str->CharactersSizeInBytes(); total_size += str->CharactersSizeInBytes();
for (ParkableStringImpl* str : unparked_strings_.Values()) { // Parking may be synchronous, need to copy values first.
str->Park(); // Parking is asynchronous. // In case of synchronous parking, |ParkableStringImpl::Park()| calls
// |OnParked()|, which moves the string from |unparked_strings_|
// to |parked_strings_|, hence the need to copy values first.
//
// Efficiency: In practice, either we are parking strings for the first time,
// and |unparked_strings_| can contain a few 10s of strings (and we will
// trigger expensive compression), or this is a subsequent one, and
// |unparked_strings_| will have few entries.
WTF::Vector<ParkableStringImpl*> unparked;
unparked.ReserveCapacity(unparked_strings_.size());
for (ParkableStringImpl* str : unparked_strings_.Values())
unparked.push_back(str);
for (ParkableStringImpl* str : unparked) {
str->Park(mode);
total_size += str->CharactersSizeInBytes(); total_size += str->CharactersSizeInBytes();
} }
size_t total_size_kb = total_size / 1000; // Only collect stats for "full" parking calls.
UMA_HISTOGRAM_COUNTS_100000("Memory.MovableStringsTotalSizeKb", if (mode == ParkableStringImpl::ParkingMode::kAlways) {
total_size_kb); size_t total_size_kb = total_size / 1000;
UMA_HISTOGRAM_COUNTS_1000("Memory.MovableStringsCount", Size()); UMA_HISTOGRAM_COUNTS_100000("Memory.MovableStringsTotalSizeKb",
total_size_kb);
UMA_HISTOGRAM_COUNTS_1000("Memory.MovableStringsCount", Size());
}
} }
size_t ParkableStringManager::Size() const { size_t ParkableStringManager::Size() const {
return parked_strings_.size() + unparked_strings_.size(); return parked_strings_.size() + unparked_strings_.size();
} }
void ParkableStringManager::RecordStatistics() { void ParkableStringManager::DropStringsWithCompressedDataAndRecordStatistics() {
DCHECK(IsMainThread()); DCHECK(IsMainThread());
DCHECK(waiting_to_record_stats_); DCHECK(waiting_to_record_stats_);
waiting_to_record_stats_ = false; waiting_to_record_stats_ = false;
...@@ -162,6 +201,11 @@ void ParkableStringManager::RecordStatistics() { ...@@ -162,6 +201,11 @@ void ParkableStringManager::RecordStatistics() {
// renderer is still backgrounded_. // renderer is still backgrounded_.
DCHECK(IsRendererBackgrounded()); DCHECK(IsRendererBackgrounded());
// We are in the background, drop all the ParkableStrings we can without
// costing any CPU (as we already have the compressed representation).
ParkAllIfRendererBackgrounded(
ParkableStringImpl::ParkingMode::kIfCompressedDataExists);
size_t total_size = 0, total_before_compression_size = 0; size_t total_size = 0, total_before_compression_size = 0;
size_t total_compressed_size = 0; size_t total_compressed_size = 0;
for (ParkableStringImpl* str : parked_strings_) { for (ParkableStringImpl* str : parked_strings_) {
...@@ -202,6 +246,10 @@ ParkableStringManager::ParkableStringManager() ...@@ -202,6 +246,10 @@ ParkableStringManager::ParkableStringManager()
waiting_to_record_stats_(false), waiting_to_record_stats_(false),
should_record_stats_(false), should_record_stats_(false),
unparked_strings_(), unparked_strings_(),
parked_strings_() {} parked_strings_() {
// No need to ever unregister, as the only ParkableStringManager instance
// lives forever.
MemoryCoordinator::Instance().RegisterClient(new OnPurgeMemoryListener());
}
} // namespace blink } // namespace blink
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "base/feature_list.h" #include "base/feature_list.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/scoped_refptr.h" #include "base/memory/scoped_refptr.h"
#include "third_party/blink/renderer/platform/bindings/parkable_string.h"
#include "third_party/blink/renderer/platform/platform_export.h" #include "third_party/blink/renderer/platform/platform_export.h"
#include "third_party/blink/renderer/platform/wtf/allocator.h" #include "third_party/blink/renderer/platform/wtf/allocator.h"
#include "third_party/blink/renderer/platform/wtf/hash_functions.h" #include "third_party/blink/renderer/platform/wtf/hash_functions.h"
...@@ -18,7 +19,6 @@ ...@@ -18,7 +19,6 @@
namespace blink { namespace blink {
class ParkableString; class ParkableString;
class ParkableStringImpl;
const base::Feature kCompressParkableStringsInBackground{ const base::Feature kCompressParkableStringsInBackground{
"CompressParkableStringsInBackground", base::FEATURE_DISABLED_BY_DEFAULT}; "CompressParkableStringsInBackground", base::FEATURE_DISABLED_BY_DEFAULT};
...@@ -37,7 +37,6 @@ class PLATFORM_EXPORT ParkableStringManager { ...@@ -37,7 +37,6 @@ class PLATFORM_EXPORT ParkableStringManager {
bool IsRendererBackgrounded() const; bool IsRendererBackgrounded() const;
// Number of parked and unparked strings. Public for testing. // Number of parked and unparked strings. Public for testing.
size_t Size() const; size_t Size() const;
void ResetForTesting();
// Whether a string is parkable or not. Can be called from any thread. // Whether a string is parkable or not. Can be called from any thread.
static bool ShouldPark(const StringImpl& string); static bool ShouldPark(const StringImpl& string);
...@@ -49,6 +48,7 @@ class PLATFORM_EXPORT ParkableStringManager { ...@@ -49,6 +48,7 @@ class PLATFORM_EXPORT ParkableStringManager {
private: private:
friend class ParkableString; friend class ParkableString;
friend class ParkableStringImpl; friend class ParkableStringImpl;
friend class OnPurgeMemoryListener;
scoped_refptr<ParkableStringImpl> Add(scoped_refptr<StringImpl>&&); scoped_refptr<ParkableStringImpl> Add(scoped_refptr<StringImpl>&&);
void Remove(ParkableStringImpl*, StringImpl*); void Remove(ParkableStringImpl*, StringImpl*);
...@@ -56,8 +56,9 @@ class PLATFORM_EXPORT ParkableStringManager { ...@@ -56,8 +56,9 @@ class PLATFORM_EXPORT ParkableStringManager {
void OnParked(ParkableStringImpl*, StringImpl*); void OnParked(ParkableStringImpl*, StringImpl*);
void OnUnparked(ParkableStringImpl*, StringImpl*); void OnUnparked(ParkableStringImpl*, StringImpl*);
void ParkAllIfRendererBackgrounded(); void ParkAllIfRendererBackgrounded(ParkableStringImpl::ParkingMode mode);
void RecordStatistics(); void DropStringsWithCompressedDataAndRecordStatistics();
void ResetForTesting();
ParkableStringManager(); ParkableStringManager();
...@@ -68,6 +69,8 @@ class PLATFORM_EXPORT ParkableStringManager { ...@@ -68,6 +69,8 @@ class PLATFORM_EXPORT ParkableStringManager {
unparked_strings_; unparked_strings_;
HashSet<ParkableStringImpl*, PtrHash<ParkableStringImpl>> parked_strings_; HashSet<ParkableStringImpl*, PtrHash<ParkableStringImpl>> parked_strings_;
friend class ParkableStringTest;
FRIEND_TEST_ALL_PREFIXES(ParkableStringTest, SynchronousCompression);
DISALLOW_COPY_AND_ASSIGN(ParkableStringManager); DISALLOW_COPY_AND_ASSIGN(ParkableStringManager);
}; };
......
...@@ -12,12 +12,16 @@ ...@@ -12,12 +12,16 @@
#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h" #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
#include "third_party/blink/renderer/platform/bindings/parkable_string.h" #include "third_party/blink/renderer/platform/bindings/parkable_string.h"
#include "third_party/blink/renderer/platform/bindings/parkable_string_manager.h" #include "third_party/blink/renderer/platform/bindings/parkable_string_manager.h"
#include "third_party/blink/renderer/platform/memory_coordinator.h"
namespace blink { namespace blink {
namespace { namespace {
constexpr size_t kSizeKb = 20; constexpr size_t kSizeKb = 20;
// Compressed size of the string returned by |MakeLargeString()|.
// Update if the assertion in the |CheckCompressedSize()| test fails.
constexpr size_t kCompressedSize = 55;
String MakeLargeString() { String MakeLargeString() {
std::vector<char> data(kSizeKb * 1000, 'a'); std::vector<char> data(kSizeKb * 1000, 'a');
...@@ -50,11 +54,25 @@ class ParkableStringTest : public ::testing::Test { ...@@ -50,11 +54,25 @@ class ParkableStringTest : public ::testing::Test {
} }
bool ParkAndWait(const ParkableString& string) { bool ParkAndWait(const ParkableString& string) {
bool return_value = string.Impl()->Park(); bool return_value =
string.Impl()->Park(ParkableStringImpl::ParkingMode::kAlways);
RunPostedTasks(); RunPostedTasks();
return return_value; return return_value;
} }
ParkableString CreateAndParkAll() {
auto& manager = ParkableStringManager::Instance();
// Checking that there are no other strings, to make sure this doesn't
// cause side-effects.
CHECK_EQ(0u, manager.Size());
ParkableString parkable(MakeLargeString().ReleaseImpl());
manager.SetRendererBackgrounded(true);
EXPECT_FALSE(parkable.Impl()->is_parked());
WaitForDelayedParking();
EXPECT_TRUE(parkable.Impl()->is_parked());
return parkable;
}
void SetUp() override { void SetUp() override {
ParkableStringManager::Instance().ResetForTesting(); ParkableStringManager::Instance().ResetForTesting();
scoped_feature_list_.InitAndEnableFeature( scoped_feature_list_.InitAndEnableFeature(
...@@ -73,6 +91,16 @@ class ParkableStringTest : public ::testing::Test { ...@@ -73,6 +91,16 @@ class ParkableStringTest : public ::testing::Test {
base::test::ScopedFeatureList scoped_feature_list_; base::test::ScopedFeatureList scoped_feature_list_;
}; };
// The main aim of this test is to check that the compressed size of a string
// doesn't change. If it does, |kCompressedsize| will need to be updated.
TEST_F(ParkableStringTest, CheckCompressedSize) {
ParkableString parkable(MakeLargeString().ReleaseImpl());
EXPECT_TRUE(parkable.Impl()->Park(ParkableStringImpl::ParkingMode::kAlways));
RunPostedTasks();
EXPECT_TRUE(parkable.Impl()->is_parked());
EXPECT_EQ(kCompressedSize, parkable.Impl()->compressed_size());
}
TEST_F(ParkableStringTest, Simple) { TEST_F(ParkableStringTest, Simple) {
ParkableString parkable_abc(String("abc").ReleaseImpl()); ParkableString parkable_abc(String("abc").ReleaseImpl());
...@@ -117,46 +145,72 @@ TEST_F(ParkableStringTest, Park) { ...@@ -117,46 +145,72 @@ TEST_F(ParkableStringTest, Park) {
ParkableString parkable(MakeLargeString().ReleaseImpl()); ParkableString parkable(MakeLargeString().ReleaseImpl());
EXPECT_TRUE(parkable.may_be_parked()); EXPECT_TRUE(parkable.may_be_parked());
EXPECT_FALSE(parkable.Impl()->is_parked()); EXPECT_FALSE(parkable.Impl()->is_parked());
EXPECT_TRUE(parkable.Impl()->Park()); EXPECT_TRUE(
parkable.Impl()->Park(ParkableStringImpl::ParkingMode::kAlways));
parkable = ParkableString(); // release the reference. parkable = ParkableString(); // release the reference.
RunPostedTasks(); // Should not crash. RunPostedTasks(); // Should not crash.
} }
} }
TEST_F(ParkableStringTest, AbortParking) { TEST_F(ParkableStringTest, AbortParking) {
ParkableString parkable(MakeLargeString().ReleaseImpl()); {
ParkableStringImpl* impl = parkable.Impl(); ParkableString parkable(MakeLargeString().ReleaseImpl());
EXPECT_TRUE(parkable.may_be_parked());
EXPECT_FALSE(parkable.Impl()->is_parked());
EXPECT_TRUE(parkable.may_be_parked()); // The string is locked at the end of parking, should cancel it.
EXPECT_FALSE(impl->is_parked()); EXPECT_TRUE(
parkable.Impl()->Park(ParkableStringImpl::ParkingMode::kAlways));
parkable.Impl()->Lock();
RunPostedTasks();
EXPECT_FALSE(parkable.Impl()->is_parked());
// The string is locked at the end of parking, should cancel it. // Unlock, OK to park.
EXPECT_TRUE(impl->Park()); parkable.Impl()->Unlock();
impl->Lock(); EXPECT_TRUE(ParkAndWait(parkable));
RunPostedTasks(); }
EXPECT_FALSE(impl->is_parked());
// Unlock, OK to park. {
impl->Unlock(); ParkableString parkable(MakeLargeString().ReleaseImpl());
EXPECT_TRUE(ParkAndWait(parkable)); // |ToString()| cancels parking as |content| is kept alive.
impl->ToString(); EXPECT_TRUE(
parkable.Impl()->Park(ParkableStringImpl::ParkingMode::kAlways));
{
String content = parkable.Impl()->ToString();
RunPostedTasks();
EXPECT_FALSE(parkable.Impl()->is_parked());
}
EXPECT_TRUE(ParkAndWait(parkable));
}
// |ToString()| cancels unparking as |content| is kept alive. {
EXPECT_TRUE(impl->Park()); ParkableString parkable(MakeLargeString().ReleaseImpl());
String content = impl->ToString(); // Transient |Lock()| or |ToString()| doesn't cancel parking.
RunPostedTasks(); EXPECT_TRUE(
EXPECT_FALSE(impl->is_parked()); parkable.Impl()->Park(ParkableStringImpl::ParkingMode::kAlways));
content = String(); parkable.Impl()->Lock();
EXPECT_TRUE(ParkAndWait(parkable)); parkable.Impl()->ToString();
impl->ToString(); parkable.Impl()->Unlock();
RunPostedTasks();
EXPECT_TRUE(parkable.Impl()->is_parked());
// Transient |Lock()| or |ToString()| doesn't cancel parking. // Synchronous parking respects locking and external references.
EXPECT_TRUE(impl->Park()); parkable.ToString();
impl->Lock(); EXPECT_TRUE(parkable.Impl()->has_compressed_data());
impl->ToString(); parkable.Lock();
impl->Unlock(); EXPECT_FALSE(
RunPostedTasks(); parkable.Impl()->Park(ParkableStringImpl::ParkingMode::kAlways));
EXPECT_TRUE(impl->is_parked()); parkable.Unlock();
{
String content = parkable.ToString();
EXPECT_FALSE(
parkable.Impl()->Park(ParkableStringImpl::ParkingMode::kAlways));
}
// Parking is synchronous.
EXPECT_TRUE(
parkable.Impl()->Park(ParkableStringImpl::ParkingMode::kAlways));
EXPECT_TRUE(parkable.Impl()->is_parked());
}
} }
TEST_F(ParkableStringTest, Unpark) { TEST_F(ParkableStringTest, Unpark) {
...@@ -209,10 +263,8 @@ TEST_F(ParkableStringTest, LockUnlock) { ...@@ -209,10 +263,8 @@ TEST_F(ParkableStringTest, LockUnlock) {
} }
TEST_F(ParkableStringTest, LockParkedString) { TEST_F(ParkableStringTest, LockParkedString) {
ParkableString parkable(MakeLargeString().Impl()); ParkableString parkable = CreateAndParkAll();
ParkableStringImpl* impl = parkable.Impl(); ParkableStringImpl* impl = parkable.Impl();
EXPECT_EQ(0, impl->lock_depth_);
EXPECT_TRUE(ParkAndWait(parkable));
parkable.Lock(); // Locking doesn't unpark. parkable.Lock(); // Locking doesn't unpark.
EXPECT_TRUE(impl->is_parked()); EXPECT_TRUE(impl->is_parked());
...@@ -339,11 +391,14 @@ TEST_F(ParkableStringTest, ManagerMultipleStrings) { ...@@ -339,11 +391,14 @@ TEST_F(ParkableStringTest, ManagerMultipleStrings) {
// - CompressedSizeKb == 0 // - CompressedSizeKb == 0
// - SavingsKb == 19 // - SavingsKb == 19
// - CompressionRatio == 0 (<1%) // - CompressionRatio == 0 (<1%)
size_t expected_savings = kSizeKb * 1000 - kCompressedSize;
histogram_tester.ExpectUniqueSample("Memory.ParkableString.CompressedSizeKb", histogram_tester.ExpectUniqueSample("Memory.ParkableString.CompressedSizeKb",
0, 1); kCompressedSize / 1000, 1);
histogram_tester.ExpectUniqueSample("Memory.ParkableString.SavingsKb", 19, 1); histogram_tester.ExpectUniqueSample("Memory.ParkableString.SavingsKb",
expected_savings / 1000, 1);
histogram_tester.ExpectUniqueSample("Memory.ParkableString.CompressionRatio", histogram_tester.ExpectUniqueSample("Memory.ParkableString.CompressionRatio",
0, 1); 100 * kCompressedSize / (kSizeKb * 1000),
1);
// Don't record statistics if the renderer moves to foreground before // Don't record statistics if the renderer moves to foreground before
// recording statistics. // recording statistics.
...@@ -358,8 +413,8 @@ TEST_F(ParkableStringTest, ManagerMultipleStrings) { ...@@ -358,8 +413,8 @@ TEST_F(ParkableStringTest, ManagerMultipleStrings) {
histogram_tester.ExpectUniqueSample("Memory.ParkableString.TotalSizeKb", histogram_tester.ExpectUniqueSample("Memory.ParkableString.TotalSizeKb",
2 * kSizeKb, 1); 2 * kSizeKb, 1);
// Calling |RecordStatistics()| resets the state, can now record stats next // Calling |ParkStringsWithCompressedDataAndRecordStatistics()| resets the
// time. // state, can now record stats next time.
manager.SetRendererBackgrounded(true); manager.SetRendererBackgrounded(true);
parking_count++; parking_count++;
WaitForDelayedParking(); WaitForDelayedParking();
...@@ -414,7 +469,7 @@ TEST_F(ParkableStringTest, AsanPoisoningTest) { ...@@ -414,7 +469,7 @@ TEST_F(ParkableStringTest, AsanPoisoningTest) {
// Non-regression test for crbug.com/905137. // Non-regression test for crbug.com/905137.
TEST_F(ParkableStringTest, CorrectAsanPoisoning) { TEST_F(ParkableStringTest, CorrectAsanPoisoning) {
ParkableString parkable(MakeLargeString().ReleaseImpl()); ParkableString parkable(MakeLargeString().ReleaseImpl());
EXPECT_TRUE(parkable.Impl()->Park()); EXPECT_TRUE(parkable.Impl()->Park(ParkableStringImpl::ParkingMode::kAlways));
// A main thread task is posted once compression is done. // A main thread task is posted once compression is done.
while (!scoped_task_environment_.MainThreadHasPendingTask()) { while (!scoped_task_environment_.MainThreadHasPendingTask()) {
parkable.Lock(); parkable.Lock();
...@@ -428,18 +483,19 @@ TEST_F(ParkableStringTest, CorrectAsanPoisoning) { ...@@ -428,18 +483,19 @@ TEST_F(ParkableStringTest, CorrectAsanPoisoning) {
TEST_F(ParkableStringTest, Compression) { TEST_F(ParkableStringTest, Compression) {
base::HistogramTester histogram_tester; base::HistogramTester histogram_tester;
ParkableString parkable(MakeLargeString().Impl()); ParkableString parkable = CreateAndParkAll();
ParkableStringImpl* impl = parkable.Impl(); ParkableStringImpl* impl = parkable.Impl();
EXPECT_TRUE(impl->Park());
EXPECT_FALSE(impl->is_parked());
RunPostedTasks();
EXPECT_TRUE(impl->is_parked()); EXPECT_TRUE(impl->is_parked());
EXPECT_TRUE(impl->compressed_); EXPECT_TRUE(impl->has_compressed_data());
EXPECT_LT(impl->compressed_->size(), impl->CharactersSizeInBytes()); EXPECT_EQ(kCompressedSize, impl->compressed_size());
parkable.ToString(); parkable.ToString(); // First decompression.
EXPECT_FALSE(impl->is_parked()); EXPECT_FALSE(impl->is_parked());
EXPECT_EQ(nullptr, impl->compressed_); EXPECT_TRUE(impl->has_compressed_data());
EXPECT_TRUE(
impl->Park(ParkableStringImpl::ParkingMode::kIfCompressedDataExists));
EXPECT_TRUE(impl->is_parked());
parkable.ToString(); // Second decompression.
histogram_tester.ExpectUniqueSample( histogram_tester.ExpectUniqueSample(
"Memory.ParkableString.Compression.SizeKb", kSizeKb, 1); "Memory.ParkableString.Compression.SizeKb", kSizeKb, 1);
...@@ -447,12 +503,13 @@ TEST_F(ParkableStringTest, Compression) { ...@@ -447,12 +503,13 @@ TEST_F(ParkableStringTest, Compression) {
1); 1);
histogram_tester.ExpectTotalCount( histogram_tester.ExpectTotalCount(
"Memory.ParkableString.Compression.ThroughputMBps", 1); "Memory.ParkableString.Compression.ThroughputMBps", 1);
// |parkable| is decompressed twice.
histogram_tester.ExpectUniqueSample( histogram_tester.ExpectUniqueSample(
"Memory.ParkableString.Decompression.SizeKb", kSizeKb, 1); "Memory.ParkableString.Decompression.SizeKb", kSizeKb, 2);
histogram_tester.ExpectTotalCount( histogram_tester.ExpectTotalCount(
"Memory.ParkableString.Decompression.Latency", 1); "Memory.ParkableString.Decompression.Latency", 2);
histogram_tester.ExpectTotalCount( histogram_tester.ExpectTotalCount(
"Memory.ParkableString.Decompression.ThroughputMBps", 1); "Memory.ParkableString.Decompression.ThroughputMBps", 2);
} }
TEST_F(ParkableStringTest, DelayedTasks) { TEST_F(ParkableStringTest, DelayedTasks) {
...@@ -479,4 +536,28 @@ TEST_F(ParkableStringTest, DelayedTasks) { ...@@ -479,4 +536,28 @@ TEST_F(ParkableStringTest, DelayedTasks) {
histogram_tester.ExpectTotalCount("Memory.ParkableString.TotalSizeKb", 1); histogram_tester.ExpectTotalCount("Memory.ParkableString.TotalSizeKb", 1);
} }
TEST_F(ParkableStringTest, SynchronousCompression) {
ParkableStringManager& manager = ParkableStringManager::Instance();
ParkableString parkable = CreateAndParkAll();
parkable.ToString();
EXPECT_TRUE(parkable.Impl()->has_compressed_data());
// No waiting, synchronous compression.
manager.ParkAllIfRendererBackgrounded(
ParkableStringImpl::ParkingMode::kIfCompressedDataExists);
EXPECT_TRUE(parkable.Impl()->is_parked());
scoped_task_environment_.FastForwardUntilNoTasksRemain();
}
TEST_F(ParkableStringTest, OnPurgeMemory) {
ParkableString parkable = CreateAndParkAll();
parkable.ToString();
EXPECT_FALSE(parkable.Impl()->is_parked());
EXPECT_TRUE(parkable.Impl()->has_compressed_data());
MemoryCoordinator::Instance().OnPurgeMemory();
EXPECT_TRUE(parkable.Impl()->is_parked());
}
} // namespace blink } // namespace blink
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment