Skip to content

Commit 1e3ba87

Browse files
authored
Add ability to cache built binaries in OpenCL engine (#11)
1 parent 2af8047 commit 1e3ba87

File tree

1 file changed

+132
-25
lines changed

1 file changed

+132
-25
lines changed

cpp/visualmesh/engine/opencl/engine.hpp

Lines changed: 132 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
// If OpenCL is disabled then don't provide this file
2222
#if !defined(VISUALMESH_DISABLE_OPENCL)
2323

24+
#include <fstream>
2425
#include <iomanip>
2526
#include <numeric>
2627
#include <sstream>
@@ -63,56 +64,164 @@ namespace engine {
6364
// OpenCL ::clSetKernelArg functions take the sizeof a pointer as their argument, this is correct
6465
static constexpr size_t MEM_SIZE = sizeof(cl_mem);
6566

66-
public:
6767
/**
68-
* @brief Construct a new OpenCL Engine object
68+
* @brief Load an OpenCL binary from a file and build it
69+
*
70+
* @param binary_path path to save the binary file to
71+
* @param device OpenCL device id
6972
*
70-
* @param structure the network structure to use classification
7173
*/
72-
Engine(const NetworkStructure<Scalar>& structure = {}) {
74+
void load_binary(const std::string& binary_path, cl_device_id& device) {
75+
// If the file doesn't exist, this isn't an error so don't throw just return that it didn't work
76+
std::ifstream read_binary(binary_path, std::ios::in);
77+
if (!read_binary) { throw std::runtime_error("Failed to read from precompiled OpenCL binary."); }
78+
79+
// Error flag to check if any OpenCL functions fail
80+
cl_int error = CL_SUCCESS;
81+
82+
// Get the length
83+
read_binary.seekg(0, read_binary.end);
84+
size_t binary_size = read_binary.tellg();
85+
read_binary.seekg(0, read_binary.beg);
86+
87+
// Read the binary file
88+
std::vector<char> binary_load(binary_size, 0);
89+
read_binary.read(binary_load.data(), binary_size);
90+
read_binary.close();
91+
if (!read_binary) { throw std::runtime_error("Failed to read from precompiled OpenCL binary."); }
92+
93+
// Create the program and build using the loaded binary
94+
cl_int binary_status = CL_SUCCESS;
95+
const unsigned char* binary_ptr = reinterpret_cast<unsigned char*>(binary_load.data());
96+
97+
program = cl::program(
98+
::clCreateProgramWithBinary(context, 1, &device, &binary_size, &binary_ptr, &binary_status, &error),
99+
::clReleaseProgram);
100+
throw_cl_error(error, "Failed to create program from binary");
73101

74-
// Create the OpenCL context and command queue
75-
cl_int error = CL_SUCCESS;
76-
cl_device_id device = nullptr;
77-
std::tie(context, device) = operation::make_context();
78-
queue = operation::make_queue(context, device);
102+
error = ::clBuildProgram(program,
103+
1,
104+
&device,
105+
"-cl-single-precision-constant -cl-fast-relaxed-math -cl-mad-enable",
106+
nullptr,
107+
nullptr);
79108

80-
// Get program sources (this does concatenated strings)
81-
std::stringstream sources;
82-
sources << operation::get_scalar_defines(Scalar(0.0));
83-
sources << PROJECT_EQUIDISTANT_CL;
84-
sources << PROJECT_EQUISOLID_CL;
85-
sources << PROJECT_RECTILINEAR_CL;
86-
sources << LOAD_IMAGE_CL;
87-
sources << operation::make_network(structure);
109+
// If it didn't work, log and throw an error
110+
if (error != CL_SUCCESS) {
111+
// Get program build log
112+
size_t used = 0;
113+
::clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, nullptr, &used);
114+
std::vector<char> log(used);
115+
::clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log.size(), log.data(), &used);
116+
// Throw an error with the build log
117+
throw_cl_error(error,
118+
"Error building OpenCL program\n" + std::string(log.begin(), log.begin() + used));
119+
}
120+
}
88121

89-
std::string source = sources.str();
90-
const char* cstr = source.c_str();
91-
size_t csize = source.size();
122+
/**
123+
* @brief Build the OpenCL program
124+
*
125+
* @param device OpenCL device id
126+
* @param source OpenCL source information
127+
*/
128+
void build_from_source(cl_device_id& device, const std::string& source) {
129+
// Error flag to check if any OpenCL functions fail
130+
cl_int error = CL_SUCCESS;
92131

132+
// Create the program and build
133+
const char* cstr = source.c_str();
134+
size_t csize = source.size();
93135
program =
94136
cl::program(::clCreateProgramWithSource(context, 1, &cstr, &csize, &error), ::clReleaseProgram);
95137
throw_cl_error(error, "Error adding sources to OpenCL program");
96138

97-
// Compile the program
98139
error = ::clBuildProgram(program,
99140
0,
100141
nullptr,
101142
"-cl-single-precision-constant -cl-fast-relaxed-math -cl-mad-enable",
102143
nullptr,
103144
nullptr);
145+
146+
// If it didn't work, log and throw an error
104147
if (error != CL_SUCCESS) {
105148
// Get program build log
106149
size_t used = 0;
107150
::clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, nullptr, &used);
108151
std::vector<char> log(used);
109152
::clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log.size(), log.data(), &used);
110-
111153
// Throw an error with the build log
112154
throw_cl_error(error,
113155
"Error building OpenCL program\n" + std::string(log.begin(), log.begin() + used));
114156
}
157+
}
158+
159+
/**
160+
* @brief Save the current OpenCL program in a binary file
161+
*
162+
* @param binary_path path to save the binary file to
163+
*/
164+
void save_binary(std::string binary_path) {
165+
166+
// Get the size of the binary to save
167+
size_t binary_size{};
168+
clGetProgramInfo(program, CL_PROGRAM_BINARY_SIZES, sizeof(size_t), &binary_size, nullptr);
169+
170+
// Get the data to save
171+
std::vector<char> binary_save(binary_size, 0);
172+
// Get an lvalue ptr to pass to clGetProgramInfo
173+
char* binary_ptr = binary_save.data();
174+
clGetProgramInfo(program, CL_PROGRAM_BINARIES, binary_save.size(), &binary_ptr, nullptr);
175+
176+
// Write to the file and close the file
177+
std::ofstream write_binary(binary_path, std::ofstream::binary);
178+
write_binary.write(binary_save.data(), binary_save.size());
179+
write_binary.close();
180+
}
181+
182+
public:
183+
/**
184+
* @brief Construct a new OpenCL Engine object
185+
*
186+
* @param structure the network structure to use classification
187+
* @param cache_directory directory to save/load the compiled OpenCL binary
188+
*/
189+
Engine(const NetworkStructure<Scalar>& structure = {}, const std::string& cache_directory = "") {
190+
// Create the OpenCL context and command queue
191+
cl_int error = CL_SUCCESS;
192+
cl_device_id device = nullptr;
193+
std::tie(context, device) = operation::make_context();
194+
queue = operation::make_queue(context, device);
195+
196+
// Get program sources (this does concatenated strings)
197+
std::stringstream sources;
198+
sources << operation::get_scalar_defines(Scalar(0.0));
199+
sources << PROJECT_EQUIDISTANT_CL;
200+
sources << PROJECT_EQUISOLID_CL;
201+
sources << PROJECT_RECTILINEAR_CL;
202+
sources << LOAD_IMAGE_CL;
203+
sources << operation::make_network(structure);
204+
205+
std::string source = sources.str();
206+
207+
// The hash of the sources represents the name of the OpenCL compiled program binary file, so that a new
208+
// binary will be created for different sources
209+
const std::size_t source_hash = std::hash<std::string>{}(source);
210+
211+
// If the compiled binary exists, read it
212+
std::string binary_path = cache_directory + "/" + std::to_string(source_hash) + ".bin";
213+
214+
// Try to read the binary
215+
try {
216+
load_binary(binary_path, device);
217+
}
218+
// The compiled binary doesn't exist, create it
219+
catch (std::exception& /* e */) {
220+
build_from_source(device, source);
221+
save_binary(binary_path);
222+
}
115223

224+
// Get the kernels
116225
project_rectilinear =
117226
cl::kernel(::clCreateKernel(program, "project_rectilinear", &error), ::clReleaseKernel);
118227
throw_cl_error(error, "Error getting project_rectilinear kernel");
@@ -513,9 +622,7 @@ namespace engine {
513622
// Cache for future runs
514623
device_points_cache[&mesh] = cl_points;
515624
}
516-
else {
517-
cl_points = device_mesh->second;
518-
}
625+
else { cl_points = device_mesh->second; }
519626

520627
// First count the size of the buffer we will need to allocate
521628
int n_points = 0;

0 commit comments

Comments
 (0)