1. 实验内容与方法
- 初始化数组。数组中的值使用c++11中的random类随机生成0到2000之间的double值,将数组中的值存入到文件中,供后续调用使用以保证并行和串行进行快速排序时的数组是相同的。
- 程序计时。使用c++11中的std::chrono库,使用system_clock表示当前的系统时钟,系统中运行的所有进程使用now()得到的时间是一致的。
- 串行执行快速排序。不使用c++中的sort函数,手动实现快速排序。
- 并行执行快速排序。将数组分成n段,使用n个线程并行对每段进行快速排序处理。需要使用pthread_barrier函数保证所有线程都将分段排好序之后,进行类似归并排序。因为可能存在数组分段后长度不同的问题,自定义了一个类用来保存分段的左右端点位置。
- 数组大小和数组中的值不变,修改线程数。分别设置线程数为2、3、4、8个,统计运行时间。
- 线程数不变,修改数组大小。将数组大小分别修改为5000万和1亿,分别运行pthread统计运行时间。
2. 实验过程
实验设备CPU(i7-7700 3.6GHz, 4核),内存16G,操作系统Ubuntu 18.04,IDE CLion。
2.1 运行时间
经过代码运行计时,得到以下的实验结果表格。
运行时间/秒 | 2000万 | 5000万 | 1亿 |
1核 | 6.72747 | 25.1391 | 76.2641 |
2核 | 3.36834 | 10.3938 | 27.2989 |
3核 | 2.39181 | 7.15399 | 17.0872 |
4核 | 2.01613 | 5.69332 | 13.0158 |
8核 | 2.09013 | 5.49922 | 11.2955 |
2.2 加速比
加速比 | 2000万 | 5000万 | 1亿 |
1核 | 1 | 1 | 1 |
2核 | 1.99726572 | 2.41866305 | 2.79366934 |
3核 | 2.81271088 | 3.51399708 | 4.46322979 |
4核 | 3.33682352 | 4.41554313 | 5.85934787 |
8核 | 3.21868496 | 4.57139376 | 6.75172414 |
2.3 图表
绘制成图表,如下图所示。
3 实验分析
从实验结果和加速比可以看出:
1 随着数据量的增大,快排所需的时间也相应增加;
2 在4核并行内,快排运行的加速比与运行的核心近似成正比;虽然系统显示为8核,但只有4个真正的核。所以当并行线程数大于4后,快排提升的效果并不明显,因为要进行线程的切换。
4 源代码
initVector.cpp
#include "iostream"
#include "fstream"
#include "random"
#include "vector"
#include "string"
#include "sstream"
#include "cstdlib"
using namespace std;
const int size = 20000000;
string path = "/home/dadan/Downloads/vector.txt";
void writeAndInitVector(int size) {
vector<double> res(size, 0.0);
default_random_engine engine;
uniform_real_distribution<double> u(0.0, 20000);
for (int i = 0; i < size; ++i) {
res[i] = u(engine);
}
ofstream outfile(path);
for (int i = 0; i < size; ++i) {
outfile << res[i] << endl;
}
outfile.close();
}
void loadVector(int size) {
vector<double> res(size, 0);
ifstream file(path);
string line;
for (int i = 0; i < size; ++i) {
getline(file, line);
res[i] = stod(line);
}
file.close();
}
int main() {
writeAndInitVector(size);
//loadVector(size);
return 0;
}
quickSort.cpp
#include <iostream>
#include "pthread.h"
#include "vector"
#include "fstream"
#include "cstring"
#include "climits"
#include "chrono"
#include "ctime"
using namespace std;
using namespace std::chrono;
const long SIZE = 20000000; //the size of vector that need to be sorted
const int THREADS_NUM = 3; // the number of threads
long sortNumPerThread = SIZE / THREADS_NUM; // the number of vector that every thread needs to sort
string path = "/home/dadan/Downloads/vector.txt"; // file path of vector
vector<double> vec(SIZE), sortedVec(SIZE);
pthread_barrier_t barrier;
class Section{
public:
int left;
int right;
Section(){
};
};
void loadVector();
void quickSort(int left, int right);
void *pthread_sort(void *arg);
void merge();
void serial_quick_sort();
int main() {
//serial_quick_sort();
loadVector();
pthread_t tid;
pthread_barrier_init(&barrier, NULL, THREADS_NUM + 1);
auto start = system_clock::now();
for (int i = 0; i < THREADS_NUM; ++i) {
Section* section = new Section;
section->left = i * sortNumPerThread;
if (i == THREADS_NUM - 1) {
section->right = SIZE - 1;
} else {
section->right = (i + 1) * sortNumPerThread - 1;
}
int status = pthread_create(&tid, NULL, pthread_sort, (void *) (section));
if (status != 0) {
cout << "create thread error" << endl;
return -1;
}
}
//main thread wait
pthread_barrier_wait(&barrier);
//merge results of per thread
merge();
auto end = system_clock::now();
auto duration = duration_cast<microseconds>(end - start);
cout << THREADS_NUM << " threads pthread-parallel quick sort takes "
<< double(duration.count()) * microseconds::period::num / microseconds::period::den << " seconds" << endl;
return 0;
}
void loadVector() {
ifstream read_file(path);
string line;
for (int i = 0; i < SIZE; ++i) {
getline(read_file, line);
vec[i] = stod(line);
}
read_file.close();
}
void quickSort(int start, int end) {
if (start >= end)
return;
double base = vec[end];
int left = start, right = end - 1;
while (true) {
while (vec[left] < base)
++left;
while (vec[right] >= base)
--right;
if (left >= right)
break;
swap(vec[left], vec[right]);
}
if (vec[left] >= vec[end])
swap(vec[left], vec[end]);
else
++left;
quickSort(start, left - 1);
quickSort(left + 1, end);
}
void merge() {
vector<int> index(THREADS_NUM), index_most(THREADS_NUM);
for (int i = 0; i < THREADS_NUM; ++i) {
index[i] = i * sortNumPerThread;
if (i == THREADS_NUM - 1) {
index_most[i] = SIZE;
} else {
index_most[i] = (i + 1) * sortNumPerThread;
}
}
for (int i = 0; i < SIZE; ++i) {
int min_index;
double min_num = INT_MAX;
for (int j = 0; j < THREADS_NUM; ++j) {
if ((index[j] < (j + 1) * sortNumPerThread) && (vec[index[j]] < min_num)) {
min_num = vec[index[j]];
min_index = j;
}
}
sortedVec[i] = vec[index[min_index]];
index[min_index]++;
}
}
void serial_quick_sort() {
loadVector();
auto start = system_clock::now();
quickSort(0, SIZE - 1);
auto end = system_clock::now();
auto duration = duration_cast<microseconds>(end - start);
cout << "serial quick sort takes "
<< double(duration.count()) * microseconds::period::num / microseconds::period::den << " seconds" << endl;
}
void *pthread_sort(void *arg) {
Section* section = (Section*)arg;
quickSort(section->left, section->right);
//wait other threads
pthread_barrier_wait(&barrier);
}