SIMD加速計算矩陣(組成原理實驗5)


介紹

實驗5的文檔內容:https://shimo.im/docs/4iV7Rw1nxLgeMsBe/

以下僅實現了第一部分的SIMD的功能。

  • a) 介紹
    並行化是計算機硬件的大趨勢。然而,程序員不做任何修改,只依賴於計算機體系結構專家、編譯器設計者和芯片工程師的工作就能讓程序跑得更快,這樣的日子已經一去不復返了。因此,如果想要讓程序跑得更快,軟件設計人員應該掌握並行編程的基本思想。
    在這部分實驗中,你需要利用SIMD內蘊函數編寫並行代碼解決問題。
  • b) 熟悉實驗代碼
    你將拿到兩個源文件 randomized.cpp 和 common.h, 你可以在你熟悉的開發平台上編譯randomized.cpp,並觀察運行效果。你需要優化的函數sum()放在common.h頭文件中,這段代碼完成的核心功能是對一個整型數組進行有條件求和,並對程序運行時間進行計時。由於核心函數的SIMD版本還未實現,你應該能夠看到兩個版本之間的運行結果是不一致的。
  • c) 使用SIMD內蘊函數對求和函數進行優化
    在common.h頭文件中找到函數sum_simd(), 你需要根據以下代碼進行相應的優化:

(注意:你僅僅需要對內層的循環體進行優化。)
在優化的過程中,你可能會用到以下內蘊函數:
在這里插入圖片描述

  • d) 一些有益的提示

    • i. __m128i是Intel用於聲明128-bit向量的數據類型,我們會用一個__m128i類型的變量存放四個32-bit的整形數;
    • ii. 代碼中提供了一個叫做_127的變量,它當中包含了數字127的四份拷貝,你可以把它用在比較上;
    • iii. 在你完成內層循環體的計算之前,不要使用保存函數(_mm_storeu_si128)。這個函數的計算代價很高,如果你在每一次循環結束時都使用它,你會發現你的代碼的性能不夠好;
    • iv. 在訪問__m128i類型的向量時,不推薦你直接地訪問它當中的每一個元素,更好的方法是把__m128i類型的向量用storeu的方法存放到一個普通的數組當中,然后單獨訪問這個數組當中的元素;
    • e) 實驗結果與實驗報告
      當你完成代碼之后,你應該能夠觀察到以下效果:
  • i. SIMD版本的代碼與未優化的代碼的計算結果保持一致;

  • ii. SIMD版本的代碼應該要比未優化的代碼快(快多少,記錄下性能加速比,並且分析為什么你認為它是正確的結果)

    在你的實驗報告中提供你的代碼、運行結果和你的思考過程,並上傳一個簡短的帶解說的演示視頻(只給出代碼截圖和實驗結果,但卻不提供分析過程的不給分。)

  • 實驗給出的源代碼common.h

#ifndef COMMON_H
#define COMMON_H

#include <x86intrin.h>

#define NUM_ELEMS ((1 << 15) + 10)
#define OUTER_ITERATIONS (1 << 15)

/* 不要修改這個函數 */
long long int sum(unsigned int vals[NUM_ELEMS]) {
	clock_t start = clock();

	long long int sum = 0;
	for(unsigned int w = 0; w < OUTER_ITERATIONS; w++) {
		for(unsigned int i = 0; i < NUM_ELEMS; i++) {
			if(vals[i] >= 128) {
				sum += vals[i];
			}
		}
	}
	clock_t end = clock();
	printf("Time taken: %f s\n", (double)(end - start) / CLOCKS_PER_SEC);
	return sum;
}

/* 不要修改這個函數 */
long long int sum_unrolled(unsigned int vals[NUM_ELEMS]) {
	clock_t start = clock();
	long long int sum = 0;

	for(unsigned int w = 0; w < OUTER_ITERATIONS; w++) { 
		for(unsigned int i = 0; i < NUM_ELEMS / 4 * 4; i += 4) {
			if(vals[i] >= 128) sum += vals[i];
			if(vals[i + 1] >= 128) sum += vals[i + 1];
			if(vals[i + 2] >= 128) sum += vals[i + 2];
			if(vals[i + 3] >= 128) sum += vals[i + 3];
		}

		//This is what we call the TAIL CASE
		//For when NUM_ELEMS isn't a multiple of 4
		//NONTRIVIAL FACT: NUM_ELEMS / 4 * 4 is the largest multiple of 4 less than NUM_ELEMS
		for(unsigned int i = NUM_ELEMS / 4 * 4; i < NUM_ELEMS; i++) {
			if (vals[i] >= 128) {
				sum += vals[i];
			}
		}
	}
	clock_t end = clock();
	printf("Time taken: %f s\n", (double)(end - start) / CLOCKS_PER_SEC);
	return sum;
}

long long int sum_simd(unsigned int vals[NUM_ELEMS]) {
	clock_t start = clock();
	
	//這句代碼會為你生成一個含有若干個127的向量
	//思考題:為什么你需要它?
	__m128i _127 = _mm_set1_epi32(127);		
	
	long long int result = 0;// 將最終計算的結果保存在這里 
	
	//不要修改此行之上的任何代碼!!! 
	
	for(unsigned int w = 0; w < OUTER_ITERATIONS; w++) {
		/* 你的代碼從這里開始 */

		/* 你的代碼在這里結束 */

	}
	clock_t end = clock();
	printf("Time taken: %f s\n", (double)(end - start) / CLOCKS_PER_SEC);
	return result;
}

/* 不要修改這個函數 */
int int_comparator(const void* a, const void* b) {
	if(*(unsigned int*)a == *(unsigned int*)b) return 0;
	else if(*(unsigned int*)a < *(unsigned int*)b) return -1;
	else return 1;
}

#endif

  • 源代碼randomized.cpp
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include "common.h"

/* ***不要修改這個文件!只能修改common.h的內容!*** */

int main(int argc, char* argv[]) {
	printf("Let's generate a randomized array.\n");
	unsigned int vals[NUM_ELEMS];
	long long int reference;
	long long int simd;
	long long int simdu;
	for(unsigned int i = 0; i < NUM_ELEMS; i++) vals[i] = rand() % 256;

	printf("Starting randomized sum.\n");
	printf("Sum: %lld\n", reference = sum(vals));

	printf("Starting randomized unrolled sum.\n");
	printf("Sum: %lld\n", sum_unrolled(vals));

	printf("Starting randomized SIMD sum.\n");
	printf("Sum: %lld\n", simd = sum_simd(vals));
	if (simd != reference) {
		printf("OH NO! SIMD sum %lld doesn't match reference sum %lld!\n", simd, reference);
	}
}

題目要求只修改common.h

我實現的代碼,如下:

#ifndef COMMON_H
#define COMMON_H

#include <x86intrin.h>
#include <stdint.h>
#define NUM_ELEMS ((1 << 15) + 10)
#define OUTER_ITERATIONS (1 << 15)

/* 不要修改這個函數 */
long long int sum(unsigned int vals[NUM_ELEMS]) {
	clock_t start = clock();

	long long int sum = 0;
	for(unsigned int w = 0; w < OUTER_ITERATIONS; w++) {
		for(unsigned int i = 0; i < NUM_ELEMS; i++) {
			if(vals[i] >= 128) {
				sum += vals[i];
			}
		}
	}
	clock_t end = clock();
	printf("Time taken: %f s\n", (double)(end - start) / CLOCKS_PER_SEC);
	return sum;
}

/* 不要修改這個函數 */
long long int sum_unrolled(unsigned int vals[NUM_ELEMS]) {
	clock_t start = clock();
	long long int sum = 0;

	for(unsigned int w = 0; w < OUTER_ITERATIONS; w++) { 
		for(unsigned int i = 0; i < NUM_ELEMS / 4 * 4; i += 4) {
			if(vals[i] >= 128) sum += vals[i];
			if(vals[i + 1] >= 128) sum += vals[i + 1];
			if(vals[i + 2] >= 128) sum += vals[i + 2];
			if(vals[i + 3] >= 128) sum += vals[i + 3];
		}

		//This is what we call the TAIL CASE
		//For when NUM_ELEMS isn't a multiple of 4
		//NONTRIVIAL FACT: NUM_ELEMS / 4 * 4 is the
		// largest multiple of 4 less than NUM_ELEMS
		for(unsigned int i = NUM_ELEMS / 4 * 4; i < NUM_ELEMS; i++) {
			if (vals[i] >= 128) {
				sum += vals[i];
			}
		}
	}
	clock_t end = clock();
	printf("Time taken: %f s\n", (double)(end - start) / CLOCKS_PER_SEC);
	return sum;
}

long long int sum_simd(unsigned int vals[NUM_ELEMS]) {
	clock_t start = clock();
	
	//這句代碼會為你生成一個含有若干個127的向量
	//思考題:為什么你需要它?
	//用來實現比較對應的128, (>127) == (>=128)
	__m128i _127 = _mm_set1_epi32(127);		
	//用來比較數字是否>=128 
	long long int result = 0;// 將最終計算的結果保存在這里 
	
	//不要修改此行之上的任何代碼!!! 
	 
	__m128i p =  _mm_setzero_si128( );//用來保存從數組中讀取的數據
	
	for(unsigned int w = 0; w < OUTER_ITERATIONS; w++) {
		/* 你的代碼從這里開始 */
		__m128i sum = _mm_setzero_si128( );//需要sum的值
		for(unsigned int i = 0; i < NUM_ELEMS / 4 * 4; i += 4){

			__m128i* h = (__m128i*)(vals+i);//地址強制類型轉換
			p = _mm_loadu_si128( h );//從指針中獲取值,獲取128位,即4Byte,恰好是vals[0-3]
			__m128i flag =  _mm_setzero_si128( ); 
		 	flag = _mm_cmpgt_epi32( p , _127 ); //大於127時應該是oxfffff,
		 	//一下子比較四個向量, 4Byte的每一bits都是應該是十進制的-1,而但小於時則會出現ox0000,
		 	//所以就是0, 在flag中的四個部分中分別比較后的結果會變成四個部分保存, 
		 	//所以只存在-1和0的結果.如果都大於,(-1,-1,-1,-1).
		 	
		 	//這樣子的話,-1時是每一bis都是1, 那么我們取大於的時候,
		 	//直接用flag和我們的p來做and與計算, 那么如果大於,直接保留不變,
		 	//小於的話是0,與完后變成0,相當於跳過計算,不影響我們的計算結果.

			__m128i hi = _mm_setzero_si128( );
			hi = _mm_and_si128( p, flag );
			
			sum = _mm_add_epi32(sum, hi);
				 
		}
			//int 一般為32位, 4*32 = 128
			int32_t *k = (int *)&sum;//將_m128i轉換為4個int型的數組 (
			result = result + k[0] + k[1] + k[2] + k[3];//將其全部加入result
		
		for(unsigned int i = NUM_ELEMS / 4 * 4; i < NUM_ELEMS; i++) {
			if (vals[i] >= 128) {
				result += vals[i];
			}
			
		}
		//result *= OUTER_ITERATIONS;
		/* 你的代碼在這里結束 */
	}
	clock_t end = clock();
	printf("Time taken: %f s\n", (double)(end - start) / CLOCKS_PER_SEC);
	return result;
}

/* 不要修改這個函數 */
int int_comparator(const void* a, const void* b) {
	if(*(unsigned int*)a == *(unsigned int*)b) return 0;
	else if(*(unsigned int*)a < *(unsigned int*)b) return -1;
	else return 1;
}

#endif

Nice

在這里插入圖片描述


免責聲明!

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



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