C++多線程求數組和

寫在前面

點擊率(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;
}
部分求和函數blockSum
void 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);
}
合併部分和函數mergeSum
void 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循環求和的速度對比,腫麼多線程花費時間還多?! 不是很理解,可能多線程有線程切換的時間消耗?!!



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章