并行与分布式计算——优化计算10^7个整数的向量之和

优化计算10^6个整数数的向量之和

1.问题描述

  • 在第一次课程中,早起单节点计算系统并行的粒度分为:Bit级并行、指令级并行和线程级并行。现代处理器如Intel、ARM、Power以及国产CPU如华为鲲鹏等均包含了并行指令集
    • 请调查这些处理器中的并行指令集,并选择其中一种进行编程练习,计算两个各包含10^6个整数的向量之和
    • 现代操作系统为了发挥多核的优势,支持多线程并行编程模型,请将问题1用多线程的方式实现,线程实现的语言不限
  • 参考实现:
    • click here https://github.com/chen0031/AVX-AVX2-Example-Code
    • click here https://software.intel.com/en-us/articles/using-intel-avx-without-writing-avx/
    • click here https://www.tutorialspoint.com/java/java_multithreading.htm

2.解决方案

问题一:
  • SIMD(Single-Instruction,Multiple-Data)单指令多数据流技术
    e = a + b
    f = c + d
    m = e*f
    运用SIMD技术同时计算第一条和第二条指令。一条指令处理了4个操作数。SIMD指令集可以提供更快的图像、声音、视频数据等运行速度
  • Intel AVX指令集,在单指令多数据流计算性能增强的同时也沿用了的MMX/SSE指令集。不过和MMX/SSE的不同点在于增强的AVX指令,从指令的格式上就发生了很大的变化。AVX是在之前的128位扩展到和256位的单指令多数据流.
  • 使用AVX指令级,并行执行8个矢量加法,实现优化计算;而往常我们都是用一层for循环实现,通过对比可以看出串行与并行计算的区别及差距,下面是指令级并行的部分函数代码,用于实现8个向量同时相加
    clock_t sum_avx(int *arr1, int *arr2, int *result) 
    {
        int i = 0;
        int j = 0;
        for(i = 0; i < __VEC_LENGTH__; i += 8)
        {
            vecarr1[i / 8] = _mm256_setr_ps(arr1[i], arr1[i + 1], arr1[i + 2], arr1[i + 3],arr1[i+4], arr1[i + 5], arr1[i + 6], arr1[i + 7]);
            vecarr2[i / 8] = _mm256_setr_ps(arr2[i], arr2[i + 1], arr2[i + 2], arr2[i + 3],arr1[i+4], arr1[i + 5], arr1[i + 6], arr1[i + 7]);
        }
        
        struct timeval tv1, tv2;
        gettimeofday(&tv1, NULL);
        for(i = 0; i < __VEC_LENGTH__ / 8; ++i)
        {
            temp[i] = _mm256_add_ps(vecarr1[i], vecarr2[i]);
        }
        gettimeofday(&tv2, NULL);
        
        for(i = 0; i < __VEC_LENGTH__ / 8; ++i)
        {
            result[i / 8] = temp[i][0];
            result[i / 8 + 1] = temp[i][1];
            result[i / 8 + 2] = temp[i][2];
            result[i / 8 + 3] = temp[i][3];
            result[i / 8 + 4] = temp[i][4];
            result[i / 8 + 5] = temp[i][5];
            result[i / 8 + 6] = temp[i][6];
            result[i / 8 + 7] = temp[i][7];
        }
        return tv2.tv_usec - tv1.tv_usec;
    }
问题二:
  • 什么是线程:

    同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。 一个进程可以有很多线程,每条线程并行执行不同的任务。

  • 线程的优点:

    线程可以提高应用程序在多核环境下处理诸如文件I/O或者socket I/O等会产生堵塞的情况的表现性能。在很多情况下,完成相关任务的不同代码间需要交换数据。如果采用多进程的方式,那么通信就需要在用户空间和内核空间进行频繁的切换,开销很大。但是如果使用多线程的方式,因为可以使用共享的全局变量,所以线程间的通信(数据交换)变得非常高效

  • 采用C语言实现多线程用于优化计算,部分代码如下:
    gettimeofday(&tv1, NULL);
    // 绑定线程,开始分叉
    for (thread = 0; thread < thread_count; ++thread)
        pthread_create(&thread_handles[thread],NULL,sum_vec,(void*)thread);
    //线程hello,和给hello的参数绑定到线程上
    // 结束线程
    for (thread = 0; thread < thread_count; ++thread)
        pthread_join(thread_handles[thread], NULL);
    gettimeofday(&tv2, NULL);
    // 输出高维加法执行时间
    printf("Spliting the date to 4 pieces takes %ld us\n",
        (tv2.tv_usec - tv1.tv_usec));
        
    printf("The first three elements of result is %lf %lf %lf\n",
        result[0], result[1], result[2]);
    free(thread_handles);

3.实验结果

问题一的结果:
cal_wayrun_time1run_time2run_time3
series41284us41284us49970us
AVX_pal19990us29982us19990us

经过多次计算发现,加速比大约为2左右

问题二的结果:
cal_wayrun_time1run_time2run_time3
series59968us59965us60002us
Mul_thr_pal39979us19989us29951us

经过多次计算发现,加速比大约为2.2左右

4.遇到的问题及解决方法

1. 刚开始AVX优化后的运行时间比串行循环的运行时间还要长
  • 在进行一些简单运算时,编译器会在你没有主动使用SIMD的情况下通过一些优化技术自动转换为SIMD的代码,所以你自己写的使用SIMD的代码最快也不会超过编译器自动优化的结果。编译器编译时默认会有-O1优化,所以编译时时需要加上-O0

  • 以加法指令为例,单指令流单数据流(SISD)型CPU对加法指令译码后,执行部件先访问主存,取得第一个操作数,之后再一次访问主存,取得第二个操作数,随后才能进行求和运算;而在SIMD型CPU中,指令译码后,几个执行部件同时访问主存,一次性获得所有操作数进行运算,而其转存的时间大约占到30%,这额外的开销是比较大的,对于这种简单加法,使用SIMD带来的计算上的效率提升,掩盖不了使用SIMD带来的额外性能损失,可能计算比较复杂时SIMD才有加速的效果,故计算时间转存的时间不加进去

2. 我的电脑是4核8线程,但在多线程优化计算中分成8线程和4线程的效果差不多
  • 四核八线程是指使用了超线程技术 , 把一个物理核心模拟成两个逻辑核心, 理论上要像八颗物理核心一样在同一时间执行八个线程,所以设备管理器和任务管理器中会显示出八个核心,但事实上并不是真正的八个核心,四核八线程就是真四核,虚拟八核

  • 四核八线程在有些情况下比如任务量不大能让CPU利用率提高很多从而使其性能接近八核CPU的水平,而在另外一些情况比如CPU占用100%满负荷工作的情况下,这时候四核八线程和八核的性能表现差距明显,其实质就是虽然采用超线程技术能同时执行两个线程,但它并不象两个真正的CPU那样,每个CPU都具有独立的资源。当两个线程都同时需要某一个资源时,其中一个要暂时停止,并让出资源,直到这些资源闲置后才能继续。因此超线程的性能并不等于两颗CPU的性能。这也是四核八线程和八核的最大区别

对了这里需要注意的一点是,在使用gcc编译带有immintrin.h头文件函数的代码时,根据你使用的函数类别,需要添加相应的编译选项(老的SSE是默认支持的通常不需要额外添加选项),如AVX2.0就需要使用-mavx2、FMA需要使用-mfma等等,否则编译会报错

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值