【发布时间】:2021-02-18 23:20:11
【问题描述】:
我使用这段代码做了一些分析
#include "Timer.h"
#include <iostream>
enum class BackendAPI {
B_API_NONE,
B_API_VULKAN,
B_API_DIRECTX_12,
B_API_WEB_GPU,
};
namespace Functional
{
typedef void* VertexBufferHandle;
namespace Vulkan
{
struct VulkanVertexBuffer {};
VertexBufferHandle CreateVertexBuffer(size_t size)
{
return nullptr;
}
__forceinline void Hello() {}
__forceinline void Bello() {}
__forceinline void Mello() {}
}
class RenderBackend {
public:
RenderBackend() {}
~RenderBackend() {}
void SetupBackendMethods(BackendAPI api)
{
switch (api)
{
case BackendAPI::B_API_VULKAN:
{
CreateVertexBuffer = Vulkan::CreateVertexBuffer;
Hello = Vulkan::Hello;
Bello = Vulkan::Bello;
Mello = Vulkan::Mello;
}
break;
case BackendAPI::B_API_DIRECTX_12:
break;
case BackendAPI::B_API_WEB_GPU:
break;
default:
break;
}
}
VertexBufferHandle(*CreateVertexBuffer)(size_t size) = nullptr;
void (*Hello)() = nullptr;
void (*Bello)() = nullptr;
void (*Mello)() = nullptr;
};
}
namespace ObjectOriented
{
struct VertexBuffer {};
class RenderBackend {
public:
RenderBackend() {}
virtual ~RenderBackend() {}
virtual VertexBuffer* CreateVertexBuffer(size_t size) = 0;
virtual void Hello() = 0;
virtual void Bello() = 0;
virtual void Mello() = 0;
};
class VulkanBackend final : public RenderBackend {
struct VulkanVertexBuffer : public VertexBuffer {};
public:
VulkanBackend() {}
~VulkanBackend() {}
__forceinline virtual VertexBuffer* CreateVertexBuffer(size_t size) override
{
return nullptr;
}
__forceinline virtual void Hello() override {}
__forceinline virtual void Bello() override {}
__forceinline virtual void Mello() override {}
};
RenderBackend* CreateBackend(BackendAPI api)
{
switch (api)
{
case BackendAPI::B_API_VULKAN:
return new VulkanBackend;
break;
case BackendAPI::B_API_DIRECTX_12:
break;
case BackendAPI::B_API_WEB_GPU:
break;
default:
break;
}
return nullptr;
}
}
int main()
{
constexpr int maxItr = 1000000;
for (int i = 0; i < 100; i++)
{
int counter = maxItr;
Timer t;
auto pBackend = ObjectOriented::CreateBackend(BackendAPI::B_API_VULKAN);
while (counter--)
{
pBackend->Hello();
pBackend->Bello();
pBackend->Mello();
auto pRef = pBackend->CreateVertexBuffer(100);
}
delete pBackend;
}
std::cout << "\n";
for (int i = 0; i < 100; i++)
{
int counter = maxItr;
Timer t;
{
Functional::RenderBackend backend;
backend.SetupBackendMethods(BackendAPI::B_API_VULKAN);
while (counter--)
{
backend.Hello();
backend.Bello();
backend.Mello();
auto pRef = backend.CreateVertexBuffer(100);
}
}
}
}
`#include "Timer.h" 在其中
#pragma once
#include <chrono>
/**
* Timer class.
* This calculates the total time taken from creation till the termination of the object.
*/
class Timer {
public:
/**
* Default contructor.
*/
Timer()
{
// Set the time point at the creation of the object.
startPoint = std::chrono::high_resolution_clock::now();
}
/**
* Default destructor.
*/
~Timer()
{
// Get the time point of the time of the object's termination.
auto endPoint = std::chrono::high_resolution_clock::now();
// Convert time points.
long long start = std::chrono::time_point_cast<std::chrono::microseconds>(startPoint).time_since_epoch().count();
long long end = std::chrono::time_point_cast<std::chrono::microseconds>(endPoint).time_since_epoch().count();
// Print the time to the console.
printf("Time taken: %15I64d\n", static_cast<__int64>(end - start));
}
private:
std::chrono::time_point<std::chrono::high_resolution_clock> startPoint; // The start time point.
};
并在一个图中输出后(使用Visual Studio 2019中的Release配置编译),结果如下,
注意: 上面的代码是为了在构建大型库时分析函数式与面向对象方法的性能差异。分析是通过运行应用程序 5 次,重新编译源代码来完成的。每次运行有 100 次迭代。测试以两种方式进行(首先面向对象,其次是功能,反之亦然),但性能结果或多或少是相同的。
我知道继承有点慢,因为它必须在运行时从 V-Table 解析函数指针。但我不明白的部分是,如果我是正确的,函数指针也会在运行时解析。这意味着程序需要在执行之前获取函数代码。
所以我的问题是,
- 为什么函数指针的性能比虚方法好一些?
- 为什么虚方法在某些时候性能下降,而函数指针却有些稳定?
谢谢!
【问题讨论】:
-
您是否尝试过交换程序的顺序并先进行功能测试?
-
@AlanBirtles 让我试试看。
-
@AlanBirtles 结果是一样的。
-
你听说过醉汉在路灯下找钥匙的故事吗?当被问及他为什么看那里时,他说“因为那里有光!”虽然您有一个模糊有趣的问题,但如果您关心的是性能,那么您就找错地方了。事实上,最大化性能的方法不是像这样预先猜测,而是完全开放地对问题的实际情况感到惊讶。这是我和其他人do it.
标签: c++ performance profiling function-pointers virtual-functions