寫在前面
點擊率(CTR)預估是計算廣告中的重要一環。CTR預估也即預估某個廣告被用戶點擊的概率,需要用到廣告,用戶,展示場景等幾個維度的信息。CTR預估一般利用LR算法來建模,最終會轉化爲一個凸優化問題。常見求解方法,最優化之路這裏總結的不錯。實際問題中一般特徵維數都較高,採用lbfgs或OWLQN(L1範數)算法求解較多,其中OWLQN源代碼見這裏。
看到公司單機版CTR預估算法就是修改OWLQN源代碼。通過README知道,我們只需要繼承DifferentiableFunction類定義一個loss function,這個類只有一個方法:
double Eval(const vector<double>& input, vector<double>& gradient) const.
該方法計算損失函數在點input處的取值,同時計算在該點處的梯度gradient。這裏不必考慮L1正則項,會在後面特殊處理。
寫Eval時,源代碼是寫for循環針對每個ins計算損失函數以及梯度值。由於這裏根據每個ins計算損失函數和梯度值,都是獨立的,公司大牛是採用了多線程方法改寫的。
囉嗦完畢,對於多線程編程,一直想學習下。於是模仿上面改進,寫了個多線程求數組和的程序。
多線程求和
基本思路,將數組切割爲幾個部分,分別求和,然後不斷累加部分和。
定義的一些基本變量和函數如下:
vector<int> num; //求和數組
vector<int> parts_sum; //部分和
int threadNum; //線程數目
boost::mutex mutex; //互斥鎖
boost::interprocess::interprocess_semaphore empty; //信號量
boost::interprocess::interprocess_semaphore full; //信號量
std::queue<int> ready; //記錄線程產生數據的消耗情況
void produce(int thread);
int consume();
void blockSum(int thread); //部分求和
void mergeSum(int t, int s); //部分和合並
int Sum(); //求和接口函數
構造函數初始化如下:
multi_thread_sum(vector<int> &num, int threadNum_): num(num), parts_sum(threadNum_), threadNum(threadNum_), empty(threadNum_), full
(0) {};
幾個重要函數實現如下:
produce函數
void multi_thread_sum::produce(int thread) {
empty.wait();
mutex.lock();
ready.push(thread);
mutex.unlock();
full.post();
}
consume函數int multi_thread_sum::consume() {
full.wait();
mutex.lock();
int r = ready.front();
ready.pop();
mutex.unlock();
empty.post();
return r;
}
部分求和函數blockSumvoid multi_thread_sum::blockSum(int thread) {
unsigned int part = num.size()/threadNum;
unsigned int begin = part * thread;
unsigned int end = (thread == threadNum - 1) ? num.size() : begin + part;
cout << "thread: " << thread << endl;
cout << "begin: " << begin << " end: " << end << endl;
for(unsigned int i = begin; i < end; ++i) {
parts_sum[thread] += num[i];
}
cout << "parts_sum[" << thread << "] " << parts_sum[thread] << endl;
produce(thread);
}
合併部分和函數mergeSumvoid multi_thread_sum::mergeSum(int t, int s) {
cout << endl;
cout << "multi_thread_sum::mergeSum " << t << "\t" << s << endl;
cout << parts_sum[t] << "\t" << parts_sum[s] << endl;
parts_sum[t] += parts_sum[s];
cout << parts_sum[t] << "\t" << parts_sum[s] << endl;
produce(t);
}
接口函數Sum
int multi_thread_sum::Sum() {
for(int i = 0; i < threadNum; ++i) {
boost::thread(boost::bind(&multi_thread_sum::blockSum, this, i));
}
for(int i = 1; i < threadNum; ++i) {
boost::thread(boost::bind(&multi_thread_sum::mergeSum, this, consume(), consume()));
}
int merge_sum = consume();
return parts_sum[merge_sum];
}
調試程序中幾點疑問:
信號量和鎖的順序是否可以交換? 暫時沒認真思考過。。
信號量可否不要? 不行!只有produce了部分和,mergeSum函數中的consume才能工作。
多線程求和和直接for循環求和的速度對比,腫麼多線程花費時間還多?! 不是很理解,可能多線程有線程切換的時間消耗?!!