原文地址:http://coderdock.com
由上一節我們已經清楚了宿主機通過命令隊列向計算設備傳遞命令。計算設備計算後返回。但是宿主機需要管理多個計算設備,多個命令隊列。那麼宿主機是如何管理、調度這些對象的呢。沒錯,就是事件(event)。
1.事件定義
事件是與命令的狀態相關聯的對象。命令隊列中的命令會產生事件,其他命令在執行之前需要等待某個事件。如我們下面條之前需要查看水開了沒有,那麼水開了
就是一個事件。
根據場景的不同,可以分爲內核端事件和宿主機端事件:
- 內核端事件:主要負責異步執行命令的同步操作(多個處理單元的階段同步)和全局內核和本地內存的同步。(內核端事件類似於
個人自掃門前雪
的意味,因爲人民內部矛盾內部解決嘛
)。 - 宿主機端事件:完成命令隊列之間的同步操作(統籌各個計算設備的操作)。
同時事件還可以劃分爲命令事件
和用戶自定義事件
。
2.事件分類
2.1命令事件
事件在命令之間傳遞狀態信息。命令的狀態即事件的值可以取以下:
- CL_QUEUED:命令已經加入命令隊列。
- CL_SUBMITTED:命令已經有宿主機提交給與所在命令隊列相關聯的設備。
- CL_RUNNING:該命令正在執行。
- CL_COMPLETE:命令已經完成
- ERROR_CORE:負數,指代不同的錯誤情況。
創建事件的方法有很多,最常見的還是命令在狀態發生變化時。如下面的命令是將內核加入隊列的API,那麼就會產生一個CL_QUEUED
命令。
cl_int clEnqueueNDRangeKernel(
cl_command_queue command_queue,
cl_kernel kernel,
cl_unit work_dim,
const size_t* global_work_offset,
const size_t* global_work_size,
const size_t* local_work_size,
cl_uint num_events_in_wait_list,
const cl_event* event_wait_list,
cl_event* event
)
我們着重關注一下後面三個參數:- num_events_in_wait_list:在執行這個命令之前需要等待幾個事件。
- event_wait_list:這是一個列表,裏面是要等待的具體事件。
- event:這個命令相關聯的事件,如當前會將其賦值爲
CL_QUEUED
,之後在執行的時候會賦值爲CL_RUNNING
等等。
再看一下多個事件之間的聯繫的一個示例:
1 2 3 4 5 6 | cl_event k_events[2]; //定義兩個事件 clEnqueueNDRangeKernel(command1, .... , 0, NULL, &k_events[0]); //將第一個命令加入隊列,會對k_events[0]賦值,指向之前不需要等待任何事件 clEnqueueNDRangeKernel(command2, ...., 0, NULL, &k_events[0]); //同上 clEnqueueNDRangeKernel(command3, ...., 2,&k_events, NULL) //執行這條命令時會等待前面的兩個命令 |
2.2用戶自定義事件
命令事件主要在命令隊列中產生,影響同一個上下文的設備。如果我們需要與其他上下文進行同步,就需要使用到用戶自定義事件:
1 2 3 4 | cl_event clCreateUserEvent( cl_context context, //指定上下文 cl_uint* errcode_ret //該函數所關聯的錯誤值 ) |
如果創建成功,errcode_ret會被賦值爲CL_SUCCESS
或者錯誤時,賦值爲一下的值:
- CL_INVALID_EVENT:上下文不合法
- CL_OUT_OF_RESOURCE: 資源未就緒或分配資源失敗
- CL_OUT_OF_HOST_MEMORY:宿主機資源未就緒或分配資源失敗
之後我們就可以在各個處理函數中設置返回的事件的值:
1 2 3 4 | cl_int clSetUserEventStatus( cl_event event, //具體事件值 cl_int execution_status //指向狀態 ) |
3.事件管理
上面已經說過了用戶自定義事件的設置,那麼命令事件的管理又是通過那些API的呢?
1 2 3 | clGetEventStatus clRetainEvent //增加引用計數,即得到事件 cl_ReleaseEvent //減少引用計數 |
當然如果需要獲取事件的更詳細的信息,需要用到:
1 2 3 4 5 6 7 | cl_int clGetEventInfo( cl_event event, //具體事件 cl_event_info param_name, //查詢的信息,如命令隊列,或者上下文 size_t param_value_size, //參數大小, void* param_value, //指向的結果的指針 size_t param_value_size_ret //返回結果的大小 ) |
4.事件回調
事件是OpenCL中爲命令指定明確順序約束的機制。不過事件不能跨上下文,跨上下文邊界時,只有一種選擇,就是讓宿主機幫忙等待一個事件,然後使用用戶事件觸發另一個上下文命令的執行。總而言之,由於事件不能跨上下文,所以宿主機必須代表兩個命令隊列在兩個上下文之間管理事件。
同時,事件還可以使用OpenCL定義的回調機制與宿主機上的函數進行交互。回調就是應用程序異步調用的函數。
我們可以使用下面這個函數設置回調:
1 2 3 4 5 6 7 8 9 | cl_int clSetEventCallback( cl_event event, cl_int command_exec_callback_type, void(CL_CALLBACK *pfn_event_notify)( cl_event event, cl_int event_command_exec_status, void *user_data), void *user_data ) |
注意事項:
- 撤銷一個事件之前,與這個事件所關聯的所有回調函數必須撤銷,
- 回調函數不能是代價高昂的的處理函數,或者調用OpenCL的API來創建上下文或命令隊列,或者調用阻塞函數。
- 爲一個事件狀態註冊的多個回調函數,但不能保證按照註冊的順序執行。
- 回調函數應保證是線程安全的,會被異步調用。
5.內核事件
上面的命令、事件都是要與命令隊列相關聯。可以同步命令,有助於命令與宿主機之間的交互提供細粒度的控制。
那麼內核中是否有相同的機制呢?其實事件還可以出現在內核內部,內核中的事件主要用來支持在全局與局部內存之間的異步數據複製。下面這些函數用以支持這些功能。
1 2 3 | event_t async_work_group_copy() event_t async_work_group_strided_copy event_t wait_group_events |
會在後面的章節有所介紹。