我正在编写一个程序来执行一些长时间的计算,我可以根据需要将其分成任意多个任务。为了便于讨论,让我们假设我正在编写一个算法,通过尝试将它除以 2 到 p-1 之间的所有数字来确定数字 p 是否为素数。这个任务显然可以分解成很多线程。
我实际上编写了一个示例应用程序来执行此操作。作为参数,我给出了我要检查的数字,以及要使用的线程数(每个线程都被赋予了一个相等大小的数字范围,以尝试将 p 除以 - 它们一起覆盖了整个范围)。
我的机器有 8 个核心。我开始使用我知道是质数 (2971215073) 的大量程序运行该程序,并使用 1、2、3 个线程等,直到达到 8 个线程——每次程序运行速度都比前一个快,这正是我所期望的。然而,当我尝试大于 8 的数字时,计算时间实际上越来越短(即使是一点点)!
我的线程中没有 I/O 或类似的东西,只有纯 CPU 计算。我预计当我通过 8 个线程时运行时间会变得更糟,因为会有更多的上下文切换并且并行运行的线程数保持在 8。很难说峰值在哪里,因为差异很小并且变化从一个运行到另一个,但是很明显,即 50 个线程以某种方式运行得比 8 个线程快(约 300 毫秒)...
我的猜测是,因为我有这么多线程,我得到更多的运行时间,因为我在系统的线程池中有更大的部分,所以我的线程被选择得更多。但是,我创建的线程越多,程序运行得越快,这似乎没有意义(否则为什么不每个人都创建1000个线程??)。
任何人都可以提供一个解释,也许还有一个关于相对于机器上的核心数量创建多少线程的最佳实践?
谢谢。
我的代码供感兴趣的人使用(在 Windows、VS2012 上编译):
#include <Windows.h>
#include <conio.h>
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
typedef struct
{
unsigned int primeCandidate;
unsigned int rangeStart;
unsigned int rangeEnd;
} param_t;
DWORD WINAPI isDivisible(LPVOID p)
{
param_t* param = reinterpret_cast<param_t*>(p);
for (unsigned int d = param->rangeStart; d < param->rangeEnd; ++d)
{
if (param->primeCandidate % d == 0)
{
cout << param->primeCandidate << " is divisible by " << d << endl;
return 1;
}
}
return 0;
}
bool isPrime(unsigned int primeCandidate, unsigned int numOfCores)
{
vector<HANDLE> handles(numOfCores);
vector<param_t> params(numOfCores);
for (unsigned int i = 0; i < numOfCores; ++i)
{
params[i].primeCandidate = primeCandidate;
params[i].rangeStart = (primeCandidate - 2) * (static_cast<double>(i) / numOfCores) + 2;
params[i].rangeEnd = (primeCandidate - 2) * (static_cast<double>(i+1) / numOfCores) + 2;
HANDLE h = CreateThread(nullptr, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(isDivisible), ¶ms[i], 0, 0);
if (NULL == h)
{
cout << "ERROR creating thread: " << GetLastError() << endl;
throw exception();
}
handles[i] = h;
}
DWORD ret = WaitForMultipleObjects(numOfCores, &handles[0], TRUE, INFINITE);
if (ret >= WAIT_OBJECT_0 && ret <= WAIT_OBJECT_0 + numOfCores - 1)
{
for (unsigned int i = 0; i < numOfCores; ++i)
{
DWORD exitCode = -1;
if (0 == GetExitCodeThread(handles[i], &exitCode))
{
cout << "Failed to get thread's exit code: " << GetLastError() << endl;
throw exception();
}
if (1 == exitCode)
{
return false;
}
}
return true;
}
else
{
cout << "ERROR waiting on threads: " << ret << endl;
throw exception();
}
}
int main()
{
unsigned int primeCandidate = 1;
unsigned int numOfCores = 1;
cout << "Enter prime candidate: ";
cin >> primeCandidate;
cout << "Enter # of cores (0 means all): ";
cin >> numOfCores;
while (primeCandidate > 0)
{
if (0 == numOfCores) numOfCores = thread::hardware_concurrency();
DWORD start = GetTickCount();
bool res = isPrime(primeCandidate, numOfCores);
DWORD end = GetTickCount();
cout << "Time: " << end-start << endl;
cout << primeCandidate << " is " << (res ? "" : "not ") << "prime!" << endl;
cout << "Enter prime candidate: ";
cin >> primeCandidate;
cout << "Enter # of cores (0 means all): ";
cin >> numOfCores;
}
return 0;
}
最佳答案
是的。这是我在 i7/Vista 64 机器上所做的一些测试的一小部分(4 个“真实”内核 + 超线程):
8 tests,
400 tasks,
counting to 10000000,
using 8 threads:
Ticks: 2199
Ticks: 2184
Ticks: 2215
Ticks: 2153
Ticks: 2200
Ticks: 2215
Ticks: 2200
Ticks: 2230
Average: 2199 ms
8 tests,
400 tasks,
counting to 10000000,
using 32 threads:
Ticks: 2137
Ticks: 2121
Ticks: 2153
Ticks: 2138
Ticks: 2137
Ticks: 2121
Ticks: 2153
Ticks: 2137
Average: 2137 ms
.. 表明,就像在您的测试中一样,线程的“超额订阅”确实会导致整体执行时间略微提高 2-3%。我的测试将简单的“计算整数”CPU 密集型任务提交给具有不同线程数的线程池。
我当时的结论是,微小的改进是因为更多的线程占用了我机器上“基本负载”的更大百分比——1000 个线程中的少数线程占负载的 1-4%-几乎总是空闲的 Firefox、uTorrent、Word、任务栏等中的奇怪线程在测试期间碰巧运行了一点。
在我的测试中,使用 64 个线程而不是 8 个线程的“上下文切换开销”似乎可以忽略不计。
这仅适用于任务使用的数据非常小的情况。后来我重复了一批类似的测试,其中任务使用 8K 数组——L1 缓存的大小。在这种“最坏情况”的情况下,使用比核心更多的线程会导致非常明显的减速,直到在 16 个及以上线程时,性能下降了 40%,因为线程交换了整个缓存。在大约 20 个线程以上,速度并没有变得更糟,因为无论有多少线程运行任务,缓存仍然以相同的速率从每个内核换出。
另请注意,我有足够的 RAM,因此页面错误很少。
关于c++ - 执行计算时 - 我应该打开多少个线程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17123795/
我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass
使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta
为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar
我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server
这里是Ruby新手。完成一些练习后碰壁了。练习:计算一系列成绩的字母等级创建一个方法get_grade来接受测试分数数组。数组中的每个分数应介于0和100之间,其中100是最大分数。计算平均分并将字母等级作为字符串返回,即“A”、“B”、“C”、“D”、“E”或“F”。我一直返回错误:avg.rb:1:syntaxerror,unexpectedtLBRACK,expecting')'defget_grade([100,90,80])^avg.rb:1:syntaxerror,unexpected')',expecting$end这是我目前所拥有的。我想坚持使用下面的方法或.join,
我遵循了教程http://gettingstartedwithchef.com/,第1章。我的运行list是"run_list":["recipe[apt]","recipe[phpap]"]我的phpapRecipe默认Recipeinclude_recipe"apache2"include_recipe"build-essential"include_recipe"openssl"include_recipe"mysql::client"include_recipe"mysql::server"include_recipe"php"include_recipe"php::modul
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
我在用Ruby执行简单任务时遇到了一件奇怪的事情。我只想用每个方法迭代字母表,但迭代在执行中先进行:alfawit=("a".."z")puts"That'sanalphabet:\n\n#{alfawit.each{|litera|putslitera}}"这段代码的结果是:(缩写)abc⋮xyzThat'sanalphabet:a..z知道为什么它会这样工作或者我做错了什么吗?提前致谢。 最佳答案 因为您的each调用被插入到在固定字符串之前执行的字符串文字中。此外,each返回一个Enumerable,实际上您甚至打印它。试试
当谈到运行时自省(introspection)和动态代码生成时,我认为ruby没有任何竞争对手,可能除了一些lisp方言。前几天,我正在做一些代码练习来探索ruby的动态功能,我开始想知道如何向现有对象添加方法。以下是我能想到的3种方法:obj=Object.new#addamethoddirectlydefobj.new_method...end#addamethodindirectlywiththesingletonclassclass这只是冰山一角,因为我还没有探索instance_eval、module_eval和define_method的各种组合。是否有在线/离线资
我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我