【原】C++11並行計算 — 數組求和


本文轉載請注明出處 —— polobymulberry-博客園

0x00 - 前言


最近想優化ORB-SLAM2,准備使用並行計算來提高其中ORB特征提取的速度。之前對並行計算方面一竅不通。借此機會,學習一下基本的並行編程。

在選擇並行編程的工具時,需要考慮以下問題:即該工具盡量不要使用與平台相關的API,如iOS端的GCD(Grand Central Dispatch),因為希望程序具有很強的移植性。一開始我想到的只有兩種選擇,一個是以TBB和OpenMP為首的第三方線程庫,另一個是原生線程庫。其中TBB和OpenMP對於Xcode的支持不是很好,原生線程庫又依賴於系統平台,所以嘗試后都放棄了。后來想到C++11已經從語言層面支持了多線程開發,也就是提供了thread庫,於是抱着試一試學一學的態度就入坑了。

並行計算中一個很經典的案例就是數組求和,網絡上有很多介紹C++11的thread使用、源碼分析的文章,不過使用C++11進行數組求和並行計算的示例卻很少,所以才有了這篇博文。

0x01 - 代碼解析


在iOS系統使用C++11進行開發。

//
//  ViewController.m
//  TestDispatch
//
//  Created by poloby on 2017/1/7.
//  Copyright © 2017年 polobymulberry. All rights reserved.
//

#import "ViewController.h"
#include <iostream>
#include <thread>

using namespace std;

// 作為求和函數的參數
// 封裝了求和函數的輸入和輸出
typedef struct ThreadArg {
    long long base;     // 從base~base+length數列求和
    long long length;
    long long sum;      // 將上述數列的和存儲在sum中
}ThreadArg;

void sum(ThreadArg *arg)
{
    long long begin = arg->base;
    long long end = arg->base + arg->length;
    long long sum = 0;
    // 不要直接使用for(long long i = arg->base; i < arg->base + arg->length)
    // 也不要使用arg->sum += i;
    // 因為指針的讀取比普通棧的讀取需要多花費一些時間
    for (long long i = begin; i < end; ++i) {
        sum += i;
    }
    arg->sum = sum;
}

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // 計算1~count數列之和
    const long long count = 1000000000;
    
    // 單線程常用方法
    NSDate *commonMethodDate = [NSDate date];
    long long commonMethodSum = 0;
    for (long long i = 0; i < count; ++i) {
        commonMethodSum += i;
    }
    // 計算單線程使用時間
    double commonMethodDuration = [[NSDate date] timeIntervalSinceDate:commonMethodDate];

    NSLog(@"Common method spend time = %fms, sum = %lld", commonMethodDuration * 1000, commonMethodSum);
    
    // 並行計算方法
    // 將1~count數列平均分為threadCount組,求解每組數列之和,再將其相加得到總和
    NSDate *parallelMethodDate = [NSDate date];
    // 設置並行線程數目
    const int threadCount = 2;
    thread threads[threadCount];
    ThreadArg args[threadCount];
    // 初始化線程及其參數
    for (int i = 0; i < threadCount; ++i) {
        long long offset = (count / threadCount) * i;
        args[i].base = offset;
        args[i].length = MIN(count - offset, count / threadCount);
        threads[i] = thread(sum, &args[i]);
    }
    
    // 啟動線程並等待線程退出
    for (int i = 0; i < threadCount; ++i) {
        threads[i].join();
    }
    
    long long parallelMethodSum = 0;
    // 將每組數列之和相加得到總和
    for (int i = 0; i < threadCount; ++i) {
        parallelMethodSum += args[i].sum;
    }
    
    // 計算多線程使用時間
    double parallelMethodDuration = [[NSDate date] timeIntervalSinceDate:parallelMethodDate];
    
    NSLog(@"Parallel method spend time = %fms, sum = %lld", parallelMethodDuration * 1000, parallelMethodSum);
}

@end

0x02 - 結果分析


Xcode8.2.1+iPhone7模擬器+1~1000000000數列之和:
線程數目 2 4 8
多線程耗時 1921.253026ms 981.853008ms 684.603035ms
單線程耗時 3171.698034ms 3472.517014ms 3447.206974ms

Xcode8.2.1+iPhone7模擬器+1~10000數列之和:

線程數目 2 4 8
多線程耗時 0.279963ms 0.212014ms 0.297010ms
單線程耗時 0.038981ms 0.027955ms 0.032008ms
可見多線程本身也需要消耗一定的資源,所以只有在系統規模較大的情況下才能取得顯著的性能提升。

0x03 - 注意事項


1. thread調用類的成員函數:

thread memberFuncThread(&ClassName::MemberFuncName, this, arg1, arg2...);

2. thread傳遞引用參數:

需要使用std::ref進行包裝,詳見thread - 傳遞引用參數


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM