我有一个实时视频流管道,可以对H.264执行RGB32帧编码。我的目标是NVIDIA硬件,因此我计划使用CUDA执行从RGB32到NV12的色彩空间转换。我查找了内核执行类似任务的示例,一切看起来都很好。然而,由于很多人提到数据传输速度是CPU到GPU通信的最关键点,我想知道是否有人有经验,这是将RGB32数据提供给CUDA内核的更好方法:
cudaMemcpy()
(至少this主题声明cudaMemcpy()
的性能优于OS图形堆栈Map()
如果有人有这方面的经验,那么我很高兴听到它,否则 - 基准测试是:)
答案 0 :(得分:6)
由于没有任何活动,我冒昧地做了基准测试,我将把所有内容留在这里,以便任何人都可以使用它们或评论改进。
我比较了1000次迭代的时间:
Map
/ memcpy
在每次迭代中动态Direct3D 11纹理上的映射内存/ Unmap
- 每次调用3ms Map
/ Unmap
(获取Map / Unmap开销的概念) - 每次调用1.4ms UpdateSubresource
在每次迭代中默认的Direct3D 11纹理(根据我的阅读,如果每帧有多个更新,这应该比动态表面慢) - 每次调用2.13ms cudaMemcpy
在每次迭代中从new
分配给cudaMalloc
分配的设备内存指针的随机指针 - 每次调用1.3ms cudaMemcpyAsync
从每次迭代中分配有new
到cudaMalloc
分配的设备内存指针的临时指针和最后一次迭代后的cudaDeviceSynchronize
- 每个1.25ms调用强> cudaMemcpyAsync
从cudaMalloc
分配的主机内存指针到每次迭代中cudaMalloc
分配的设备内存指针,并在最后一次迭代后cudaDeviceSynchronize
- 0.250ms调用强> 基本上我似乎应该坚持使用Cuda,因为它比使用Direct3D 11表面将数据从系统内存传输到GPU内存更快。
当Map
/ Unmap
本身为UpdateSubresource
/ Map
时,似乎Unmap
/ // STL
#include <iostream>
#include <cstdlib>
#include <memory>
#include <vector>
// ATL
#include <atlbase.h>
// CUDA
#include "cuda.h"
#include "cuda_runtime_api.h"
#pragma comment(lib, "cudart.lib")
// DXGI
#include <dxgi.h>
#pragma comment(lib, "dxgi.lib")
// D3D11
#include <d3d11.h>
#pragma comment(lib, "d3d11.lib")
int main(int argc, char** argv)
{
std::string sDeviceName("GeForce GTX 750 Ti");
std::wstring sDeviceNameWide(sDeviceName.begin(), sDeviceName.end());
const size_t nWidth = 1920, nHeight = 1080, nIterations = 1000;
#pragma region Direct3D 11
CComPtr<IDXGIFactory1> pDXGIFactory1;
ATLENSURE_SUCCEEDED(CreateDXGIFactory1(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(&pDXGIFactory1)));
ULONG nAdapterIndex = 0;
CComPtr<IDXGIAdapter1> pDXGIAdapter1;
DXGI_ADAPTER_DESC1 DXGIAdapterDescription1 = {};
bool bD3D11AdapterFound = false;
while (SUCCEEDED(pDXGIFactory1->EnumAdapters1(nAdapterIndex++, &pDXGIAdapter1)))
{
ATLENSURE_SUCCEEDED(pDXGIAdapter1->GetDesc1(&DXGIAdapterDescription1));
std::wstring sDescription(DXGIAdapterDescription1.Description);
if (sDescription.find(sDeviceNameWide) != std::string::npos)
{
bD3D11AdapterFound = true;
break;
}
}
if (bD3D11AdapterFound == false)
{
std::cout << "Direct3D 11 compatbile adapter named " << sDeviceName.c_str() << "was not found!" << std::endl;
return EXIT_FAILURE;
}
const D3D_FEATURE_LEVEL RequestedFeatureLevels = D3D_FEATURE_LEVEL_11_0;
D3D_FEATURE_LEVEL FeatureLevel;
UINT nFlags = 0;
#ifdef _DEBUG
nFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
CComPtr<ID3D11Device> pDevice;
CComPtr<ID3D11DeviceContext> pDeviceContext;
ATLENSURE_SUCCEEDED(D3D11CreateDevice(pDXGIAdapter1, D3D_DRIVER_TYPE_UNKNOWN, NULL, nFlags, &RequestedFeatureLevels, 1, D3D11_SDK_VERSION, &pDevice, &FeatureLevel, &pDeviceContext));
std::unique_ptr<unsigned char[]> pFrame(new unsigned char[nWidth * nHeight * 3 / 2]);
D3D11_TEXTURE2D_DESC TextureDescription = {};
TextureDescription.Width = nWidth;
TextureDescription.Height = nHeight;
TextureDescription.Format = DXGI_FORMAT_NV12;
TextureDescription.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
TextureDescription.Usage = D3D11_USAGE_DYNAMIC;
TextureDescription.MipLevels = 1;
TextureDescription.ArraySize = 1;
TextureDescription.SampleDesc.Count = 1;
TextureDescription.BindFlags = D3D11_BIND_DECODER;
CComPtr<ID3D11Texture2D> pTexture;
ATLENSURE_SUCCEEDED(pDevice->CreateTexture2D(&TextureDescription, NULL, &pTexture));
CComQIPtr<ID3D11Resource> pResource(pTexture);
D3D11_MAPPED_SUBRESOURCE MappedSubresource = {};
{
FILETIME StartFileTime = {};
::GetSystemTimeAsFileTime(&StartFileTime);
for (size_t nIteration = 0; nIteration < nIterations; ++nIteration)
{
ATLENSURE_SUCCEEDED(pDeviceContext->Map(pResource, 0, D3D11_MAP_WRITE_DISCARD, 0, &MappedSubresource));
_ASSERT(nWidth == MappedSubresource.RowPitch);
{
memcpy(MappedSubresource.pData, pFrame.get(), nWidth * nHeight * 3 / 2);
}
pDeviceContext->Unmap(pResource, 0);
}
FILETIME EndFileTime = {};
::GetSystemTimeAsFileTime(&EndFileTime);
ULARGE_INTEGER StartTime = { StartFileTime.dwLowDateTime, StartFileTime.dwHighDateTime }, EndTime = { EndFileTime.dwLowDateTime, EndFileTime.dwHighDateTime };
double fElapsedMiliseconds = static_cast<double>((EndTime.QuadPart - StartTime.QuadPart) / 10000.0f);
std::cout << "Map/memcpy/Unmap total time: " << fElapsedMiliseconds << " ms, " << fElapsedMiliseconds / nIterations << " per call" << std::endl;
}
{
FILETIME StartFileTime = {};
::GetSystemTimeAsFileTime(&StartFileTime);
for (size_t nIteration = 0; nIteration < nIterations; ++nIteration)
{
ATLENSURE_SUCCEEDED(pDeviceContext->Map(pResource, 0, D3D11_MAP_WRITE_DISCARD, 0, &MappedSubresource));
pDeviceContext->Unmap(pResource, 0);
}
FILETIME EndFileTime = {};
::GetSystemTimeAsFileTime(&EndFileTime);
ULARGE_INTEGER StartTime = { StartFileTime.dwLowDateTime, StartFileTime.dwHighDateTime }, EndTime = { EndFileTime.dwLowDateTime, EndFileTime.dwHighDateTime };
double fElapsedMiliseconds = static_cast<double>((EndTime.QuadPart - StartTime.QuadPart) / 10000.0f);
std::cout << "Map/Unmap total time: " << fElapsedMiliseconds << " ms, " << fElapsedMiliseconds / nIterations << " per call" << std::endl;
}
TextureDescription.Usage = D3D11_USAGE_DEFAULT;
TextureDescription.CPUAccessFlags = 0;
pTexture.Release();
ATLENSURE_SUCCEEDED(pDevice->CreateTexture2D(&TextureDescription, NULL, &pTexture));
pResource = pTexture;
{
FILETIME StartFileTime = {};
::GetSystemTimeAsFileTime(&StartFileTime);
for (size_t nIteration = 0; nIteration < nIterations; ++nIteration)
{
pDeviceContext->UpdateSubresource(pResource, 0, NULL, pFrame.get(), 1920, 0);
}
FILETIME EndFileTime = {};
::GetSystemTimeAsFileTime(&EndFileTime);
ULARGE_INTEGER StartTime = { StartFileTime.dwLowDateTime, StartFileTime.dwHighDateTime }, EndTime = { EndFileTime.dwLowDateTime, EndFileTime.dwHighDateTime };
double fElapsedMiliseconds = static_cast<double>((EndTime.QuadPart - StartTime.QuadPart) / 10000.0f);
std::cout << "UpdateSubresource total time: " << fElapsedMiliseconds << " ms, " << fElapsedMiliseconds / nIterations << " per call" << std::endl;
}
#pragma endregion
#pragma region Cuda
int nCudaDeviceCount = 0;
auto nCudaError = cudaGetDeviceCount(&nCudaDeviceCount);
_ASSERT(nCudaError == CUDA_SUCCESS);
std::vector<cudaDeviceProp> Devices;
Devices.resize(nCudaDeviceCount);
bool bCudaDeviceFound = false;
int nCudaDevice = 0;
for (; nCudaDevice < nCudaDeviceCount; ++nCudaDevice)
{
nCudaError = cudaGetDeviceProperties(&Devices[nCudaDevice], nCudaDevice);
_ASSERT(nCudaError == CUDA_SUCCESS);
if (Devices[nCudaDevice].name == sDeviceName)
{
bCudaDeviceFound = true;
break;
}
}
if (bCudaDeviceFound == false)
{
std::cout << "Cuda compatbile adapter named " << sDeviceName.c_str() << "was not found!" << std::endl;
return EXIT_FAILURE;
}
nCudaError = cudaSetDevice(nCudaDevice);
_ASSERT(nCudaError == CUDA_SUCCESS);
void *pHostMemory = NULL, *pDeviceMemory = NULL;
nCudaError = cudaMalloc(&pDeviceMemory, nWidth * nHeight * 3 / 2);
_ASSERT(nCudaError == CUDA_SUCCESS);
nCudaError = cudaMallocHost(&pHostMemory, nWidth * nHeight * 3 / 2);
_ASSERT(nCudaError == CUDA_SUCCESS);
{
FILETIME StartFileTime = {};
::GetSystemTimeAsFileTime(&StartFileTime);
for (size_t nIteration = 0; nIteration < nIterations; ++nIteration)
{
nCudaError = cudaMemcpy(pDeviceMemory, pFrame.get(), nWidth * nHeight * 3 / 2, cudaMemcpyHostToDevice);
_ASSERT(nCudaError == CUDA_SUCCESS);
}
FILETIME EndFileTime = {};
::GetSystemTimeAsFileTime(&EndFileTime);
ULARGE_INTEGER StartTime = { StartFileTime.dwLowDateTime, StartFileTime.dwHighDateTime }, EndTime = { EndFileTime.dwLowDateTime, EndFileTime.dwHighDateTime };
double fElapsedMiliseconds = static_cast<double>((EndTime.QuadPart - StartTime.QuadPart) / 10000.0f);
std::cout << "cudaMemcpy total time: " << fElapsedMiliseconds << " ms, " << fElapsedMiliseconds / nIterations << " per call" << std::endl;
}
{
FILETIME StartFileTime = {};
::GetSystemTimeAsFileTime(&StartFileTime);
for (size_t nIteration = 0; nIteration < nIterations; ++nIteration)
{
nCudaError = cudaMemcpyAsync(pDeviceMemory, pFrame.get(), nWidth * nHeight * 3 / 2, cudaMemcpyHostToDevice);
_ASSERT(nCudaError == CUDA_SUCCESS);
}
cudaDeviceSynchronize();
FILETIME EndFileTime = {};
::GetSystemTimeAsFileTime(&EndFileTime);
ULARGE_INTEGER StartTime = { StartFileTime.dwLowDateTime, StartFileTime.dwHighDateTime }, EndTime = { EndFileTime.dwLowDateTime, EndFileTime.dwHighDateTime };
double fElapsedMiliseconds = static_cast<double>((EndTime.QuadPart - StartTime.QuadPart) / 10000.0f);
std::cout << "cudaMemcpyAsync total time: " << fElapsedMiliseconds << " ms, " << fElapsedMiliseconds / nIterations << " per call" << std::endl;
}
{
FILETIME StartFileTime = {};
::GetSystemTimeAsFileTime(&StartFileTime);
for (size_t nIteration = 0; nIteration < nIterations; ++nIteration)
{
nCudaError = cudaMemcpyAsync(pDeviceMemory, pHostMemory, nWidth * nHeight * 3 / 2, cudaMemcpyHostToDevice);
_ASSERT(nCudaError == CUDA_SUCCESS);
}
cudaDeviceSynchronize();
FILETIME EndFileTime = {};
::GetSystemTimeAsFileTime(&EndFileTime);
ULARGE_INTEGER StartTime = { StartFileTime.dwLowDateTime, StartFileTime.dwHighDateTime }, EndTime = { EndFileTime.dwLowDateTime, EndFileTime.dwHighDateTime };
double fElapsedMiliseconds = static_cast<double>((EndTime.QuadPart - StartTime.QuadPart) / 10000.0f);
std::cout << "cudaMemcpyAsync with cudaMalloc'ed input memory total time: " << fElapsedMiliseconds << " ms, " << fElapsedMiliseconds / nIterations << " per call" << std::endl;
}
cudaFree(pDeviceMemory);
cudaFree(pHostMemory);
#pragma endregion
return EXIT_SUCCESS;
}
approch胜过默认曲面和{{1}}。非常少见。
我将在下面发布基准代码(也可在GitHub上找到) - 我会对任何反馈都非常满意,因为可能会出现影响结果的基准问题因为我对Direct3D 11和Cuda都很陌生。
{{1}}