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>
diff --git a/src/dawn_native/ObjectBase.cpp b/src/dawn_native/ObjectBase.cpp
index 3e40af4..0f8c1a3 100644
--- a/src/dawn_native/ObjectBase.cpp
+++ b/src/dawn_native/ObjectBase.cpp
@@ -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 @@
}
bool ObjectBase::IsError() const {
- return mIsError;
+ return GetRefCountPayload() == kErrorPayload;
}
} // namespace dawn_native
diff --git a/src/dawn_native/ObjectBase.h b/src/dawn_native/ObjectBase.h
index 02dd7ec..2a1f86a 100644
--- a/src/dawn_native/ObjectBase.h
+++ b/src/dawn_native/ObjectBase.h
@@ -35,10 +35,6 @@
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
diff --git a/src/dawn_native/RefCounted.cpp b/src/dawn_native/RefCounted.cpp
index 5ec8050..6782c14 100644
--- a/src/dawn_native/RefCounted.cpp
+++ b/src/dawn_native/RefCounted.cpp
@@ -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;
}
}
diff --git a/src/dawn_native/RefCounted.h b/src/dawn_native/RefCounted.h
index 89b0666..7b94d2e 100644
--- a/src/dawn_native/RefCounted.h
+++ b/src/dawn_native/RefCounted.h
@@ -22,17 +22,18 @@
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>
diff --git a/src/tests/unittests/RefCountedTests.cpp b/src/tests/unittests/RefCountedTests.cpp
index e8a7f6f..03b2dff 100644
--- a/src/tests/unittests/RefCountedTests.cpp
+++ b/src/tests/unittests/RefCountedTests.cpp
@@ -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 @@
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 @@
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 @@
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();
+}