Use a bit vector in ADCE
The unordered_set in ADCE that holds all of the live instructions takes
a very long time to be destroyed. In some shaders, it takes over 40% of
the time.
If we look at the unique ids of the live instructions, I believe they
are dense enough make a simple bit vector a good choice for to hold that
data. When I check the density of the bit vector for larger shaders, we
are usually using less than 4 bytes per element in the vector, and
almost always less than 16.
So, in this commit, I introduce a simple bit vector class, and
use it in ADCE.
This help improve the compile time for some shaders on windows by the
40% mentioned above.
Contributes to https://github.com/KhronosGroup/SPIRV-Tools/issues/1328.
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index e944475..d9f5955 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -218,6 +218,7 @@
${CMAKE_CURRENT_SOURCE_DIR}/util/bitutils.h
${CMAKE_CURRENT_SOURCE_DIR}/util/bit_stream.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/util/bit_vector.h
${CMAKE_CURRENT_SOURCE_DIR}/util/hex_float.h
${CMAKE_CURRENT_SOURCE_DIR}/util/parse_number.h
${CMAKE_CURRENT_SOURCE_DIR}/util/string_utils.h
@@ -253,6 +254,7 @@
${CMAKE_CURRENT_SOURCE_DIR}/validate.h
${CMAKE_CURRENT_SOURCE_DIR}/util/bit_stream.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/util/bit_vector.cpp
${CMAKE_CURRENT_SOURCE_DIR}/util/parse_number.cpp
${CMAKE_CURRENT_SOURCE_DIR}/util/string_utils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/assembly_grammar.cpp
diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp
index 014ce00..71ad809 100644
--- a/source/opt/aggressive_dead_code_elim_pass.cpp
+++ b/source/opt/aggressive_dead_code_elim_pass.cpp
@@ -477,11 +477,6 @@
void AggressiveDCEPass::Initialize(ir::IRContext* c) {
InitializeProcessing(c);
- // Clear collections
- worklist_ = std::queue<ir::Instruction*>{};
- live_insts_.clear();
- live_local_vars_.clear();
-
// Initialize extensions whitelist
InitExtensions();
}
diff --git a/source/opt/aggressive_dead_code_elim_pass.h b/source/opt/aggressive_dead_code_elim_pass.h
index 9c1749b..aefdd70 100644
--- a/source/opt/aggressive_dead_code_elim_pass.h
+++ b/source/opt/aggressive_dead_code_elim_pass.h
@@ -17,6 +17,7 @@
#ifndef LIBSPIRV_OPT_AGGRESSIVE_DCE_PASS_H_
#define LIBSPIRV_OPT_AGGRESSIVE_DCE_PASS_H_
+#include <util/bit_vector.h>
#include <algorithm>
#include <map>
#include <queue>
@@ -60,7 +61,7 @@
// Return true if |inst| is marked live.
bool IsLive(const ir::Instruction* inst) const {
- return live_insts_.find(inst) != live_insts_.end();
+ return live_insts_.Get(inst->unique_id());
}
// Returns true if |inst| is dead.
@@ -72,7 +73,9 @@
// Add |inst| to worklist_ and live_insts_.
void AddToWorklist(ir::Instruction* inst) {
- if (live_insts_.insert(inst).second) worklist_.push(inst);
+ if (!live_insts_.Set(inst->unique_id())) {
+ worklist_.push(inst);
+ }
}
// Add all store instruction which use |ptrId|, directly or indirectly,
@@ -168,7 +171,7 @@
std::vector<ir::Instruction*> private_stores_;
// Live Instructions
- std::unordered_set<const ir::Instruction*> live_insts_;
+ utils::BitVector live_insts_;
// Live Local Variables
std::unordered_set<uint32_t> live_local_vars_;
diff --git a/source/util/bit_vector.cpp b/source/util/bit_vector.cpp
new file mode 100644
index 0000000..80c0f0c
--- /dev/null
+++ b/source/util/bit_vector.cpp
@@ -0,0 +1,40 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "bit_vector.h"
+
+#include <iostream>
+
+namespace spvtools {
+namespace utils {
+
+void BitVector::ReportDensity(std::ostream& out) {
+ uint32_t count = 0;
+
+ for (BitContainer e : bits_) {
+ while (e != 0) {
+ if ((e & 1) != 0) {
+ ++count;
+ }
+ e = e >> 1;
+ }
+ }
+
+ out << "count=" << count
+ << ", total size (bytes)=" << bits_.size() * sizeof(BitContainer)
+ << ", bytes per element="
+ << (double)(bits_.size() * sizeof(BitContainer)) / (double)(count);
+}
+} // namespace utils
+} // namespace spvtools
diff --git a/source/util/bit_vector.h b/source/util/bit_vector.h
new file mode 100644
index 0000000..f8a654d
--- /dev/null
+++ b/source/util/bit_vector.h
@@ -0,0 +1,102 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef LIBSPIRV_UTILS_BIT_VECTOR_H_
+#define LIBSPIRV_UTILS_BIT_VECTOR_H_
+
+#include <cstdint>
+#include <iosfwd>
+#include <vector>
+
+namespace spvtools {
+namespace utils {
+
+// Implements a bit vector class.
+//
+// All bits default to zero, and the upper bound is 2^32-1.
+class BitVector {
+ private:
+ using BitContainer = uint64_t;
+ enum { kBitContainerSize = 64 };
+ enum { kInitialNumBits = 1024 };
+
+ public:
+ // Creates a bit vector contianing 0s.
+ BitVector() : bits_(kInitialNumBits / kBitContainerSize, 0) {}
+
+ // Sets the |i|th bit to 1. Returns the |i|th bit before it was set.
+ bool Set(uint32_t i) {
+ uint32_t element_index = i / kBitContainerSize;
+ uint32_t bit_in_element = i % kBitContainerSize;
+
+ if (element_index >= bits_.size()) {
+ bits_.resize(element_index + 1, 0);
+ }
+
+ BitContainer original = bits_[element_index];
+ BitContainer ith_bit = static_cast<BitContainer>(1) << bit_in_element;
+
+ if ((original & ith_bit) != 0) {
+ return true;
+ } else {
+ bits_[element_index] = original | ith_bit;
+ return false;
+ }
+ }
+
+ // Sets the |i|th bit to 0. Return the |i|th bit before it was cleared.
+ bool Clear(uint32_t i) {
+ uint32_t element_index = i / kBitContainerSize;
+ uint32_t bit_in_element = i % kBitContainerSize;
+
+ if (element_index >= bits_.size()) {
+ return false;
+ }
+
+ BitContainer original = bits_[element_index];
+ BitContainer ith_bit = static_cast<BitContainer>(1) << bit_in_element;
+
+ if ((original & ith_bit) == 0) {
+ return false;
+ } else {
+ bits_[element_index] = original & (~ith_bit);
+ return true;
+ }
+ }
+
+ // Returns the |i|th bit.
+ bool Get(uint32_t i) const {
+ uint32_t element_index = i / kBitContainerSize;
+ uint32_t bit_in_element = i % kBitContainerSize;
+
+ if (element_index >= bits_.size()) {
+ return false;
+ }
+
+ return (bits_[element_index] &
+ (static_cast<BitContainer>(1) << bit_in_element)) != 0;
+ }
+
+ // Print a report on the densicy of the bit vector, number of 1 bits, number
+ // of bytes, and average bytes for 1 bit, to |out|.
+ void ReportDensity(std::ostream& out);
+
+ private:
+ std::vector<BitContainer> bits_;
+};
+
+} // namespace utils
+} // namespace spvtools
+
+#endif // LIBSPIRV_UTILS_BIT_VECTOR_H_
diff --git a/test/util/CMakeLists.txt b/test/util/CMakeLists.txt
index 9b6ca2c..d3bbc18 100644
--- a/test/util/CMakeLists.txt
+++ b/test/util/CMakeLists.txt
@@ -14,5 +14,8 @@
add_spvtools_unittest(TARGET util_intrusive_list
SRCS ilist_test.cpp
- LIBS SPIRV-Tools-opt
+)
+
+add_spvtools_unittest(TARGET bit_vector
+ SRCS bit_vector_test.cpp
)
diff --git a/test/util/bit_vector_test.cpp b/test/util/bit_vector_test.cpp
new file mode 100644
index 0000000..12528fe
--- /dev/null
+++ b/test/util/bit_vector_test.cpp
@@ -0,0 +1,112 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <vector>
+
+#include "gmock/gmock.h"
+
+#include "util/bit_vector.h"
+
+namespace {
+
+using spvtools::utils::BitVector;
+using BitVectorTest = ::testing::Test;
+
+TEST(BitVectorTest, Initialize) {
+ BitVector bvec;
+
+ // Checks that all values are 0. Also tests checking a bit past the end of
+ // the vector containing the bits.
+ for (int i = 1; i < 10000; i *= 2) {
+ EXPECT_FALSE(bvec.Get(i));
+ }
+}
+
+TEST(BitVectorTest, Set) {
+ BitVector bvec;
+
+ // Since 10,000 is larger than the initial size, this tests the resizing
+ // code.
+ for (int i = 3; i < 10000; i *= 2) {
+ bvec.Set(i);
+ }
+
+ // Check that bits that were not set are 0.
+ for (int i = 1; i < 10000; i *= 2) {
+ EXPECT_FALSE(bvec.Get(i));
+ }
+
+ // Check that bits that were set are 1.
+ for (int i = 3; i < 10000; i *= 2) {
+ EXPECT_TRUE(bvec.Get(i));
+ }
+}
+
+TEST(BitVectorTest, SetReturnValue) {
+ BitVector bvec;
+
+ // Make sure |Set| returns false when the bit was not set.
+ for (int i = 3; i < 10000; i *= 2) {
+ EXPECT_FALSE(bvec.Set(i));
+ }
+
+ // Make sure |Set| returns true when the bit was already set.
+ for (int i = 3; i < 10000; i *= 2) {
+ EXPECT_TRUE(bvec.Set(i));
+ }
+}
+
+TEST(BitVectorTest, Clear) {
+ BitVector bvec;
+ for (int i = 3; i < 10000; i *= 2) {
+ bvec.Set(i);
+ }
+
+ // Check that the bits were properly set.
+ for (int i = 3; i < 10000; i *= 2) {
+ EXPECT_TRUE(bvec.Get(i));
+ }
+
+ // Clear all of the bits except for bit 3.
+ for (int i = 6; i < 10000; i *= 2) {
+ bvec.Clear(i);
+ }
+
+ // Make sure bit 3 was not cleared.
+ EXPECT_TRUE(bvec.Get(3));
+
+ // Make sure all of the other bits that were set have been cleared.
+ for (int i = 6; i < 10000; i *= 2) {
+ EXPECT_FALSE(bvec.Get(i));
+ }
+}
+
+TEST(BitVectorTest, ClearReturnValue) {
+ BitVector bvec;
+ for (int i = 3; i < 10000; i *= 2) {
+ bvec.Set(i);
+ }
+
+ // Make sure |Clear| returns true if the bit was set.
+ for (int i = 3; i < 10000; i *= 2) {
+ EXPECT_TRUE(bvec.Clear(i));
+ }
+
+ // Make sure |Clear| returns false if the bit was not set.
+ for (int i = 3; i < 10000; i *= 2) {
+ EXPECT_FALSE(bvec.Clear(i));
+ }
+}
+
+} // namespace