私は完全にopenclの初心者です。インターネットを検索したところ、openclプロジェクトの「helloworld」デモが見つかりました。通常、このような最小限のプロジェクトでは、*。clファイルに何らかのopenclカーネルが含まれており、*。cファイルに主な機能が含まれています。次に、この種のプロジェクトをコマンドラインを使用してコンパイルするにはどうすればよいですか。 Linuxでは-lOpenCLフラグを使用し、Macでは-framework OpenCLを使用する必要があることはわかっています。しかし、*。clカーネルをメインのソースファイルにリンクすることは考えていません。コメントや役立つリンクをありがとう。
OpenCLでは、デバイスカーネルコードを含む.cl
ファイルは通常、実行時にコンパイルおよびビルドされます。これは、ホストOpenCLプログラムのどこかで、デバイスプログラムを使用できるようにコンパイルおよびビルドする必要があることを意味します。この機能により、最大の移植性が実現します。
2冊の本から集めた例を考えてみましょう。以下は、2つのグローバル配列から2つの数値を追加し、それらを別のグローバル配列に保存する非常に単純なOpenCLカーネルです。このコードをvector_add_kernel.cl
という名前のファイルに保存します。
kernel void vecadd( global int* A, global int* B, global int* C ) {
const int idx = get_global_id(0);
C[idx] = A[idx] + B[idx];
}
以下は、OpenCL C++ APIを利用するC++で記述されたホストコードです。 ocl_vector_addition.cpp
ファイルを保存した場所の横にある.cl
という名前のファイルに保存します。
#include <iostream>
#include <fstream>
#include <string>
#include <memory>
#include <stdlib.h>
#define __CL_ENABLE_EXCEPTIONS
#if defined(__Apple__) || defined(__MACOSX)
#include <OpenCL/cl.cpp>
#else
#include <CL/cl.hpp>
#endif
int main( int argc, char** argv ) {
const int N_ELEMENTS=1024*1024;
unsigned int platform_id=0, device_id=0;
try{
std::unique_ptr<int[]> A(new int[N_ELEMENTS]); // Or you can use simple dynamic arrays like: int* A = new int[N_ELEMENTS];
std::unique_ptr<int[]> B(new int[N_ELEMENTS]);
std::unique_ptr<int[]> C(new int[N_ELEMENTS]);
for( int i = 0; i < N_ELEMENTS; ++i ) {
A[i] = i;
B[i] = i;
}
// Query for platforms
std::vector<cl::Platform> platforms;
cl::Platform::get(&platforms);
// Get a list of devices on this platform
std::vector<cl::Device> devices;
platforms[platform_id].getDevices(CL_DEVICE_TYPE_GPU|CL_DEVICE_TYPE_CPU, &devices); // Select the platform.
// Create a context
cl::Context context(devices);
// Create a command queue
cl::CommandQueue queue = cl::CommandQueue( context, devices[device_id] ); // Select the device.
// Create the memory buffers
cl::Buffer bufferA=cl::Buffer(context, CL_MEM_READ_ONLY, N_ELEMENTS * sizeof(int));
cl::Buffer bufferB=cl::Buffer(context, CL_MEM_READ_ONLY, N_ELEMENTS * sizeof(int));
cl::Buffer bufferC=cl::Buffer(context, CL_MEM_WRITE_ONLY, N_ELEMENTS * sizeof(int));
// Copy the input data to the input buffers using the command queue.
queue.enqueueWriteBuffer( bufferA, CL_FALSE, 0, N_ELEMENTS * sizeof(int), A.get() );
queue.enqueueWriteBuffer( bufferB, CL_FALSE, 0, N_ELEMENTS * sizeof(int), B.get() );
// Read the program source
std::ifstream sourceFile("vector_add_kernel.cl");
std::string sourceCode( std::istreambuf_iterator<char>(sourceFile), (std::istreambuf_iterator<char>()));
cl::Program::Sources source(1, std::make_pair(sourceCode.c_str(), sourceCode.length()));
// Make program from the source code
cl::Program program=cl::Program(context, source);
// Build the program for the devices
program.build(devices);
// Make kernel
cl::Kernel vecadd_kernel(program, "vecadd");
// Set the kernel arguments
vecadd_kernel.setArg( 0, bufferA );
vecadd_kernel.setArg( 1, bufferB );
vecadd_kernel.setArg( 2, bufferC );
// Execute the kernel
cl::NDRange global( N_ELEMENTS );
cl::NDRange local( 256 );
queue.enqueueNDRangeKernel( vecadd_kernel, cl::NullRange, global, local );
// Copy the output data back to the Host
queue.enqueueReadBuffer( bufferC, CL_TRUE, 0, N_ELEMENTS * sizeof(int), C.get() );
// Verify the result
bool result=true;
for (int i=0; i<N_ELEMENTS; i ++)
if (C[i] !=A[i]+B[i]) {
result=false;
break;
}
if (result)
std::cout<< "Success!\n";
else
std::cout<< "Failed!\n";
}
catch(cl::Error err) {
std::cout << "Error: " << err.what() << "(" << err.err() << ")" << std::endl;
return( EXIT_FAILURE );
}
std::cout << "Done.\n";
return( EXIT_SUCCESS );
}
このコードは、Ubuntu 12.04を搭載したマシンで次のようにコンパイルします。
g++ ocl_vector_addition.cpp -lOpenCL -std=c++11 -o ocl_vector_addition.o
ocl_vector_addition.o
が生成され、実行すると正常な出力が表示されます。コンパイルコマンドを見ると、.cl
ファイルについて何も渡していないことがわかります。 -lOpenCL
フラグのみを使用して、プログラムでOpenCLライブラリを有効にしました。また、-std=c++11
コマンドに気を取られないでください。ホストコードでstd::unique_ptr
を使用したため、コンパイルを成功させるにはこのフラグを使用する必要がありました。
それでは、この.cl
ファイルはどこで使用されていますか? Hostコードを見ると、以下に番号を付けて繰り返している4つの部分があります。
// 1. Read the program source
std::ifstream sourceFile("vector_add_kernel.cl");
std::string sourceCode( std::istreambuf_iterator<char>(sourceFile), (std::istreambuf_iterator<char>()));
cl::Program::Sources source(1, std::make_pair(sourceCode.c_str(), sourceCode.length()));
// 2. Make program from the source code
cl::Program program=cl::Program(context, source);
// 3. Build the program for the devices
program.build(devices);
// 4. Make kernel
cl::Kernel vecadd_kernel(program, "vecadd");
最初のステップでは、デバイスコードを保持するファイルの内容を読み取り、sourceCode
という名前のstd::string
に入れます。次に、文字列とその長さのペアを作成し、タイプcl::Program::Sources
を持つsource
に保存します。コードを準備したら、program
のcontext
という名前のcl::program
オブジェクトを作成し、ソースコードをプログラムオブジェクトにロードします。 3番目のステップは、OpenCLコードがdevice
用にコンパイル(およびリンク)されるステップです。デバイスコードは3番目のステップで作成されるため、vecadd_kernel
という名前のカーネルオブジェクトを作成し、その内部のvecadd
というカーネルをcl::kernel
オブジェクトに関連付けることができます。これは、プログラムで.cl
ファイルをコンパイルするために必要な一連のステップでした。
私が示して説明したプログラムは、カーネルソースコードからデバイスプログラムを作成します。別のオプションは、代わりにバイナリを使用することです。バイナリプログラムを使用すると、アプリケーションの読み込み時間が長くなり、プログラムのバイナリ配布が可能になりますが、移植性が制限されます。1つのデバイスで正常に機能するバイナリは別のデバイスでは機能しない場合があるためです。ソースコードとバイナリを使用してプログラムを作成することは、それぞれオフラインコンパイルとオンラインコンパイルとも呼ばれます(詳細 ここ )。答えが長すぎるため、ここでは省略します。
私の答えは4年遅れて来ます。それにもかかわらず、@ Farzadの答えを補足する次のようなものを追加します。
紛らわしいことに、OpenCLの慣例では、動詞(to to compile)は、2つの異なる互換性のないものを意味するために使用されます。
1つはビルド時に発生します。もう1つは実行時に発生します。
2つの異なる動詞が導入されていれば、混乱が少なくなったかもしれませんが、用語が進化したわけではありません。通常、動詞コンパイルするは両方に使用されます。
わからない場合は、この実験を試してください。*。clファイルの名前を変更して、他のソースファイルが見つけられないようにしてからビルドします。
見る?それはうまく構築しますね?
これは、ビルド時に* .clファイルが参照されないためです。その後、バイナリ実行可能ファイルを実行しようとすると、プログラムは失敗します。
役立つ場合は、*。clファイルをデータファイル、構成ファイル、またはスクリプトであるかのように考えることができます。文字通り、データファイル、構成ファイル、スクリプトではありません。おそらく、最終的には一種のマシンコードにコンパイルされますが、マシンコードはGPUコードであり、*。clプログラムテキストから作成されていません実行時まで。さらに、実行時には、Cコンパイラ自体は関与しません。むしろ、ビルドを行うのはOpenCLライブラリです。
これらの概念を頭の中で正直するのにかなり長い時間がかかりました。これは、主に-あなたと同じように-私はC/C++ビルドサイクルのステージに長い間慣れていたためです。したがって、私はコンパイルするのような単語の意味がわかっていると思っていました。あなたの心が言葉と概念をまっすぐに理解すると、さまざまなOpenCLドキュメントが意味を持ち始め、作業を開始できます。