精华内容
下载资源
问答
  • c++ 并行编程

    万次阅读 2017-05-02 17:30:52
    本博客将看C++并行编程的例子 首先看如何看 #include #include using namespace std; int main() { clock_t startTime,endTime; startTime = clock(); for (int i = 0; i ; i++) { i++; }

    本博客将看C++并行编程的例子

    1. 线程进程原理

    线程是轻量级的进程,一个进程可以拥有多个线程。

    编译多线程程序加入 <pthread.h> 

    g++ -lphread

    2. openmp库加速

    2.1 openmp库加速配置及hello,world

    事实上有个openmp库,可以实现单台cpu的加速

    Windows下使用vs
    Configuration Properties->C/C++->Language->OpenMP Support,在下拉菜单里选择Yes。然后才能使用OpenMP
    并行代码为例,只需要把相应的行注释掉,就是串行代码了
    默认情况下,并行区内线程数=系统中核的个数

    在Linux下使用g++即可

    #include <stdio.h>
    #include "omp.h"
    int main()
    {
      #pragma omp parallel
      printf( "Hello world!\n" );
      return 0;
    }

    g++ test.cpp
    ./a.out
    输出一个
    g++ -fopenmp  test.cpp
    ./a.out
    输出12个,默认为cpu的逻辑核数(=cpu块数*cpu物理核数*(开启超线程*2))
    export OMP_NUM_THREADS=6
    ./a.out
    输出6个

    一旦采用了OpenMP,线程数量就将由编译器来决定(而不是您),因此无论线程数量如何,重要的是使程序能够正常运行

    2. 实战测试

    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include <math.h>
    
    //一个简单的耗时任务
    double Sum0(double* data,long data_count);
    
    int main()
    {
        long data_count=200000;
        double* data=new double[data_count];
        long i;
    
        //初始化测试数据 
        for (i=0;i<data_count;++i)
            data[i]=(double)(rand()*(1.0/RAND_MAX));
    
        const long test_count=200*2;//为了能够测量出代码执行的时间,让函数执行多次
        double sumresult=0;
        double runtime=(double)clock();
        for( i=0; i<test_count; ++i ) 
        {
            sumresult+=Sum0(data,data_count);
        }
        runtime=((double)clock()-runtime)/CLOCKS_PER_SEC;
        printf ("< Sum0 >  ");
        printf ("  最后结果     = %10.4f  ",sumresult);
        printf ("  执行时间(秒) = %f  ",runtime);
    
        delete [] data;
        return 0;
    }
    
    
    double Sum0(double* data,long data_count)
    {
        double result=0;
        #pragma omp parallel for schedule(static) reduction(+: result)
        for (long i=0;i<data_count;++i)
        {
            data[i]=(double)sin(cos(data[i]));
            result+=data[i];
        }
        return  result;
    }
    
    在windows上

    是否 OMP_NUM_TREADS 时间

    无                     3.437s

    有        2           1.586

    有        4            1.349

    有        8             1.46

    在linux上

    无           2.9036

    有   12   13.9541

    有    6      6.792




    可以看到windows上做了将近三倍的加速,但是在linux上速度降低。可能是编程的方式不对。

    下面将对其进行分析,希望能实现12倍加速



    首先看如何看时间

    #include <time.h>
    #include <iostream>
    using namespace std;
    
    
    int main()
    {
        clock_t startTime,endTime;
        startTime = clock();
        for (int i = 0; i < 10000000; i++)
        {
            i++;
        }
        endTime = clock();
        cout << "Totle Time : " <<(double)(endTime - startTime) / (CLOCKS_PER_SEC*1000) << "ms" << endl;
        return 0;
    }
    

    在linux上运行能看到结果 

    [1] linux下查看核数

    [2] windows下查看核数

    [3] windows下openmp的配置 

    [4] linux下openmp编程基础 

    [5] c++并行与分布式编程[书籍]

    展开全文
  • C++并行编程

    2017-06-08 20:32:55
    深入理解计算机系统中并行编程部分
  • C++并行编程实战

    2018-09-24 22:09:31
    C++并行编程实战,英文版,重点讲解C++ 2011里面的thread mutex lock,原子类型等等
  • C++ Concurrency In Action的中文翻译,称作C++并行编程实践,基于c++11新标准的并发和多线程编程指南。
  • c++并行编程》是中文版的名字,原名为"C++ Concurrency in Action",第一本讲述c++ 11标准中多线程并行编程的书籍,作者又是boost库多线程的重要贡献者,C++11的多线程标准大多来自于boost库,因此,此书具有很高的...
  • 我们都在学习并行编程或则分布式编程。但并行编程或则分布式编程究竟是在解决什么问题呢??????软件开发可以看作是有无数函数和数据结构组成的。当软件运行时会产生各种的状态数据。哪么如果我们有多个计算机。...

    833b8dacf70ec8feb8af9274bb0beed4.png

    我们都在学习并行编程或则分布式编程。

    但并行编程或则分布式编程究竟是在解决什么问题呢??????

    软件开发可以看作是有无数函数和数据结构组成的。

    当软件运行时会产生各种的状态数据。哪么如果我们有多个计算机。

    如何将这些软件平均分配到这些计算机上呢?

    这就是并行编程或则分布式编程所要解决的问题。

    所以有了阿姆达尔定律。

    S=1/(1-a+a/n)

    其中 a 表示了软件并行部分所占的比例。

    n 就是计算机的个数。

    S为并行软件的加速比。

    这样,当1- a=0时,(即没有串行,只有并行)最大加速比S = n;当a = 0时(即只有串行,没有并行),最小加速比S = 1;当 n → ∞时,极限加速比 s→ 1/(1- a),这也就是加速比的上限。

    也就是说我们的软件在运行时有一部分是并行。另一部分串行的。并行的部分可以放入任意计算机内。而串行的部分需要集中放在一台计算机。而对于编程来说,线程可以看作是一个虚拟的计算环境。

    到这里并行问题就变成了,软件如何分配到线程的问题。而答案也非常的明显,找到软件并行的部分和串行的两部分。把串行的部分放入一个线程,并行的部分根据并行的数量放入线程。

    首先写一个简单的程序计算1到10万运算操作然后将结果放入数组输出。

    #include 

    在我的电脑上用了1.36秒。

    将其改为多线程方式,数组的前5个与后5个分别计算。也就是这段软件被分为了两个并行的部分。

    #include 

    计算共耗时0.695秒比单线程方式少了一半左右的时间。

    由阿姆达尔定律可知道。

    因为我们的程序是完全可以并行的两个部分,所以并行比例a为1。线程数n为2得到理论加速比为。

    S = 1/(1-1+1/2) = 2

    与我们的加速比试验结果1.36 / 0.695 = 1.95是相近的。

    如果我们将数组0到5的部分进行两次计算。0到5的部分变为了两次执行的串行操作。

    int 

    修改单线程执行部分的代码如下。

    void 

    可以得到多线程的运算时间为1.37秒。

    在单线程模式下运算时间为2秒。

    由阿姆达尔定律可以得到试验条件的加速比为。

    2.0 / 1.37 = 1.45

    将1.45代入到阿姆达尔公式得到试验的并行比例。

    1.45 = 1/(1-a+a/2)

    a = 0.62

    因为是两个并行操作,一个串行操作。

    与我们理论的并行比例为 2/3=0.666 是接近一致的。

    到这里我们知道了并行编程是依据并行比例拆分软件到不同的硬件上运行的方法。

    并知道如何通过阿姆达尔定理计算出并行的加速比以及反向推算软件的并行比例的方法。

    展开全文
  • 写在前面之前微信公众号里有一位叫sara的朋友建议我写一下Parallel的相关内容,因为手中商城...Parallel是并行编程的相关内容,而Parallel.For和Parallel.Foreach又是并行编程中相当重要的方法,所以不能孤立的去讨...

    写在前面

    之前微信公众号里有一位叫sara的朋友建议我写一下Parallel的相关内容,因为手中商城的重构工作量较大,一时之间无法抽出时间。近日,这套系统已有阶段性成果,所以准备写一下Parallel的相关内容,正好也延续之前的C#并发编程系列。

    Parallel是并行编程的相关内容,而Parallel.For和Parallel.Foreach又是并行编程中相当重要的方法,所以不能孤立的去讨论Parallel,必须要放到并行编程的讨论中去

    并行化,一般是对所要完成的任务进行划分,并且以并发的方式处理属于自己的那份任务,并且最终可以做到整合,所以并行化总会产生并发性。

    实际上并行是并发的子集,并发和并行都可以多线程执行,就看其处理器是否是多核的,这些线程能不能同时被cpu多个核执行,如果可以就说明是并行,而并发是多个线程被cpu单核轮流切换着执行。总之,只有在多核处理器上并行才会有意义

    并行化总会有着很大的挑战,即每一个部分以不同顺序或者交错执行,都能保证最终结果的正确性,尤其涉及到各并行部分之间需要串行执行的部分,这个挑战是很大的。由于并行化程序设计要比普通的串行代码复杂很多,也难维护很多,所以不是所有的问题都可以使用并行的。比如绝对执行时间本来就很少,即使使用并发可以提高整体的执行时间,那么我们也应该使用传统方式。但是如果主要涉及到提升用户响应能力的功能,那么我们推荐使用并行编程,同时处理分割后依然可以独立进行而不影响整体任务的功能也可以使用并行编程。

    并行的相关实战

    说到并行,就需要先说下.NET FX4中引入的Task Parallel Library(任务并行库),简称TPL。TPL主要覆盖了三大使用场景,数据并行、任务并行和流水线,TPL以其高度的封装特性,隐藏了并行编程里复杂的处理,使得开发人员可以以较低的门槛进行并行编程。

    数据并行

    这种场景在于有大量数据需要处理,而且对每一份数据都要执行的同样的操作。

    19bafa868ff28ecba1e1e03d958a13b9.png

    任务并行

    有很多相对独立的不同操作,或者可以分割成多个子任务但彼此之间是独立的,就可以通过任务并行来发挥并行化的优势6e04eb622263e916dcce74e65628ce3e.png

    流水线

    流水线是以上两种场景的结合,这个也是最复杂最难处理的场景,因为这里面涉及到多个并发的任务进行协调处理。

    此场景,奈何小编理解的不是很好,所以不敢乱写,多方查找资料,找到了oschina上的一篇文章。

    流水线技术,指的是允许一个机器周期内的计算机各处理步骤重叠进行。特别是,当执行一条指令时,可以读取下一条指令,也就意味着,在任何一个时刻可以有不止一条指令在“流水线”上,每条指令处在不同的执行阶段。这样,即便读取和执行每条指令的时间保持不变,而计算机的总的吞吐量提高了。

    749475acf07ec87c8a824a39580d6ad9.png

    原文地址:https://my.oschina.net/u/3374461/blog/1930305

    System.Threading.Tasks.Parallel类

    虽然Parallel类在System.Threading.Tasks命名空间下,但是创建并行代码不一定要直接使用Task类的实例,我们可以直接使用Parallel静态类所提供的方法。

    Parallel.For:为固定数目的独立For循环迭代提供了负载均衡式的并行执行

    Parallel.For(0, 5, i =>{    Console.WriteLine("the number is", i);});

    Parallel.Foreach:为固定数目的独立ForEach循环迭代提供了负载均衡式的并行执行。这个方法支持自定义分区器(Partitioner),以使得我们可以完全掌控数据分发。

    string[] letters = new string[] {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M"};Parallel.ForEach(letters, i => Console.WriteLine("letter is " + i));

    Parallel.Invoke:为给定的独立任务提供了负载均衡式的并行执行,接下来会重点讨论这个方法。

    Parallel.Invoke

    这个方法很实用,也很简单。

    以下代码可以返回void的无参数方法:

    Parallel.Invoke(Method1(), Method2(), Method3(), Method4());
    通过Lambda表达式运行: 
    Parallel.Invoke(() => Method1(), () => Method2(), () => Method3(), () => Method4());

    通过Lambda表达式和匿名类型来运行:

    Parallel.Invoke(() =>{    Method1();    // Do something}, () =>{    Method2();    // Do something },  () => {     Method3();     // Do something },  () => {     Method4();     // Do something});

    以上代码需要并行执行四个方法,但是如果空余逻辑内核不足四个或者根本就没有四个逻辑内核,这四个方法是不能并发执行的。因此在理想情况下,正好有至少四个空余逻辑内核时,我们就可以并行执行这四个方法了。

    这四个方法,我们无法准确的预测其执行顺序,因为这一切是由底层的逻辑会根据运行时的现有可用资源创建出最合适的执行计划。当然TPL依然有机制保证方法的顺序执行,这个以后我们再讨论。

    Parallel.Invoke最大的优势就是简单,但是并不能因为它简单,就不分场合的使用,事实上,我们需要在某些场景下权衡使用。

    • 如果这四个方法的执行时间不一致,那么就需要根据最长的执行时间才能返回控制,这就可能造成一些逻辑内核处于闲置状态。所以我们需要预测一下大致的执行时间,如果时间过长,那么就要认真考虑是否真的需要使用这个方法。

    • 其扩展性很差,因为它只能调用固定数目的逻辑内核,剩余内核就会一直处于闲置状态。

    • 方法之间的交互极其困难,极易产生Bug,当然这是并行编程的常见问题,TPL也考虑到了这点,也有足够机制解决这个问题。

    • 如果其中某个方法有了异常,捕捉异常会很困难,所以需要大家在相应的被调用方法里编写足够的日志。

    • 小编在以前的使用中还遇到了内存溢出的异常,这些也会在以后的文章中说明其原因以及解决方法。

    今天就写到这儿吧,已经十二点了,要休息一下,保护头发了,哈哈哈。

    展开全文
  • C++ 并行编程之原子操作的内存顺序 1. C++原子操作的内存顺序概述 memory order主要有以下几种: memory_order_relaxed 只提供对单个atomic变量的原子读/写,不和前后语句有任何memory order的约束关系。 ...

    C++ 并行编程之原子操作的内存顺序

    1. C++原子操作的内存顺序概述

    1. memory order主要有以下几种:
    • memory_order_relaxed
      只提供对单个atomic变量的原子读/写,不和前后语句有任何memory order的约束关系。

    • memory_order_consume
      程序可以说明哪些变量有依赖关系,从而只需要同步这些变量的内存。
      类似于memory_order_acquire,但是只对有依赖关系的内存。意思是别的CPU执行了memory_order_release操作,而其他依赖于这个atomic变量的内存会被执行memory_order_consume的CPU看到。这个操作是C++特有的,x86也不支持这种类型的memory order,不清楚其他种类的cpu是否支持,这还涉及到编译器是否支持这种细粒度控制,也许它直接按memory_order_acquire来处理。

    • memory_order_acquire
      执行memory_order_acquire的cpu,可以看到别的cpu执行memory_order_release为止的语句对内存的修改。执行memory_order_acquire这条指令犹如一道栅栏,这条指令没执行完之前,后续的访问内存的指令都不能执行,这包括读和写。

    • memory_order_release
      执行memory_order_release的cpu,在这条指令执行前的对内存的读写指令都执行完毕,这条语句之后的对内存的修改指令不能超越这条指令优先执行。这也象一道栅栏。
      在这之后,别的cpu执行memory_order_acquire,都可以看到这个cpu所做的memory修改。

    • memory_order_acq_rel
      是memory_order_acquire和memory_order_release的合并,这条语句前后的语句都不能被reorder。

    • memory_order_seq_cst
      这是比memory_order_acq_rel更加严格的顺序保证,memory_order_seq_cst执行完毕后,所有其cpu都是确保可以看到之前修改的最新数据的。如果前面的几个memory order模式允许有缓冲存在的话,memory_order_seq_cst指令执行后则保证真正写入内存。一个普通的读就可以看到由memory_order_seq_cst修改的数据,而memory_order_acquire则需要由memory_order_release配合才能看到,否则什么时候一个普通的load能看到memory_order_release修改的数据是不保证的。

    1. x86的memory order
      x86的memory order是一种strong memory order,它保证:
    • LoadLoad是顺序的
      一个cpu上前后两条load指令是顺序执行的,前面一条没执行完毕,后面一条不能执行
    • StoreStore是顺序的
      一个cpu上前后两条store指令是顺序执行的,前面一条没执行完毕,后面一条不能执行
    • LoadStore
      一个cpu上前面一条是Load指令,这条指令没执行完毕,后面一条store不能执行

    2. 为什么需要定义这样内存顺序

    简单的解释,就是cache的存在,虽然前面更新了变量的值,但是可能存在某个CPU的缓存中,而其他CPU的缓存还是原来的值,这样就可能产生错误,定义内存顺序就可以强制性的约束一些值的更新。
    为什么需要这么多类型?灵活度更大,合理的使用可以最大化利用系统的性能。

    3. 内存操作的种类

    • Store operations —— 写
      which can have memory_order_relaxed , memory_order_release ,or memory_order_seq_cst ordering
    • Load operations —— 读
      which can have memory_order_relaxed , memory_order_consume ,memory_order_acquire, or memory_order_seq_cst ordering
    • Read-modify-write operations —— 读-修改-写
      which can have memory_order_relaxed , memory_order_consume , memory_order_acquire , memory_order_release , memory_order_acq_rel, or memory_order_seq_cst ordering

    4. 内存模型关系

    为什么要理解这个,其实这就是定义内存顺序的目的,因为有了这些关系的需求,才要定义这些顺序。

    • happens-before
      对同一线程,就是一个操作要在另一个操作之前执行。对多个线程,某一个线程的操作A要在另一线程的操作B之前发生。若顺序变了,可能就不能达到我们希望的效果。

    • synchronizes-with
      某一线程的load操作,依赖于另一线程的store操作,简单说,就是线程A读取的变量值a,应该是线程B修改后的值。

    5. C++定义的内存顺序分别可以实现哪些关系?

    • relaxed 松散顺序
      memory_order_relaxed 由这个值指定的,这个很简单,就是各个CPU读取的值是未定义的,一个CPU在一个线程中修改一个值后,其他CPU不知道。

    • sequentially-consistent 顺序一致顺序
      memory_order_seq_cst 由这个值指定,这个也很简单,相当于各CPU的原子操作都是在一个线程上工作,一个修改后,其他CPU都会更新到新的值。
      如下例,在此顺序下,只要x,y的值修改了,其他线程的load操作就会是最新的值,所以线程c和d,不管哪个先执行完,后执行完的肯定会修改z,所以一定不会报异常。但是要是松散模型,即使x,y变了,但是在线程cd load时,也可以是之前为未修改的值。

    #include <atomic>
    #include <thread>
    #include <assert.h>
    std::atomic<bool> x,y;
    std::atomic<int> z;
    void write_x() //a
    {
    x.store(true,std::memory_order_seq_cst);
    }
    void write_y() //b
    {
    y.store(true,std::memory_order_seq_cst);
    }
    void read_x_then_y() //c
    {
    while(!x.load(std::memory_order_seq_cst)); //  1
    if(y.load(std::memory_order_seq_cst))  // 2
    ++z;
    }
    void read_y_then_x() //d
    {
    while(!y.load(std::memory_order_seq_cst)); // 3
    if(x.load(std::memory_order_seq_cst)) // 4
    ++z;
    }
    int main()
    {
    x=false;
    y=false;
    z=0;
    std::thread a(write_x);
    std::thread b(write_y);
    std::thread c(read_x_then_y);
    std::thread d(read_y_then_x);
    a.join();
    b.join();
    c.join();
    d.join();
    assert(z.load()!=0);
    }
    
    • acquire-release 获得-释放顺序
      acquire操作(load)—— memory_order_consume , memory_order_acquire
      release操作(store)—— memory_order_release
      acquire-release操作 —— memory_order_acq_rel (先acquire再做一次release,对于fetch_add这样的操作,既要load一下,又要store一下的,就用这个)
      一个原子做了这个acquire操作,肯定是读的是这个原子最后一次release操作修改的值。换句话说,要是采用release的方式store一个值,那么其他CPU都会看到这一次的修改。
      (不举例子了,C++并发编程实战这本书上有,理解后再去看还是比较简单的)

    • memory_order_consume 和 memory_order_acquire 有什么区别?
      memory_order_consume: 只能保证当前的变量,不能保证此线程中此load操作语句之前的其他变量的操作也被其他CPU看到,也就是只能保证自己在其他CPU上都更新了。(这样说不准确,应该做可以保证当前变量,以及此线程中此语句之前的,并且与当前变量有依赖关系的变量的修改被其他CPU看到)
      memory_order_acquire:不仅能保证当前变量的修改被其他CPU看到,也能保证这条语句之前的其他变量的load操作的值,也被其他CPU看到,不论其他变量是什么顺序的(relax的也会更新)。

    参考

    [1] 知乎上的这个回答,写的很好: 如何理解 C++11 的六种 memory order? - 知乎
    [2] C++11 memory order-博客-云栖社区-阿里云
    [3] 官方文档写的还可以(当我理解了,才觉得很好) std::memory_order - cppreference.com
    [4] 《C++并发编程实战》或者《C++ Concurrency in Action》

    展开全文
  • CUDA C++编程手册(总论)CUDA C++ Programming GuideThe programming guide to the CUDA model and interface.Changes from Version 10.0Use CUDA C++ instead of CUDA C to clarify that CUDA C++ is a C++ ...
  • Visual C++并行编程实战

    2017-05-03 13:33:00
    mobi版的,方便推送到kindle上进行阅读,经过简单的转换也可以转换成其他版本
  • Parallelism in Modern C++ - Hartmut Kaiser - CppCon 2016
  • 并发编程导论随着硬件性能的迅猛发展与大数据时代的来临,并发编程日益成为编程中不可忽略的重要组成部分。简单定义来看,如果执行单元的逻辑控制流在时间上重叠,那它们就是并发(Concurrent)的。并发编程复兴的...
  • 一.如果只是简单地解决在多线程中对共享资源的读写并发...在此基础上,如果想在并行编程中获得更好的性能,尤其当使用的是一些弱内存顺序的平台(比如PowerPC)的话,设定原子操作间的内存顺序则很有必要. C++11 加入了...
  • 由北京并行科技股份有限公司与中科院计算所西部高等技术研究院联合主办、北京中科图云科技有限公司与北京安控冠博科技有限公司协办的“多语言框架高性能GPU并行编程--C/C++,Matlab,Python”培训将于2019年12月14日...
  • 一.如果只是简单地解决在多线程中对共享资源的读写并发问题,只需要用C++以下内容: 线程...在此基础上,如果想在并行编程中获得更好的性能,尤其当使用的是一些弱内存顺序的平台(比如PowerPC)的话,设定原子操作间的内存...
  • 并行计算笔记(001)-安装mpi,openmp 动力需要用高性能计算求解微分方程,这也将是我这接下来三年的学习方向之一。目前以MPI为主,将来肯定会涉及到异构计算。希望写这些笔记记录我的修炼历程。目前的学习目标及就是...
  • C++并行编程1

    2014-08-19 00:55:00
    不一定欧,如果你计算机是单核,就一定不是并行了,而是把你的任务分成你根本感觉不到的任务片,近似在并行执行,其实是在串行执行。如果是双核,也不一定,有可能一个核上同时执行两个任务,也有可能是并行欧,不同...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,438
精华内容 575
关键字:

c++并行编程

c++ 订阅