为什么 pthread 会减慢代码速度?

Why pthread slow down the code?

本文关键字:代码 速度 pthread 为什么      更新时间:2023-10-16

我是pthreads的新手,我写这段代码进行测试。我不明白为什么如果我只用 1 个 pthread 运行代码,它比我用多个 pthread 运行时完成得更快。 该代码是用于解析TSP的遗传算法的设置部分。 我有 3 个线性数组(city_x、city_y、city_id(来保存数据:

  • 1 代表 x
  • 1 表示 y
  • 每个城市的 ID 为 1

这些数组类似于线性化,表示总体元素。每个元素都有 x,y 和 id 的NUM_CITIES数据。因此,如果我们有:

  • 人口的3个要素
  • 每个元素 10 NUM_CITIES
  • 每个数组的数据总数为 3*10=30

该代码要求输入总体元素的数量,在city_set数组中设置一些坐标,并使用整个总体中所有元素的坐标 x、y 和 id 创建全局数组。

#include <pthread.h>
#include <limits> // std::numeric_limits<double>
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <utility>
//#include <math.h>
#include <algorithm>    // std::lower_bound, std::find
#include <random>
#include <cmath> 
#include <cstring>
#include <iomanip>      // std::setprecision
#include <vector>       // std::vector
#define NUM_CITIES 10  // This is a tour for the LIN105. It has length 14379.
// #define SIZE_POP 100000000
#define SIZE_MATING 3
#define MUTATION_RATE 0.03
#define STALL_LIMIT 10
// variabili condivise
long size_pop = 0;
long tot_elem = 0;
const int num_threads = 24;
int tid[num_threads];
int start[num_threads];
int stop[num_threads];
// città
int city_set_x[NUM_CITIES];
int city_set_y[NUM_CITIES];
int city_set_id[NUM_CITIES];
// elementi della popolazione
int *city_x;
int *city_y;
int *city_id;
void *setup(void *p) {
int id = *(int *)p;
// std::cout << "id: " << id << "n";
int s = start[id];
int perm[NUM_CITIES];
for(int i = 0; i < NUM_CITIES; ++i) {
perm[i] = i;
// std::cout << perm[i] << ",";
}
for(long i = start[id]; i < stop[id]; i += NUM_CITIES) {
std::random_shuffle ( perm, perm + NUM_CITIES );
for(int j = 0; j < NUM_CITIES; ++j) {
city_id[i + j] =  perm[j];
city_x[i + j] =  city_set_x[perm[j]];
city_y[i + j] =  city_set_y[perm[j]];
// std::cout << "(" << city_x[i + j] << "," << city_y[i + j] << ") ";
}
// std::cout << "n";
}
}

static inline const double diffmsec(const struct timeval & a, 
const struct timeval & b) {
long sec  = (a.tv_sec  - b.tv_sec);
long usec = (a.tv_usec - b.tv_usec);
if(usec < 0) {
--sec;
usec += 1000000;
}
return ((double)(sec*1000)+ (double)usec/1000.0);
}
int main(int argc, char *argv[]) {
size_pop = atol(argv[1]);
std::cout << size_pop << "n";
tot_elem = NUM_CITIES * size_pop;
std::cout << "tot_elem: " << tot_elem << "n";
struct timeval program_start, program_end, setup_start, setup_end;
std::vector<double> v_set;
city_x = (int *)malloc(tot_elem * sizeof(int));
// memset(city_x, -1, tot_elem * sizeof(int));
city_y = (int *)malloc(tot_elem * sizeof(int));
// memset(city_y, -1, tot_elem * sizeof(int));
city_id = (int *)malloc(tot_elem * sizeof(int));
for(int i = 0; i < tot_elem; ++i) {
city_x[i] = -1;
city_y[i] = -1;
city_id[i] = -1;
}
srand(time(NULL));
int x[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int y[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// stampa
std::cout << "[CITTA.X]n";
for(int i = 0; i < NUM_CITIES; ++i) {
city_set_x[i] = x[i];
// city_set[i].x = i + 1;
std::cout << city_set_x[i] << " ";
}
std::cout << "n";
std::cout << "[CITTA.Y]n";
for(int i = 0; i < NUM_CITIES; ++i) {
city_set_y[i] = y[i];
// city_set[i].y = i + 1;
std::cout << city_set_y[i] << " ";
}
std::cout << "n";
std::cout << "[CITTA.ID]n";
for(int i = 0; i < NUM_CITIES; ++i) {
city_set_id[i] = i;
std::cout << city_set_id[i] << " ";
}
std::cout << "n";
// std::cin.get() != 'n';
pthread_t threads[num_threads];
for(int i = 0; i < num_threads; ++i) {
tid[i] = i;
start[i] = i * NUM_CITIES * floor(size_pop/num_threads);
// std::cout << "start: " << start << "n";
if(i != num_threads - 1) {
stop[i] = start[i] + (floor(size_pop/num_threads) * NUM_CITIES);
// std::cout << "stop: " << stop << "n";
}
else {
stop[i] = tot_elem;
// std::cout << "stop: " << stop << "n";
}
// std::cout << "n";
}
for(int c = 0; c < 10; c++) {
gettimeofday(&setup_start, NULL);
for(int i = 0; i < num_threads; ++i) {
if( pthread_create( &threads[i], NULL, &setup, (void *) &tid[i]) )
{
printf("Thread creation failedn");
}
}
for(int i = 0; i < num_threads; ++i) {
pthread_join( threads[i], NULL);
}
gettimeofday(&setup_end, NULL);
v_set.push_back(diffmsec(setup_end, setup_start) / 1000);
}
// // stampa
// std::cout << "[SETUP]n";
// for(int i = 0; i < size_pop; ++i){
//  long idx = i * NUM_CITIES;
//  std::cout << "pop[" << i << "]: ";
//  for(int j = 0; j < NUM_CITIES; ++j){
//      std::cout << "(" << city_x[idx + j] << "," << city_y[idx + j] << ") ";
//  }
//  std::cout << "n";
// }
double sum = 0;
double mean;

sum = 0;
for (int i = 0; i < v_set.size(); ++i) {
sum += v_set[i];
}
mean = sum / v_set.size();
std::cout << "[SET]: " << mean << " sn";
free(city_x);
free(city_y);
free(city_id);
}

我用1000000 个 elemets运行代码,将线程数设置为 1,结果是 0.332 秒。 运行1000000 个 elemets但将线程数设置为 4 后,结果为 1.361 秒。 如果我将数字增加到 24,结果是 0.60 秒,但实际上是连续的两倍! 当我超过24个线程数时,结果保持不变或再次增加。

编辑

使用:grep -c 处理器/proc/cpuinfo

我得到56。

使用:cat/proc/cpuinfo

处理器 : 0

vendor_id : 正版英特尔

中央处理器系列 : 6

型号 : 79

型号名称 : 英特尔® 至强® CPU E5-2680 v4 @ 2.40GHz 步进 : 1

微码 : 0xb00001e

中央处理器兆赫 : 1967.906

缓存大小 : 35840 KB

物理 ID : 0

兄弟姐妹 : 28

核心 ID : 0

中央处理器内核 : 14

顶点 : 0

初始条件 : 0

FPU : 是

fpu_exception : 是

中央处理器级别 : 20

WP : 是

的flags : FPU VME de PSE TSC MSR PAE MCE CX8 apic SEP MTRR PGE MCA CMOV PAT PSE36 clflush DTS ACPI MMX FXSR SSE SSE2 SS HT TM PBE SYSCALL NX PDPE1GB rdTSCP LM constant_tsc arch_perfmon PEBS BTS rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timerAES xsave AVX F16C RDRAND lahf_lm ABM 3dnowprefetch ARAT EPB PLN PTS dtherm intel_pt tpr_shadow VNI Flexpriority ept VPID FSGSbase tsc_adjust BMI1 HLE AVX2 SMEP BMI2 ERMS invpcid RTM CQM RDSEED ADX SMAP xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local

波戈米普斯 : 4799.62

小号:64

cache_alignment : 64

地址大小:46位物理,48位虚拟

对于 56 个处理器中的每一个。

std::random_shuffle使用共享资源,所有线程都使用它,因此您的程序具有很高的争用,线程大多在等待彼此。为每个线程使用单独的随机生成器(例如,std::mt19937withstd::shuffle,请查看 cpp首选项(。

此外,您可能希望增加NUM_CITIES,因此每个线程使用单独的缓存行。

使用各种线程运行代码需要系统在每个线程之间进行上下文切换,这意味着您有计算开销,而实际上却没有从中获得任何好处。此外,您需要一个循环来计算线程参数,该线程参数的计算量越大,生成的线程越多,但这可能是引入的延迟最少的,因为它不需要大量计算。

另请注意,线程可能在单个物理内核上运行,请检查程序运行时资源的使用情况。如果程序仅在单个内核上运行,那么您实际上并没有使用具有多个内核中引入的硬件加速。

最后,由于这是C++我建议使用本机std::thread。

最后,我认为这种延迟主要是由于线程之间的上下文切换以及线程可能在单个内核上运行的事实。尝试检查在多个物理内核上运行程序的可能性,并检查需要多少时间。