Inline a 1 bit payload in RefCounted

This changes RefCounted to increment and decrement the refcount by steps
of 2, so that a 1 bit payload can be inlined in the refcount. This
avoids using a whole boolean + padding (4 or 8 bytes in general) to know
if objects are errors.

Fixes a longstanding optimization TODO.

BUG=dawn:105

Change-Id: I5e8f66465d23772fb6082ea3e0d5d4aba2132648
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/13680
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
This commit is contained in:
Corentin Wallez 2019-11-19 14:35:10 +00:00 committed by Commit Bot service account
parent 9c81f8738a
commit 083a1ce2bf
5 changed files with 63 additions and 22 deletions

View File

@ -16,10 +16,14 @@
namespace dawn_native {
ObjectBase::ObjectBase(DeviceBase* device) : mDevice(device), mIsError(false) {
static constexpr uint64_t kErrorPayload = 0;
static constexpr uint64_t kNotErrorPayload = 1;
ObjectBase::ObjectBase(DeviceBase* device) : RefCounted(kNotErrorPayload), mDevice(device) {
}
ObjectBase::ObjectBase(DeviceBase* device, ErrorTag) : mDevice(device), mIsError(true) {
ObjectBase::ObjectBase(DeviceBase* device, ErrorTag)
: RefCounted(kErrorPayload), mDevice(device) {
}
ObjectBase::~ObjectBase() {
@ -30,7 +34,7 @@ namespace dawn_native {
}
bool ObjectBase::IsError() const {
return mIsError;
return GetRefCountPayload() == kErrorPayload;
}
} // namespace dawn_native

View File

@ -35,10 +35,6 @@ namespace dawn_native {
private:
DeviceBase* mDevice;
// TODO(cwallez@chromium.org): This most likely adds 4 bytes to most Dawn objects, see if
// that bit can be hidden in the refcount once it is a single 64bit refcount.
// See https://bugs.chromium.org/p/dawn/issues/detail?id=105
bool mIsError;
};
} // namespace dawn_native

View File

@ -18,26 +18,39 @@
namespace dawn_native {
RefCounted::RefCounted() {
static constexpr size_t kPayloadBits = 1;
static constexpr uint64_t kPayloadMask = (uint64_t(1) << kPayloadBits) - 1;
static constexpr uint64_t kRefCountIncrement = (uint64_t(1) << kPayloadBits);
RefCounted::RefCounted(uint64_t payload) : mRefCount(kRefCountIncrement + payload) {
ASSERT((payload & kPayloadMask) == payload);
}
RefCounted::~RefCounted() {
}
uint64_t RefCounted::GetRefCount() const {
return mRefCount;
uint64_t RefCounted::GetRefCountForTesting() const {
return mRefCount >> kPayloadBits;
}
uint64_t RefCounted::GetRefCountPayload() const {
// We only care about the payload bits of the refcount. These never change after
// initialization so we can use the relaxed memory order. The order doesn't guarantee
// anything except the atomicity of the load, which is enough since any past values of the
// atomic will have the correct payload bits.
return kPayloadMask & mRefCount.load(std::memory_order_relaxed);
}
void RefCounted::Reference() {
ASSERT(mRefCount != 0);
mRefCount++;
ASSERT((mRefCount & ~kPayloadMask) != 0);
mRefCount += kRefCountIncrement;
}
void RefCounted::Release() {
ASSERT(mRefCount != 0);
ASSERT((mRefCount & ~kPayloadMask) != 0);
mRefCount--;
if (mRefCount == 0) {
mRefCount -= kRefCountIncrement;
if (mRefCount < kRefCountIncrement) {
delete this;
}
}

View File

@ -22,17 +22,18 @@ namespace dawn_native {
class RefCounted {
public:
RefCounted();
RefCounted(uint64_t payload = 0);
virtual ~RefCounted();
uint64_t GetRefCount() const;
uint64_t GetRefCountForTesting() const;
uint64_t GetRefCountPayload() const;
// Dawn API
void Reference();
void Release();
protected:
std::atomic_uint64_t mRefCount = {1};
std::atomic_uint64_t mRefCount;
};
template <typename T>

View File

@ -20,8 +20,7 @@
using namespace dawn_native;
struct RCTest : public RefCounted {
RCTest() {
}
using RefCounted::RefCounted;
RCTest(bool* deleted): deleted(deleted) {
}
@ -76,7 +75,7 @@ TEST(RefCounted, RaceOnReferenceRelease) {
t1.join();
t2.join();
ASSERT_EQ(test->GetRefCount(), 200001u);
ASSERT_EQ(test->GetRefCountForTesting(), 200001u);
auto releaseManyTimes = [test]() {
for (uint32_t i = 0; i < 100000; ++i) {
@ -88,7 +87,7 @@ TEST(RefCounted, RaceOnReferenceRelease) {
std::thread t4(releaseManyTimes);
t3.join();
t4.join();
ASSERT_EQ(test->GetRefCount(), 1u);
ASSERT_EQ(test->GetRefCountForTesting(), 1u);
}
// Test Ref remove reference when going out of scope
@ -207,3 +206,31 @@ TEST(Ref, MoveAssignment) {
destination = nullptr;
ASSERT_TRUE(deleted);
}
// Test the payload initial value is set correctly
TEST(Ref, InitialPayloadValue) {
RCTest* testDefaultConstructor = new RCTest();
ASSERT_EQ(testDefaultConstructor->GetRefCountPayload(), 0u);
testDefaultConstructor->Release();
RCTest* testZero = new RCTest(0);
ASSERT_EQ(testZero->GetRefCountPayload(), 0u);
testZero->Release();
RCTest* testOne = new RCTest(1);
ASSERT_EQ(testOne->GetRefCountPayload(), 1u);
testOne->Release();
}
// Test that the payload survives ref and release operations
TEST(Ref, PayloadUnchangedByRefCounting) {
RCTest* test = new RCTest(1);
ASSERT_EQ(test->GetRefCountPayload(), 1u);
test->Reference();
ASSERT_EQ(test->GetRefCountPayload(), 1u);
test->Release();
ASSERT_EQ(test->GetRefCountPayload(), 1u);
test->Release();
}