/* * Copyright (c) Advanced Micro Devices, Inc., or its affiliates. * * SPDX-License-Identifier: MIT */ #include "OCLPerfDoubleDMA.h" #include #include #include #include #include #include #include "CL/cl.h" #include "CL/cl_ext.h" const size_t blockX = 256; const size_t blockY = 256; const size_t blockZ = 512; const size_t chunk = 16; const size_t size_S = blockX * blockY * blockZ * sizeof(cl_float4); const size_t size_s = blockX * blockY * chunk * sizeof(cl_float4); static const int WindowWidth = 80; const size_t MaxQueues = 3; bool profEnable = false; static const char* strKernel = "__kernel void dummy(__global float4* out) \n" "{ \n" " uint id = get_global_id(0); \n" " float4 value = (float4)(1.0f, 2.0f, 3.0f, 4.0f); \n" " uint factorial = 1; \n" " for (uint i = 1; i < (id / 0x400); ++i)\n" " { \n" " factorial *= i; \n" " } \n" " out[id] = value * factorial; \n" "} \n"; class ProfileQueue { public: enum Operation { Write = 0, Execute, Read, Total }; static const char* OperationName[Total]; static const char StartCommand[Total]; static const char ExecCommand[Total]; ProfileQueue() {} ~ProfileQueue() { for (size_t op = 0; op < Total; ++op) { for (size_t idx = 0; idx < events_[op].size(); ++idx) { clReleaseEvent(events_[op][idx]); } } } void addEvent(Operation op, cl_event event) { events_[op].push_back(event); } void findMinMax(cl_long* min, cl_long* max) { // Find time min/max ranges for the frame scaling for (size_t op = 0; (op < ProfileQueue::Total); ++op) { cl_long time; if (events_[op].size() == 0) continue; clGetEventProfilingInfo(events_[op][0], CL_PROFILING_COMMAND_START, sizeof(cl_long), &time, NULL); if (0 == *min) { *min = time; } else { *min = std::min(*min, time); } clGetEventProfilingInfo(events_[op][events_[op].size() - 1], CL_PROFILING_COMMAND_END, sizeof(cl_long), &time, NULL); if (0 == *max) { *max = time; } else { *max = std::max(*max, time); } } } void display(cl_long start, cl_long finish) { std::string graph; graph.resize(WindowWidth + 1); graph[WindowWidth] = '\x0'; cl_long timeFrame = finish - start; cl_long interval = timeFrame / WindowWidth; // Find time min/max ranges for the frame scaling for (size_t op = 0; (op < Total); ++op) { if (events_[op].size() == 0) continue; cl_long timeStart, timeEnd; int begin = 0, end = 0; for (size_t idx = 0; idx < events_[op].size(); ++idx) { bool cutStart = false; clGetEventProfilingInfo(events_[op][idx], CL_PROFILING_COMMAND_START, sizeof(cl_long), &timeStart, NULL); clGetEventProfilingInfo(events_[op][idx], CL_PROFILING_COMMAND_END, sizeof(cl_long), &timeEnd, NULL); // Continue if out of the frame scope if (timeStart >= finish) continue; if (timeEnd <= start) continue; if (timeStart <= start) { timeStart = start; cutStart = true; } if (timeEnd >= finish) { timeEnd = finish; } // Readjust time to the frame timeStart -= start; timeEnd -= start; timeStart = static_cast(floor(static_cast(timeStart) / interval + 0.5f)); timeEnd = static_cast(floor(static_cast(timeEnd) / interval + 0.5f)); begin = static_cast(timeStart); // Idle from end to begin for (int c = end; c < begin; ++c) { graph[c] = '-'; } end = static_cast(timeEnd); for (int c = begin; c < end; ++c) { if ((c == begin) && !cutStart) { graph[c] = StartCommand[op]; } else { graph[c] = ExecCommand[op]; } } if ((begin == end) && (end < WindowWidth)) { graph[begin] = '+'; } } if (end < WindowWidth) { for (int c = end; c < WindowWidth; ++c) { graph[c] = '-'; } } printf("%s\n", graph.c_str()); } } private: // Profiling events std::vector events_[Total]; }; const char* ProfileQueue::OperationName[Total] = {"BufferWrite", "KernelExecution", "BufferRead"}; const char ProfileQueue::StartCommand[Total] = {'W', 'X', 'R'}; const char ProfileQueue::ExecCommand[Total] = {'>', '#', '<'}; class Profile { public: Profile(bool profEna, int numQueues) : profileEna_(profEna), numQueues_(numQueues), min_(0), max_(0), execTime_(0) {} ~Profile() {} void addEvent(int queue, ProfileQueue::Operation op, cl_event event) { if (profileEna_) { profQueue[queue].addEvent(op, event); } } cl_long findExecTime() { if (execTime_ != 0) return execTime_; for (int q = 0; q < numQueues_; ++q) { profQueue[q].findMinMax(&min_, &max_); } execTime_ = max_ - min_; return execTime_; } void display(cl_long start, cl_long finish) { if (!profileEna_) return; printf("\n ----------- Time frame %.3f (us), scale 1:%.0f\n", (float)(finish - start) / 1000, (float)(finish - start) / (1000 * WindowWidth)); for (size_t op = 0; (op < ProfileQueue::Total); ++op) { printf("%s - %c%c; ", ProfileQueue::OperationName[op], ProfileQueue::StartCommand[op], ProfileQueue::ExecCommand[op]); } printf("\n"); for (int q = 0; q < numQueues_; ++q) { printf("CommandQueue #%d\n", q); profQueue[q].display(min_ + start, min_ + finish); } } private: bool profileEna_; int numQueues_; //!< Total number of queues cl_long min_; //!< Min HW timestamp cl_long max_; //!< Max HW timestamp cl_long execTime_; //!< Profile time ProfileQueue profQueue[MaxQueues]; }; OCLPerfDoubleDMA::OCLPerfDoubleDMA() { _numSubTests = 2 * MaxQueues * 2; failed_ = false; } OCLPerfDoubleDMA::~OCLPerfDoubleDMA() {} void OCLPerfDoubleDMA::open(unsigned int test, char* units, double& conversion, unsigned int deviceId) { _deviceId = deviceId; OCLTestImp::open(test, units, conversion, deviceId); CHECK_RESULT((error_ != CL_SUCCESS), "Error opening test"); test_ = test; cl_device_type deviceType; error_ = _wrapper->clGetDeviceInfo(devices_[deviceId], CL_DEVICE_TYPE, sizeof(deviceType), &deviceType, NULL); CHECK_RESULT((error_ != CL_SUCCESS), "CL_DEVICE_TYPE failed"); if (!(deviceType & CL_DEVICE_TYPE_GPU)) { printf("GPU device is required for this test!\n"); failed_ = true; return; } program_ = _wrapper->clCreateProgramWithSource(context_, 1, &strKernel, NULL, &error_); CHECK_RESULT((error_ != CL_SUCCESS), "clCreateProgramWithSource() failed"); error_ = _wrapper->clBuildProgram(program_, 1, &devices_[deviceId], NULL, NULL, NULL); if (error_ != CL_SUCCESS) { char programLog[1024]; _wrapper->clGetProgramBuildInfo(program_, devices_[deviceId], CL_PROGRAM_BUILD_LOG, 1024, programLog, 0); printf("\n%s\n", programLog); fflush(stdout); } CHECK_RESULT((error_ != CL_SUCCESS), "clBuildProgram() failed"); kernel_ = _wrapper->clCreateKernel(program_, "dummy", &error_); CHECK_RESULT((error_ != CL_SUCCESS), "clCreateKernel() failed"); size_t bufSize = size_s; cl_mem buffer; if (test_ >= (2 * MaxQueues)) { profEnable = true; } test_ %= 2 * MaxQueues; size_t numBufs = (test_ % MaxQueues) + 1; for (size_t b = 0; b < numBufs; ++b) { buffer = _wrapper->clCreateBuffer(context_, CL_MEM_READ_WRITE, bufSize, NULL, &error_); CHECK_RESULT((error_ != CL_SUCCESS), "clCreateBuffer() failed"); buffers_.push_back(buffer); } buffer = _wrapper->clCreateBuffer(context_, CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR, size_S, NULL, &error_); CHECK_RESULT((error_ != CL_SUCCESS), "clCreateBuffer() failed"); buffers_.push_back(buffer); } static void CL_CALLBACK notify_callback(const char* errinfo, const void* private_info, size_t cb, void* user_data) {} void OCLPerfDoubleDMA::run(void) { if (failed_) { return; } CPerfCounter timer; const int numQueues = (test_ % MaxQueues) + 1; const bool useKernel = ((test_ / MaxQueues) > 0); const int numBufs = numQueues; Profile profile(profEnable, numQueues); std::vector cmdQueues(numQueues); int q; cl_command_queue_properties qProp = (profEnable) ? CL_QUEUE_PROFILING_ENABLE : 0; for (q = 0; q < numQueues; ++q) { cl_command_queue cmdQueue = _wrapper->clCreateCommandQueue(context_, devices_[_deviceId], qProp, &error_); CHECK_RESULT((error_ != CL_SUCCESS), "clCreateCommandQueue() failed"); cmdQueues[q] = cmdQueue; } float* Data_s = (float*)_wrapper->clEnqueueMapBuffer(cmdQueues[0], buffers_[numBufs], CL_TRUE, CL_MAP_READ | CL_MAP_WRITE, 0, size_S, 0, NULL, NULL, &error_); size_t gws[1] = {size_s / (4 * sizeof(float))}; size_t lws[1] = {256}; // Warm-up for (q = 0; q < numQueues; ++q) { error_ |= _wrapper->clEnqueueWriteBuffer(cmdQueues[q], buffers_[q], CL_FALSE, 0, size_s, (char*)Data_s, 0, NULL, NULL); error_ |= _wrapper->clSetKernelArg(kernel_, 0, sizeof(cl_mem), (void*)&buffers_[q]); error_ |= _wrapper->clEnqueueNDRangeKernel(cmdQueues[q], kernel_, 1, NULL, gws, lws, 0, NULL, NULL); error_ |= _wrapper->clEnqueueReadBuffer(cmdQueues[q], buffers_[q], CL_FALSE, 0, size_s, (char*)Data_s, 0, NULL, NULL); error_ |= _wrapper->clFinish(cmdQueues[q]); } CHECK_RESULT_NO_RETURN((error_ != CL_SUCCESS), "Execution failed"); size_t s_done = 0; cl_event r[MaxQueues] = {0}, w[MaxQueues] = {0}, x[MaxQueues] = {0}; /*---------- pass2: copy Data_s to and from GPU Buffers ----------*/ s_done = 0; timer.Reset(); timer.Start(); int idx = numBufs - 1; // Start from the last so read/write won't go to the same DMA when kernel is // executed q = numQueues - 1; size_t iter = 0; while (1) { if (0 == r[idx]) { error_ |= _wrapper->clEnqueueWriteBuffer(cmdQueues[q], buffers_[idx], CL_FALSE, 0, size_s, (char*)Data_s + s_done, 0, NULL, &w[idx]); } else { error_ |= _wrapper->clEnqueueWriteBuffer(cmdQueues[q], buffers_[idx], CL_FALSE, 0, size_s, (char*)Data_s + s_done, 1, &r[idx], &w[idx]); if (!profEnable) { error_ |= _wrapper->clReleaseEvent(r[idx]); } } _wrapper->clFlush(cmdQueues[q]); profile.addEvent(q, ProfileQueue::Write, w[idx]); if (useKernel) { // Change the queue ++q %= numQueues; // Implicit flush of DMA engine on kernel start, because memory dependency error_ |= _wrapper->clSetKernelArg(kernel_, 0, sizeof(cl_mem), (void*)&buffers_[idx]); error_ |= _wrapper->clEnqueueNDRangeKernel(cmdQueues[q], kernel_, 1, NULL, gws, lws, 1, &w[idx], &x[idx]); if (!profEnable) { error_ |= _wrapper->clReleaseEvent(w[idx]); } profile.addEvent(q, ProfileQueue::Execute, x[idx]); } _wrapper->clFlush(cmdQueues[q]); // Change the queue ++q %= numQueues; error_ |= _wrapper->clEnqueueReadBuffer(cmdQueues[q], buffers_[idx], CL_FALSE, 0, size_s, (char*)Data_s + s_done, 1, (useKernel) ? &x[idx] : &w[idx], &r[idx]); if (!profEnable) { error_ |= _wrapper->clReleaseEvent((useKernel) ? x[idx] : w[idx]); } profile.addEvent(q, ProfileQueue::Read, r[idx]); _wrapper->clFlush(cmdQueues[q]); if ((s_done += size_s) >= size_S) { if (!profEnable) { error_ |= _wrapper->clReleaseEvent(r[idx]); } break; } ++iter; ++idx %= numBufs; ++q %= numQueues; } for (q = 0; q < numQueues; ++q) { error_ |= _wrapper->clFinish(cmdQueues[q]); } timer.Stop(); error_ = _wrapper->clEnqueueUnmapMemObject(cmdQueues[0], buffers_[numBufs], Data_s, 0, NULL, NULL); error_ |= _wrapper->clFinish(cmdQueues[0]); CHECK_RESULT_NO_RETURN((error_ != CL_SUCCESS), "Execution failed"); cl_long gpuTimeFrame = profile.findExecTime(); cl_long oneIter = gpuTimeFrame / iter; // Display 4 iterations in the middle cl_long startFrame = oneIter * (iter / 2 - 2); cl_long finishFrame = oneIter * (iter / 2 + 2); profile.display(startFrame, finishFrame); for (q = 0; q < numQueues; ++q) { error_ = _wrapper->clReleaseCommandQueue(cmdQueues[q]); CHECK_RESULT_NO_RETURN((error_ != CL_SUCCESS), "clReleaseCommandQueue() failed"); } double GBytes = (double)(2 * size_S) / (double)(1000 * 1000 * 1000); _perfInfo = static_cast(GBytes / timer.GetElapsedTime()); std::stringstream stream; if (useKernel) { stream << "Write/Kernel/Read operation "; } else { stream << "Write/Read operation "; } stream << numQueues << " queues; profiling " << ((profEnable) ? "enabled" : "disabled") << " [GB/s]"; stream.flags(std::ios::right | std::ios::showbase); testDescString = stream.str(); } unsigned int OCLPerfDoubleDMA::close(void) { return OCLTestImp::close(); }