本节中,我们以一个应用开发者的角度来看一个较为复杂的例子。这里的代码需要使用之前章节所提到的编译技术进行转换,转换成一个正确,且有较高性能OpenCL实现。应用我们选择了二项式期权。注意,我们不会从数学和经济学的角度深度探讨该问题,只是对于编译器作者来说,将其做为一个完整的例子。
Copy void binomial_options_gpu (
std :: vector < float > & v_s ,
std :: vector < float > & v_x ,
std :: vector < float > & v_vdt ,
std :: vector < float > & v_pu_by_df ,
std :: vector < float > & v_pd_by_df ,
std :: vector < float > & call_value)
上面的代码就是二项式期权函数的声明,其中call_value作为存储最终结果的对象,其他的参数都仅作为输入参数。
Copy extent < 1 > e ( data_size );
arrar_view < float , 1 > av_call_value ( e , call_value );
av_call_value . discard_data ();
为了将输入数据输入内核函数,数据需要通过C++ AMP
的容器进行包装。本例中,使用concurrency::array_view
。av_call_value对象调用discar_data,就是用来告诉运行时,这段数据无需从主机端拷贝到设备端。
Copy array_view < const float , 1 > av_s ( e , v_s );
array_view < const float , 1 > av_x ( e , v_x );
array_view < const float , 1 > av_vdt ( e , v_vdt );
array_view < const float , 1 > av_pu_by_df ( e , v_pu_by_df );
array_view < const float , 1 > av_pd_by_df ( e , v_pd_by_df );
exten < 1 > ebuf ( MAX_OPTIONS * (NUM_STEPS + 16 ));
array < float , 1 > a_call_buffer ( ebuf );
注意这里av_s,av_x,av_vdt,av_pu_by_df,av_pd_by_df均由array_view包装,也就是在计算完成后不需要拷贝回主机。
Copy extent < 1 > compute_extent ( CACHE_SIZE * MAX_OPTIONS);
parallel_for_each ( compute_extent . tile < CACHE_SIZE > () ,
[ = , & a_call_buffer]tile_index<CACHE_SIZE> ti)restrict( amp ){
binomial_options_gpu (ti , av_s , av_x , av_vdt , av_pu_by_df , av_pd_by_df , av_call_value , a_call_buffer);
});
av_call_value . synchronize ();
C++ AMP
使用parallel_for_each完成计算。在计算完成之后,使用同步成员函数对计算结果进行同步,以确保所有计算结果都已经保存在容器中。所有使用到的数据都会在运行时进行隐式处理。编程者不需要显式的在设备和主机之间进行数据的传递或拷贝。注意parallel_for_each使用显式线程划分进行线程局部控制。
Copy void binomial_options_kernel (
tiled_index < CACHE_SIZE > & tidx ,
array_view < const float , 1 > s ,
array_view < const float , 1 > x ,
array_view < const float , 1 > vdt ,
array_view < const float , 1 > pu_by_df ,
array_view < const float , 1 > pd_by_df ,
array_view < float , 1 > call_value ,
array < float , 1 > & call_buffer) restrict ( amp ){
index < 1 > tile_idx = tidx . tile;
index < 1 > local_idx = tidx . local;
tile_static float call_a [CACHE_SIZE + 1 ];
tile_static float call_b [CACHE_SIZE + 1 ];
int tid = local_idx [ 0 ];
int i;
for (i = tid; i <= NUM_STEPS; i += CACHE_SIZE){
index < 1 > idx ( tile_idx [ 0 ] * (NUM_STEPS + 16 ) + (i));
call_buffer [idx] = expiry_call_value ( s [tile_idx] , x [tile_idx] , vdt [tile_idx] , i);
}
for (i = NUM_STEPS; i > 0 ; i -= CACHE_DELTA){
for ( int c_base = 0 ; c_base < i; c_base += CACHE_STEP){
int c_start = min (CACHE_SIZE - 1 , i - c_base);
int c_end = c_start - CACHE_DELTA;
tidx . barrier . wait ();
if (tid <= c_start){
index < 1 > idx ( tile_idx [ 0 ] * (NUM_STEPS + 16 ) + (c_base + tid));
call_a [tid] = call_buffer [idx];
}
for ( int k = c_start - 1 ; k >= c_end;){
tidx . barrier . wait ();
call_b [tid] = pu_by_df [tile_idx] * call_a [tid + 1 ] + pd_by_df [tile_idx] * call_a [tid];
k -- ;
tidx . barrier . wait ();
call_a [tid] = pu_by_df [tile_idx] * call_b [tid + 1 ] + pd_by_df [tile_idx] * call_b [tid];
k -- ;
}
tidx . barrier . wait ();
if (tid <= c_end){
index < 1 > idx ( tile_idx [ 0 ] * (NUM_STEPS + 16 ) + (c_base + tid));
call_buffer [idx] = call_a [tid];
}
}
if (tid == 0 ){
call_value [tile_idx] = call_a [ 0 ];
}
}
声明为tile_static类型的数据将在同一工作组内进行共享。为了确保共享数据的一致性,我们这里使用了tidx.barrier.wait函数。在同一工作组内的工作项将会在这个调用点进行等待,直到工作组内所有线程都到达该调用点为止。