学习多线程C++:添加线程不会使执行速度更快,即使它看起来应该
Learning to multithread in C++: Adding threads doesn't make execution faster even though it seems like it should
长话短说,我遇到了Monty Hall问题,我有兴趣把一些东西放在一起,这样我就可以通过计算来测试它了。这很好,但在这个过程中,我对C++中的多线程应用程序感到好奇。我是一名计算机科学专业的学生,但我只用不同的语言简要介绍了这个话题。我想看看我是否可以利用一些额外的CPU内核,使蒙特霍尔模拟更快。
我似乎让它发挥了作用,但遗憾的是,它实际上并没有任何性能提升。该程序对一个简单的函数执行大量迭代,基本上可以归结为几个rand_r((调用和几个比较。我希望它是一个可以在线程之间分割的琐碎例子,基本上只是让每个线程处理总迭代的相等部分。
我只是想理解这一点,我想知道我是否犯了错误,或者后台是否有什么事情在多线程执行,即使我只在代码中指定了一个线程。
不管怎样,看看并分享你的想法。也请记住,我做这只是一次学习经历,最初没有计划让其他人阅读:D
#include <cstdlib>
#include <climits>
#include <ctime>
#include <iostream>
#include <thread>
#include <chrono>
enum strategy {STAY = 0, SWITCH = 1};
unsigned ITERATIONS = 1;
unsigned THREADS = 5;
struct counts
{
unsigned stay_correct_c;
unsigned switch_correct_c;
};
void simulate (struct counts&, unsigned&);
bool game (enum strategy, unsigned&);
int main (int argc, char **argv)
{
if (argc < 2)
std::cout << "Usage: " << argv[0] << " -i [t|s|m|l|x] -t [1|2|4|5|10]n", exit(1);
if (argv[1][1] == 'i') {
switch (argv[2][0]) {
case 's':
ITERATIONS = 1000;
break;
case 'm':
ITERATIONS = 100000;
break;
case 'l':
ITERATIONS = 10000000;
break;
case 'x':
ITERATIONS = 1000000000;
break;
default:
std::cerr << "Invalid argument.n", exit(1);
}
}
if (argv[3][1] == 't') {
switch (argv[4][0])
{
case '1':
if (argv[4][1] != '0')
THREADS = 1;
else if (argv[4][1] == '0')
THREADS = 10;
break;
case '2':
THREADS = 2;
break;
case '4':
THREADS = 4;
break;
case '5':
THREADS = 5;
break;
}
}
srand(time(NULL));
auto start = std::chrono::high_resolution_clock::now();
struct counts total_counts;
total_counts.stay_correct_c = 0;
total_counts.switch_correct_c = 0;
struct counts per_thread_count[THREADS];
std::thread* threads[THREADS];
unsigned seeds[THREADS];
for (unsigned i = 0; i < THREADS; ++i) {
seeds[i] = rand() % UINT_MAX;
threads[i] = new std::thread (simulate, std::ref(per_thread_count[i]), std::ref(seeds[i]));
}
for (unsigned i = 0; i < THREADS; ++i) {
std::cout << "Waiting for thread " << i << " to finish...n";
threads[i]->join();
}
for (unsigned i = 0; i < THREADS; ++i) {
total_counts.stay_correct_c += per_thread_count[i].stay_correct_c;
total_counts.switch_correct_c += per_thread_count[i].switch_correct_c;
}
auto stop = std::chrono::high_resolution_clock::now();
std::cout <<
"The simulation performed " << ITERATIONS <<
" iterations on " << THREADS << " threads of both the stay and switch strategies " <<
"taking " << std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count() <<
" ms." << std::endl <<
"Score:" << std::endl <<
" Stay Strategy: " << total_counts.stay_correct_c << std::endl <<
" Switch Strategy: " << total_counts.switch_correct_c << std::endl << std::endl <<
"Ratios:" << std::endl <<
" Stay Strategy: " << (double)total_counts.stay_correct_c / (double)ITERATIONS << std::endl <<
" Switch Strategy: " << (double)total_counts.switch_correct_c / (double)ITERATIONS << std::endl << std::endl;
}
void simulate (struct counts& c, unsigned& seed)
{
c.stay_correct_c = 0;
c.switch_correct_c = 0;
for (unsigned i = 0; i < (ITERATIONS / THREADS); ++i) {
if (game (STAY, seed))
++c.stay_correct_c;
if (game (SWITCH, seed))
++c.switch_correct_c;
}
}
bool game (enum strategy player_strat, unsigned& seed)
{
unsigned correct_door = rand_r(&seed) % 3;
unsigned player_choice = rand_r(&seed) % 3;
unsigned elim_door;
do {
elim_door = rand_r(&seed) % 3;
}
while ((elim_door != correct_door) && (elim_door != player_choice));
seed = rand_r(&seed);
if (player_strat == SWITCH) {
do
player_choice = (player_choice + 1) % 3;
while (player_choice != elim_door);
return correct_door == player_choice;
}
else
return correct_door == player_choice;
}
编辑:下面将添加一些关于一些可靠评论建议的补充信息。
我在6核/12线程AMD Ryzen r5 1600上运行。Htop显示了您期望从命令行参数中获得的高利用率逻辑核的数量。PID的数量与指定的线程数量加一相同,利用率为~=100%的逻辑内核的数量与在每种情况下指定的线程的数量相同。
就数字而言,以下是我在大量迭代中使用l标志收集的一些数据:
CORES AVG MIN MAX
1 102541 102503 102613
4 90183 86770 96248
10 72119 63581 91438
对于像这个程序这样简单的划分,我本希望在添加线程时看到总时间的线性减少,但我显然错过了一些东西。我的想法是,如果一个线程可以在y时间内执行x次模拟,那么该线程应该能够在y/4时间内执行x/4次模拟。我误解了什么?
编辑2:我应该补充一下,由于上面的代码存在,不同线程的时间差异不太明显,但我做了一些小的优化,使delta变大了一点。
感谢您发布代码;它没有在我的机器上编译(Apple LLVM版本9.0.0(clang-900.0.39.2((。喜欢标准。
我把它黑进了C版本,你的问题似乎是虚假分享;也就是说,每个线程都会大量命中其"种子"条目,但由于内存缓存将相邻位置聚合为"行",因此cpu会花费所有时间来回复制这些行。如果你把"种子"的定义改为
struct myseed {
unsigned seed;
unsigned dont_share_me[15];
};
您应该看到您期望的可伸缩性。您可能希望对结构计数执行同样的操作。通常,malloc会为您进行这种调整,所以如果您将"每个线程"上下文标记到一个包中并对其进行malloc,它会返回正确对齐的缓存位置。
- std::vector的包装器,使数组的结构看起来像结构的数组
- 看起来is_nothrow_constructible_v()在MSVC中被破坏了,我错了吗
- 学习多线程C++:添加线程不会使执行速度更快,即使它看起来应该
- 尽管一切看起来都很好,但值不会交换
- 自制的上衣:看起来一样,但不完全相同
- 如何使它看起来像正在下的雪
- 如何用逗号分隔输出?如何改进此代码以使其看起来更体面?
- 我的代码看起来不错,但某些输入会导致不需要的输出
- 为什么这个结构的大小是 40,而它看起来应该是 12/24?
- 我需要将阵列样式的邻接矩阵转换为矢量样式(以使其看起来更好)
- 结构化绑定:当某些内容看起来像引用并且行为类似于引用,但它不是引用时
- 如何使QTextEdit看起来被禁用
- 在正确性或良好的代码结构方面,这种动态对象创建看起来如何
- 看起来如此主要的错误.cpp:(.text.startup+0xd6):未定义对"vtable for Counter"的引用?
- C 时间测量看起来太慢了
- 我应该如何使 std::filesystem 看起来符合 Visual Studio 2015 的标准
- 写入.ini文件 - SimpleIni SetValue 尽管看起来成功了,但什么也没做
- std::string 上的 substr 无法正常工作,因为存在一些不可见但看起来像空格的字符
- 使标题看起来像它们在不同的文件夹中
- 我如何编写看起来像方法的lambda表达式