Avoid recompiling identical SPIR-V binaries
We previously took the serial ID from the unoptimized SPIR-V module, for
use as part of the Routine cache keys. If a pipeline is created with
specialization constants, we assumed them to produce unique optimized
SPIR-V binaries, regardless of their actual values, and obtained a new
serial ID each time. If a shader module was used with identical
specialization data, we would redundantly JIT-compile the same routine if
no other state used by the pipeline stage was different.
This change takes advantage of the fact that the pipeline cache doesn't
add a new entry (of optimized SPIR-V) for an already optimized module
unless the values of the specialization constants are different and
thus would produce a distinct binary (assuming said specialization
constants affect the code). A new serial ID is now associated with every
new SPIR-V binary. For a (pipeline) cache hit, the existing entry's
ID is used as part of the lookup key for the routine cache.
Note an application has to use a VkPipelineCache object to take
advantage of this.
When there's no pipeline cache, we still inherit the ID from the
unoptimized binary, to avoid Reactor routine recompiles.
Bug: b/172839674
Change-Id: I18b937800ea021235081bba0d827436265816414
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/57988
Kokoro-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Alexis Hétu <sugoi@google.com>
Tested-by: Nicolas Capens <nicolascapens@google.com>
diff --git a/src/Device/PixelProcessor.cpp b/src/Device/PixelProcessor.cpp
index 617c0ad..fb492f9 100644
--- a/src/Device/PixelProcessor.cpp
+++ b/src/Device/PixelProcessor.cpp
@@ -91,7 +91,7 @@
if(fragmentShader)
{
- state.shaderID = fragmentShader->getSerialID();
+ state.shaderID = fragmentShader->getIdentifier();
state.pipelineLayoutIdentifier = pipelineState.getPipelineLayout()->identifier;
}
else
diff --git a/src/Device/VertexProcessor.cpp b/src/Device/VertexProcessor.cpp
index 68bdfc5..833fa6a 100644
--- a/src/Device/VertexProcessor.cpp
+++ b/src/Device/VertexProcessor.cpp
@@ -69,7 +69,7 @@
{
State state;
- state.shaderID = vertexShader->getSerialID();
+ state.shaderID = vertexShader->getIdentifier();
state.pipelineLayoutIdentifier = pipelineState.getPipelineLayout()->identifier;
state.robustBufferAccess = pipelineState.getRobustBufferAccess();
state.isPoint = pipelineState.getTopology() == VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
diff --git a/src/Pipeline/BUILD.gn b/src/Pipeline/BUILD.gn
index d88e255..6b44f4b 100644
--- a/src/Pipeline/BUILD.gn
+++ b/src/Pipeline/BUILD.gn
@@ -40,6 +40,7 @@
"SamplerCore.cpp",
"SetupRoutine.cpp",
"ShaderCore.cpp",
+ "SpirvBinary.cpp",
"SpirvShader.cpp",
"SpirvShaderArithmetic.cpp",
"SpirvShaderControlFlow.cpp",
diff --git a/src/Pipeline/CMakeLists.txt b/src/Pipeline/CMakeLists.txt
index 4daf9e1..2b5b15c 100644
--- a/src/Pipeline/CMakeLists.txt
+++ b/src/Pipeline/CMakeLists.txt
@@ -33,6 +33,7 @@
ShaderCore.cpp
ShaderCore.hpp
SpirvBinary.hpp
+ SpirvBinary.cpp
SpirvID.hpp
SpirvShader.cpp
SpirvShader.hpp
diff --git a/src/Pipeline/SpirvBinary.cpp b/src/Pipeline/SpirvBinary.cpp
new file mode 100644
index 0000000..88ade4d
--- /dev/null
+++ b/src/Pipeline/SpirvBinary.cpp
@@ -0,0 +1,36 @@
+// Copyright 2021 The SwiftShader Authors. All Rights Reserved.
+//
+// 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 "SpirvBinary.hpp"
+
+namespace sw {
+
+std::atomic<uint32_t> SpirvBinary::serialCounter(1); // Start at 1, 0 is invalid shader.
+
+SpirvBinary::SpirvBinary()
+ : identifier(serialCounter++)
+{}
+
+SpirvBinary::SpirvBinary(const uint32_t *binary, uint32_t wordCount)
+ : std::vector<uint32_t>(binary, binary + wordCount)
+ , identifier(serialCounter++)
+{
+}
+
+void SpirvBinary::inheritIdentifier(const SpirvBinary &binary)
+{
+ identifier = binary.identifier;
+}
+
+} // namespace sw
\ No newline at end of file
diff --git a/src/Pipeline/SpirvBinary.hpp b/src/Pipeline/SpirvBinary.hpp
index b4766bb..9ff4536 100644
--- a/src/Pipeline/SpirvBinary.hpp
+++ b/src/Pipeline/SpirvBinary.hpp
@@ -1,4 +1,4 @@
-// Copyright 2018 The SwiftShader Authors. All Rights Reserved.
+// Copyright 2021 The SwiftShader Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
#ifndef sw_SpirvBinary_hpp
#define sw_SpirvBinary_hpp
+#include <atomic>
#include <cstdint>
#include <vector>
@@ -23,12 +24,17 @@
class SpirvBinary : public std::vector<uint32_t>
{
public:
- SpirvBinary() = default;
+ SpirvBinary();
+ SpirvBinary(const uint32_t *binary, uint32_t wordCount);
- SpirvBinary(const uint32_t *binary, uint32_t wordCount)
- : std::vector<uint32_t>(binary, binary + wordCount)
- {
- }
+ inline uint32_t getIdentifier() const { return identifier; };
+
+ void inheritIdentifier(const SpirvBinary &binary);
+
+private:
+ static std::atomic<uint32_t> serialCounter;
+
+ uint32_t identifier;
};
} // namespace sw
diff --git a/src/Pipeline/SpirvShader.cpp b/src/Pipeline/SpirvShader.cpp
index 4d434ce..c56fd38 100644
--- a/src/Pipeline/SpirvShader.cpp
+++ b/src/Pipeline/SpirvShader.cpp
@@ -26,7 +26,6 @@
namespace sw {
SpirvShader::SpirvShader(
- uint32_t codeSerialID,
VkShaderStageFlagBits pipelineStage,
const char *entryPointName,
SpirvBinary const &insns,
@@ -37,7 +36,6 @@
: insns{ insns }
, inputs{ MAX_INTERFACE_COMPONENTS }
, outputs{ MAX_INTERFACE_COMPONENTS }
- , codeSerialID(codeSerialID)
, robustBufferAccess(robustBufferAccess)
{
ASSERT(insns.size() > 0);
diff --git a/src/Pipeline/SpirvShader.hpp b/src/Pipeline/SpirvShader.hpp
index 151e69d..9e5a42d 100644
--- a/src/Pipeline/SpirvShader.hpp
+++ b/src/Pipeline/SpirvShader.hpp
@@ -557,13 +557,12 @@
// This method is for retrieving an ID that uniquely identifies the
// shader entry point represented by this object.
- uint64_t getSerialID() const
+ uint64_t getIdentifier() const
{
- return ((uint64_t)entryPoint.value() << 32) | codeSerialID;
+ return ((uint64_t)entryPoint.value() << 32) | insns.getIdentifier();
}
- SpirvShader(uint32_t codeSerialID,
- VkShaderStageFlagBits stage,
+ SpirvShader(VkShaderStageFlagBits stage,
const char *entryPointName,
SpirvBinary const &insns,
const vk::RenderPass *renderPass,
@@ -835,7 +834,6 @@
WorkgroupMemory workgroupMemory;
private:
- const uint32_t codeSerialID;
const bool robustBufferAccess;
Function::ID entryPoint;
diff --git a/src/Vulkan/VkPipeline.cpp b/src/Vulkan/VkPipeline.cpp
index 168f6a3..9c06350 100644
--- a/src/Vulkan/VkPipeline.cpp
+++ b/src/Vulkan/VkPipeline.cpp
@@ -35,7 +35,7 @@
// optimizeSpirv() applies and freezes specializations into constants, and runs spirv-opt.
sw::SpirvBinary optimizeSpirv(const vk::PipelineCache::SpirvBinaryKey &key)
{
- const sw::SpirvBinary &code = key.getInsns();
+ const sw::SpirvBinary &code = key.getBinary();
const VkSpecializationInfo *specializationInfo = key.getSpecializationInfo();
bool optimize = key.getOptimization();
@@ -232,14 +232,17 @@
else
{
spirv = optimizeSpirv(key);
+
+ // If the pipeline does not have specialization constants, there's a 1-to-1 mapping between the unoptimized and optimized SPIR-V,
+ // so we should use a 1-to-1 mapping of the identifiers to avoid JIT routine recompiles.
+ if(!key.getSpecializationInfo())
+ {
+ spirv.inheritIdentifier(key.getBinary());
+ }
}
- // If the pipeline has specialization constants, assume they're unique and
- // use a new serial ID so the shader gets recompiled.
- uint32_t codeSerialID = (key.getSpecializationInfo() ? vk::ShaderModule::nextSerialID() : module->getSerialID());
-
// TODO(b/201798871): use allocator.
- auto shader = std::make_shared<sw::SpirvShader>(codeSerialID, pStage->stage, pStage->pName, spirv,
+ auto shader = std::make_shared<sw::SpirvShader>(pStage->stage, pStage->pName, spirv,
vk::Cast(pCreateInfo->renderPass), pCreateInfo->subpass, robustBufferAccess, dbgctx);
setShader(pStage->stage, shader);
@@ -289,17 +292,20 @@
else
{
spirv = optimizeSpirv(shaderKey);
+
+ // If the pipeline does not have specialization constants, there's a 1-to-1 mapping between the unoptimized and optimized SPIR-V,
+ // so we should use a 1-to-1 mapping of the identifiers to avoid JIT routine recompiles.
+ if(!shaderKey.getSpecializationInfo())
+ {
+ spirv.inheritIdentifier(shaderKey.getBinary());
+ }
}
- // If the pipeline has specialization constants, assume they're unique and
- // use a new serial ID so the shader gets recompiled.
- uint32_t codeSerialID = (stage.pSpecializationInfo ? vk::ShaderModule::nextSerialID() : module->getSerialID());
-
// TODO(b/201798871): use allocator.
- shader = std::make_shared<sw::SpirvShader>(codeSerialID, stage.stage, stage.pName, spirv,
+ shader = std::make_shared<sw::SpirvShader>(stage.stage, stage.pName, spirv,
nullptr, 0, robustBufferAccess, dbgctx);
- const PipelineCache::ComputeProgramKey programKey(shader->getSerialID(), layout->identifier);
+ const PipelineCache::ComputeProgramKey programKey(shader->getIdentifier(), layout->identifier);
if(pPipelineCache)
{
diff --git a/src/Vulkan/VkPipelineCache.cpp b/src/Vulkan/VkPipelineCache.cpp
index c4e9c31..c187632 100644
--- a/src/Vulkan/VkPipelineCache.cpp
+++ b/src/Vulkan/VkPipelineCache.cpp
@@ -18,10 +18,10 @@
namespace vk {
-PipelineCache::SpirvBinaryKey::SpirvBinaryKey(const sw::SpirvBinary &insns,
+PipelineCache::SpirvBinaryKey::SpirvBinaryKey(const sw::SpirvBinary &spirv,
const VkSpecializationInfo *specializationInfo,
bool optimize)
- : insns(insns)
+ : spirv(spirv)
, specializationInfo(specializationInfo)
, optimize(optimize)
{
@@ -29,12 +29,12 @@
bool PipelineCache::SpirvBinaryKey::operator<(const SpirvBinaryKey &other) const
{
- if(insns.size() != other.insns.size())
+ if(spirv.size() != other.spirv.size())
{
- return insns.size() < other.insns.size();
+ return spirv.size() < other.spirv.size();
}
- int cmp = memcmp(insns.data(), other.insns.data(), insns.size() * sizeof(uint32_t));
+ int cmp = memcmp(spirv.data(), other.spirv.data(), spirv.size() * sizeof(uint32_t));
if(cmp != 0)
{
return cmp < 0;
diff --git a/src/Vulkan/VkPipelineCache.hpp b/src/Vulkan/VkPipelineCache.hpp
index 9a60954..d875764 100644
--- a/src/Vulkan/VkPipelineCache.hpp
+++ b/src/Vulkan/VkPipelineCache.hpp
@@ -57,18 +57,18 @@
struct SpirvBinaryKey
{
- SpirvBinaryKey(const sw::SpirvBinary &insns,
+ SpirvBinaryKey(const sw::SpirvBinary &spirv,
const VkSpecializationInfo *specializationInfo,
bool optimize);
bool operator<(const SpirvBinaryKey &other) const;
- const sw::SpirvBinary &getInsns() const { return insns; }
+ const sw::SpirvBinary &getBinary() const { return spirv; }
const VkSpecializationInfo *getSpecializationInfo() const { return specializationInfo.get(); }
bool getOptimization() const { return optimize; }
private:
- const sw::SpirvBinary insns;
+ const sw::SpirvBinary spirv;
const vk::SpecializationInfo specializationInfo;
const bool optimize;
};
diff --git a/src/Vulkan/VkShaderModule.cpp b/src/Vulkan/VkShaderModule.cpp
index b49151d..edf495b 100644
--- a/src/Vulkan/VkShaderModule.cpp
+++ b/src/Vulkan/VkShaderModule.cpp
@@ -20,11 +20,8 @@
namespace vk {
-std::atomic<uint32_t> ShaderModule::serialCounter(1); // Start at 1, 0 is invalid shader.
-
ShaderModule::ShaderModule(const VkShaderModuleCreateInfo *pCreateInfo, void *mem)
- : serialID(nextSerialID())
- , binary(pCreateInfo->pCode, pCreateInfo->codeSize / sizeof(uint32_t))
+ : binary(pCreateInfo->pCode, pCreateInfo->codeSize / sizeof(uint32_t))
{
#if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)
spvtools::SpirvTools spirvTools(SPIRV_VERSION);
diff --git a/src/Vulkan/VkShaderModule.hpp b/src/Vulkan/VkShaderModule.hpp
index 611aebf..2bba35d 100644
--- a/src/Vulkan/VkShaderModule.hpp
+++ b/src/Vulkan/VkShaderModule.hpp
@@ -18,8 +18,6 @@
#include "VkObject.hpp"
#include "Pipeline/SpirvBinary.hpp"
-#include <atomic>
-
namespace vk {
class ShaderModule : public Object<ShaderModule, VkShaderModule>
@@ -30,13 +28,7 @@
static size_t ComputeRequiredAllocationSize(const VkShaderModuleCreateInfo *pCreateInfo);
const sw::SpirvBinary &getBinary() const { return binary; }
- uint32_t getSerialID() const { return serialID; }
- static uint32_t nextSerialID() { return serialCounter++; }
-
private:
- const uint32_t serialID;
- static std::atomic<uint32_t> serialCounter;
-
sw::SpirvBinary binary;
};