diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e72ceff6..5bbdc2294 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,11 @@ if (VEXCL_SHOW_KERNELS) add_definitions(-DVEXCL_SHOW_KERNELS) endif (VEXCL_SHOW_KERNELS) +option(VEXCL_CACHE_KERNELS "Cache compiled kernels offline") +if (VEXCL_CACHE_KERNELS) + add_definitions(-DVEXCL_CACHE_KERNELS) +endif (VEXCL_CACHE_KERNELS) + #---------------------------------------------------------------------------- # Find Boost #---------------------------------------------------------------------------- diff --git a/vexcl/util.hpp b/vexcl/util.hpp index b55dc6525..b72148cff 100644 --- a/vexcl/util.hpp +++ b/vexcl/util.hpp @@ -45,6 +45,16 @@ THE SOFTWARE. #include +#ifdef VEXCL_CACHE_KERNELS +# include +# include +# include +# include +# include +# include +# include +#endif + #ifdef BOOST_NO_VARIADIC_TEMPLATES # include # include @@ -243,7 +253,106 @@ inline std::string standard_kernel_header(const cl::Device &dev) { ) + get_program_header(dev); } +#ifdef VEXCL_CACHE_KERNELS +/// Path delimiter symbol. +inline const std::string& path_delim() { + static const std::string delim = boost::filesystem::path("/").make_preferred().native(); + return delim; +} + +/// Path to appdata folder. +inline const std::string& appdata_path() { +#ifdef WIN32 +# ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4996) +# endif + static const std::string appdata = getenv("APPDATA") + path_delim() + "vexcl"; +# ifdef _MSC_VER +# pragma warning(pop) +# endif +#else + static const std::string appdata = getenv("HOME") + path_delim() + ".vexcl"; +#endif + return appdata; +} + +/// Path to cached binaries. +inline std::string program_binaries_path(const std::string &hash, bool create = false) +{ + std::string dir = appdata_path() + path_delim() + hash.substr(0, 2); + if (create) boost::filesystem::create_directories(dir); + return dir + path_delim() + hash.substr(2); +} + +/// Saves program binaries for future reuse. +inline void save_program_binaries(const std::string &hash, const cl::Program &program) +{ + std::ofstream bfile(program_binaries_path(hash, true), std::ios::binary); + if (!bfile) return; + + std::vector sizes = program.getInfo(); + std::vector binaries = program.getInfo(); + + assert(sizes.size() == 1); + + bfile.write((char*)&sizes[0], sizeof(size_t)); + bfile.write(binaries[0], sizes[0]); + delete[] binaries[0]; +} + +/// Tries to read program binaries from file cache. +inline boost::optional load_program_binaries( + const std::string &hash, const cl::Context &context, + const std::vector &device + ) +{ + std::ifstream bfile(program_binaries_path(hash), std::ios::binary); + if (!bfile) return boost::optional(); + + size_t n; + std::vector buf; + + bfile.read((char*)&n, sizeof(size_t)); + buf.resize(n); + bfile.read(buf.data(), n); + + cl::Program program(context, device, cl::Program::Binaries( + 1, std::make_pair(static_cast(buf.data()), n))); + + try { + program.build(device, ""); + } catch(const cl::Error&) { + std::cerr << "Loading binaries failed:" << std::endl + << program.getBuildInfo(device[0]) + << std::endl; + return boost::optional(); + } + + return boost::optional(program); +} + +/// Returns SHA1 hash of the string parameter. +inline std::string sha1(const std::string &src) { + boost::uuids::detail::sha1 sha1; + sha1.process_bytes(src.c_str(), src.size()); + + unsigned int hash[5]; + sha1.get_digest(hash); + + std::ostringstream buf; + for(int i = 0; i < 5; ++i) + buf << std::hex << std::setfill('0') << std::setw(8) << hash[i]; + + return buf.str(); +} +#endif + /// Create and build a program from source string. +/** + * If VEXCL_CACHE_KERNELS macro is defined, then program binaries are cached + * in filesystem and reused in the following runs. + */ inline cl::Program build_sources( const cl::Context &context, const std::string &source, const std::string &options = "" @@ -253,12 +362,31 @@ inline cl::Program build_sources( std::cout << source << std::endl; #endif + auto device = context.getInfo(); + std::string compile_options = options + " " + get_compile_options(device[0]); + +#ifdef VEXCL_CACHE_KERNELS + // Get unique (hopefully) hash string for the kernel. + std::ostringstream hashsrc; + + hashsrc + << source + << compile_options + << device[0].getInfo() + << cl::Platform(device[0].getInfo()).getInfo(); + + std::string hash = sha1( hashsrc.str() ); + + // Try to get cached program binaries: + if (boost::optional program = load_program_binaries(hash, context, device)) + return *program; +#endif + + // If cache is not available, just compile the sources. cl::Program program(context, cl::Program::Sources( 1, std::make_pair(source.c_str(), source.size()) )); - auto device = context.getInfo(); - try { program.build(device, (options + " " + get_compile_options(device[0])).c_str()); } catch(const cl::Error&) { @@ -269,6 +397,11 @@ inline cl::Program build_sources( throw; } +#ifdef VEXCL_CACHE_KERNELS + // Save program binaries for future reuse: + save_program_binaries(hash, program); +#endif + return program; }