将多线程提供任何能提高?
-
12-09-2019 - |
题
我是新编程,一般所以请谨记在心,当你回答我的问题。
我有一个程序,需要大3D阵列(1亿元)和总结要素沿着各种轴线产生2D阵列的一个投射的每一侧的数据。这里的问题是,这是非常ram密集型的程序是不断获取信息,从ram、读和写。
问题是,我获得任何的性能增加,如果我多线程的程序或会我跑进一个RAM访问瓶颈?当我说多线程,我的意思是多线程2-4核心,没有更多。
如果有帮助,我目前的计算机配置是2.4千兆赫酷睿2四,1033fsb,4gb ram在667mhz.
在此先感谢,
-Faken
编辑:
在我看来,这里的人都更感兴趣在这个问题,我已经第一预期。我会扩大问题并且发布一些代码为那些有兴趣。
首先,一个小小的背景在我所以你明白我从哪里来的.我是个机械工程研究生谁一些如何设法选择一个主题,几乎什么都没有做机械工程。我已1课程中介绍性java(强制)的大约5年前从来没有碰过程,直到大约一个月前,当我开始了我的论文认真。我们还采取(再次被迫的,仍然不知道为什么)课程的电子和计算机工程,我们处理微控制器(8-bit),他们的内部运作,以及一些个体和小型的编码。除此之外,我知道接下来什么节目。
这里是代码:
int dim = 1000;
int steps = 7 //ranges from 1 to 255
for (int stage = 1; stage < steps; stage++)
for (int j = 0; j < dim; j++)
for (int i = 0; i < dim; i++)
{
sum = 0;
for (int k = 0; k < dim; k++)
if (partMap[(((i * dim) + k) * dim) + j] >= stage)
sum++;
projection[(j*dim) + i] = sum;
}
这一部分代码的工作在z轴只。主要的数据,由于它的方式构成的,有一个奇怪的解决系统,但你不需要担心。还有其它的代码这样做的突起的其他方面的立方体,但他们这样做非常不同的事情。
解决方案
有只有一个优化代码的方法:找出你在做什么这是缓慢的,并且少做吧。 “做的要少”的特殊情况是做别的事情而不是更快。
所以首先,这里是根据你贴的代码我在做什么:
#include <fstream>
#include <sstream>
using std::ios_base;
template<typename Iterator, typename Value>
void iota(Iterator start, Iterator end, Value val) {
while (start != end) {
*(start++) = val++;
}
}
int main() {
const int dim = 1000;
const int cubesize = dim*dim*dim;
const int squaresize = dim*dim;
const int steps = 7; //ranges from 1 to 255
typedef unsigned char uchar;
uchar *partMap = new uchar[cubesize];
// dummy data. I timed this separately and it takes about
// a second, so I won't worry about its effect on overall timings.
iota(partMap, partMap + cubesize, uchar(7));
uchar *projection = new uchar[squaresize];
for (int stage = 1; stage < steps; stage++) {
for (int j = 0; j < dim; j++) {
for (int i = 0; i < dim; i++)
{
int sum = 0;
for (int k = 0; k < dim; k++)
if (partMap[(((i * dim) + k) * dim) + j] >= stage)
sum++;
projection[(j*dim) + i] = sum;
}
}
std::stringstream filename;
filename << "results" << stage << ".bin";
std::ofstream file(filename.str().c_str(),
ios_base::out | ios_base::binary | ios_base::trunc);
file.write((char *)projection, squaresize);
}
delete[] projection;
delete[] partMap;
}
(编辑:只注意到“投影”应该是一个int数组,不UCHAR我的坏这将有所作为的一些定时,但是希望不是太大之一。)
然后我复制result*.bin
到gold*.bin
,所以我可以检查我的未来变化如下:
$ make big -B CPPFLAGS="-O3 -pedantic -Wall" && time ./big; for n in 1 2 3 4 5
6; do diff -q results$n.bin gold$n.bin; done
g++ -O3 -pedantic -Wall big.cpp -o big
real 1m41.978s
user 1m39.450s
sys 0m0.451s
行,因此在时刻100秒。
因此,推测它是通过跨越数十亿项数据阵列的速度慢,让我们尝试仅仅通过一次去,而不是每一次阶段:
uchar *projections[steps];
for (int stage = 1; stage < steps; stage++) {
projections[stage] = new uchar[squaresize];
}
for (int j = 0; j < dim; j++) {
for (int i = 0; i < dim; i++)
{
int counts[256] = {0};
for (int k = 0; k < dim; k++)
counts[partMap[(((i * dim) + k) * dim) + j]]++;
int sum = 0;
for (int idx = 255; idx >= steps; --idx) {
sum += counts[idx];
}
for (int stage = steps-1; stage > 0; --stage) {
sum += counts[stage];
projections[stage][(j*dim) + i] = sum;
}
}
}
for (int stage = 1; stage < steps; stage++) {
std::stringstream filename;
filename << "results" << stage << ".bin";
std::ofstream file(filename.str().c_str(),
ios_base::out | ios_base::binary | ios_base::trunc);
file.write((char *)projections[stage], squaresize);
}
for (int stage = 1; stage < steps; stage++) delete[] projections[stage];
delete[] partMap;
这是一个快一点:
$ make big -B CPPFLAGS="-O3 -pedantic -Wall" && time ./big; for n in 1 2 3 4 5
6; do diff -q results$n.bin gold$n.bin; done
g++ -O3 -pedantic -Wall big.cpp -o big
real 1m15.176s
user 1m13.772s
sys 0m0.841s
现在,steps
在这个例子中相当小的,所以我们做了很多不必要的工作与“罪状”阵列。以计数到1000(沿着我们的列运行)相比,甚至没有谱,我猜数到256两次(一次以清除阵列,一旦来概括)是相当显著。因此,让我们改变:
for (int j = 0; j < dim; j++) {
for (int i = 0; i < dim; i++)
{
// steps+1, not steps. I got this wrong the first time,
// which at least proved that my diffs work as a check
// of the answer...
int counts[steps+1] = {0};
for (int k = 0; k < dim; k++) {
uchar val = partMap[(((i * dim) + k) * dim) + j];
if (val >= steps)
counts[steps]++;
else counts[val]++;
}
int sum = counts[steps];
for (int stage = steps-1; stage > 0; --stage) {
sum += counts[stage];
projections[stage][(j*dim) + i] = sum;
}
}
}
现在,我们确实需要我们只使用尽可能多桶。
$ make big -B CPPFLAGS="-O3 -pedantic -Wall" && time ./big; for n in 1 2 3 4 5
6; do diff -q results$n.bin gold$n.bin; done
g++ -O3 -pedantic -Wall big.cpp -o big
real 0m27.643s
user 0m26.551s
sys 0m0.483s
乌拉。该代码是作为第一版本近4倍的速度,并产生相同的结果。所有我做的是改变什么顺序数学做是:我们还没看多线程或预取呢。我还没有尝试任何技术性很强的闭环优化,只需将它留给编译器。因此,这也算是一个不错的开始。
但它仍然以数量级的时间比这丝毫在运行1秒。因此,有可能大幅上涨仍然找到。一个主要的区别是,丝毫运行的不跳过所有的地方按顺序一维数组了。正如我在第一次回答说,你的目标应该是在立方体始终使用顺序。
所以,让我们做一个单行的变化,切换i和j的循环:
for (int i = 0; i < dim; i++)
for (int j = 0; j < dim; j++) {
这仍然是不连续的顺序,但它确实意味着我们在同一时间集中于我们的立方体一百万字节的片。现代的CPU至少有4MB缓存,所以有一点点运气,我们只会在整个程序中打主内存立方体的任何部分一次。有了更好的地方,我们可以减少进出流量L1缓存,过,但主内存是最慢的。
多大区别呢?
$ make big -B CPPFLAGS="-O3 -pedantic -Wall" && time ./big; for n in 1 2 3 4 5
6; do diff -q results$n.bin gold$n.bin; done
g++ -O3 -pedantic -Wall big.cpp -o big
real 0m8.221s
user 0m4.507s
sys 0m0.514s
不坏。事实上,这种变化带来了独自从100秒到20秒的原代码。因此,这是负责的5倍,和其他一切我所做的是负责5的另一个因素(我觉得“用户”,并在上面的“真实”时间的事实主要是占的区别我的病毒扫描仪运行,这是不早。“用户”是程序多少时间占据的CPU,“真实的”包括所花费的时间暂停,或者等待I / O或给另一个进程的运行时间)。
当然,我的桶排序依赖,无论我们的价值观在做每一列是可交换和关联的事实。降低桶的数量,只是因为大的值都同等对待。这可能不是所有的操作真实的,所以你必须要看看每一个反过来的内部循环,以弄清楚如何处理它。
和代码是有点复杂。而不是运行在数据做“嗒嗒”的每个阶段,我们计算在同一时间所有阶段中对数据的一次运行。如果你开始做的行和列计算在一个单一的传球,因为我在我的第一个答案建议,这将变得更糟。您可能要开始打破你的代码的功能,以保持它的可读性。
最后,我的很多性能提升都来自对优化事实是“台阶”是很小的。随着steps=100
,我得到:
$ make big -B CPPFLAGS="-O3 -pedantic -Wall" && time ./big; for n in 1 2 3 4 5
6; do diff -q results$n.bin gold$n.bin; done
g++ -O3 -pedantic -Wall big.cpp -o big
real 0m22.262s
user 0m10.108s
sys 0m1.029s
这是没有那么糟糕。随着步骤= 100的原代码可能需要约1400秒,虽然我不会运行它来证明。但是,这是值得记住的是,我还没有完全被带走的时间依赖于“台阶”,只是做它的子线。
其他提示
多横跨多个核能减少所需的时间总和横轴,但特殊护理需要。实际上,你可能会获得更大的业绩提升,从某些改变你可以做到你的一个线程代码:
你只需要作很多线程的匹配数量的核可。这是一个CPU密集型作业,和线是不可能等待I/O.
上述假设可能不保留,如果整个阵不适合在RAM。如果部分的阵列的调入和调出,一些线将等待呼行动来完成。在这种情况下,该程序可能会受益于有更多线程的核心。太多,然而,性能就会下降,由于成本的上下文交换。你可能需要试验的线数。一般的规则是尽量减少的数量方面交换机之间的准备线。
如果整个阵不适合在RAM,要最大限度地减少呼!为其中每一个的线访问记忆的事项,因为不存储器模式访问的所有运行的线。尽可能你会想完成一部分的阵之前移动到接下来,再也没有回来一个复盖区域。
每个核心将受益于有访问一个完全独立的地区的存储器。你想要避免存接造成的延误锁和旅竞争。至少对于一个尺寸的魔方,这应该是直截了当的:每个线程与其自己的部分。
每个核心也将从中受益的访问更多的数据,从其高速缓冲存储器(s),而不是取自RAM。这将意味着订购的循环,这种内在的循环,接近的词,而不是跳过跨行。
最后,根据中的数据类型阵列,单指令的指令的Intel/AMD处理(上证,在他们各种各样的后代)可以帮助加快单一核心业绩求和多个单元的一次。VC++有一些 建立在支持.
如果你必须优先考虑你的工作时,你可能希望首先尽量减少盘呼,然后集中精力优化存访问使用的CPU缓存,然后才处理多线程。
请问你的代码工作。它是否是这样的?
for each row: add up the values
for each column: add up the values
for each stack: add up the values
如果是这样,你可能要在“访问的局部性”读了。根据您的数据是如何存储,你可能会发现,当你正在做堆,整个高速缓存线被拉到在每个值,因为这些值都远不及对方在内存中。事实上,有十亿值,你可以从磁盘拉东西一路。具有长步幅(值之间的距离)的顺序访问是对高速缓存中的最坏可能的用途。试着分析,如果你看到加起来堆栈比添加了行需要更长的时间,这是几乎可以肯定这是为什么。
我想你可能会饱和内存总线(*),其中,如果酷睿2四核采用了不同的内核不同总线的情况下,多线程只会帮助。但是,如果你不饱和的总线带宽,你不能获得最佳的性能这样即使一旦你多线程。您将有4个核心消费停滞高速缓存所有的时间都没有抓住,而不是一个。
如果你是内存高速缓存范围,那么你的目标应该是内存的每一页/行参观的几次越好。所以我想尝试的东西像在数据运行一次,每次增值三种不同的总计,当您去。如果运行在单核快,然后我们在生意。下一步是用1000x1000x1000立方体,你在旅途中300万点的总数。不适合在高速缓存要么,所以你不必担心写入相同的缓存缺失的问题,你做阅读。
您想确保你的RAM以及1000个相邻值的行运行增加了行总觉得他们都有,你也加入了列和堆栈相邻总数(他们不商店)。所以列总计的“广场”应存放在适当的方式,也应堆的“广场”。你刚才拉的内存大约12K到缓存中(1000点堆栈总数4K为1000个值,加上4K 1000点总数,加上4K)处理您十亿值1000的方式。作为防止这一点,你正在做的商店比你会通过每次集中在1个总(其因此可以在一个寄存器)。
所以我不能保证什么,但我认为这是值得看的内存访问顺序,不管你多线程与否。如果你能在访问的内存只有相对少量做多CPU的工作,那么你将加速单线程版本,但也把自己在多线程要好得多,因为核心共享有限的高速缓存,内存总线和主RAM。
(*)返回包络计算的:在从互联网随机随机的评论最高估计FSB带宽为2处理器到目前为止我发现是在12GB / s的极限,在每个4x199MHz)2个信道。高速缓存行大小为64个字节,小于你前进的步伐。所以,总结一列或堆叠坏的方式,每个值抓64个字节,只会总线饱和的情况,如果是做每2个亿值。我猜这是没有这样快(10-15秒,整个事情),否则你就不会问如何加快速度。
所以我的第一个猜测是可能的路要走。除非你的编译器或CPU已经插入了一些非常巧妙预取中,单个芯不能使用2个通道和每个周期4个同时的传输。对于这个问题,4芯不能使用2个通道和4个同步传输。一系列要求的有效的总线带宽可能比物理极限,在这种情况下,你会希望看到从多线程很好的改善,只是因为你有4个核心,要求4条不同的缓存线低很多,所有这些都可以同时加载而不困扰FSB或高速缓冲存储器控制器。但延迟仍是杀手,所以如果你能比总结每值一个高速缓存行加载少,你会做的更好。
这是不可能告诉,一般来说,因为你没有指定你的CPU和RAM的速度有多快。良好的机会,这将改善的事情,因为我无法想象怎么连4个线程并行总结会饱和RAM不够,它会成为一个瓶颈(而不是CPU)。
我的直觉说,你会看到适度改善。然而,预测的优化的结果是出了名的容易出错的事情。
尝试和基准结果。
如果,这是个很大的可能,它被编码适当你肯定会看到一个加快。现在,作为一位教授总是指出,人们常常试图采取一种算法,它拧在到底是慢。这往往是由于低效的同步。所以基本上,如果你觉得自己钻研线程(老实说,我不会建议,如果你是新的编程)一展身手。
在您的特定情况下,同步可能是相当简单的。这就是说,你可以每个线程分配给大3 d矩阵,其中每个线程都保证有输入和输出矩阵的特定区域唯一访问的四分之一,因而不存在真正的需要“保护'从多个接入数据/写。
总之,在这种特定的情况下,简单的线程可以是相当容易的,但在一般的同步时做得不好会导致程序需要更长的时间。这真的要看情况。
多线程只会让你的代码运行得更快,如果计算可以分解成能够在独立且同时被加工块。
修改强>
我上面说的(这几乎是自动回复),因为我看到很多开发者花费了大量的时间对多线程代码没有性能提升的。当然,那么它们最终与管理多个线程的相同(或甚至更慢的性能)和额外的并发症。
是,它并再次阅读您的问题,并考虑到您的特定情况下,你会从多线程中获益后出现。
RAM是非常快的,所以我认为这将是非常困难的饱和内存带宽,除非你有很多很多的线程。
我觉得即使多线程可以产生性能提升很接近优化走错了路。多个内核都风靡一时,因为它们对于CPU厂商在商品率提供更快的CPU速度的唯一途径 - 不一定是因为他们是一个了不起的编程工具(还是有很多成熟的需要发生)<。 / p>
总是看你使用高于一切的算法。你说你的程序是非常密集的RAM - 你能做些什么来提高缓存命中率?有没有一种方法,以便计算可线性地应用到你的数组排序?什么编程语言,你使用,并会受益你在一个较低层次的语言优化?有,您可以使用动态编程来存储您的结果呢?
在一般情况下,花费所有的资源投入到更有效的算法工作,数学和编译器优化,那么不用担心多核。当然,你可能已经在该阶段,在这种情况下,这评论不是非常有用; P
你去多线程之前,您应该运行对你的代码分析器。它可能是一个不同的问题,即其中一个良好的(可能)自由C ++分析器可以找到。
这将帮助您识别占用的计算时间显著的部分代码的任何位。调整方案在这里和那里经过一番分析有时可以做出巨大的性能差异。
您需要回答您的具体应用的问题是众所周知的。
首先,是工作可并行? Amdahl定律会给你一个上限多少,你可以加快速度与多线程。
其次,将多线程溶液引入大量的开销?你说的节目“RAM密集型的程序不断取出由RAM,同时在读取和写入信息。”所以,你需要确定读/写是要引起href="http://www.ddj.com/architect/208200273" rel="nofollow noreferrer">协调开销显著
第三,你存储器约束? “RAM密集型的。”是不一样的东西“记忆的约束。”如果您目前CPU绑定,然后将多线程加快速度。如果您目前内存有关联,那么多线程甚至可能会慢下来(如果一个线程的内存速度过快,那么将与多线程会怎么样呢?)。 四,你慢了一些其他的原因?如果你 不过,总的来说,没有看到你的代码,我希望它是CPU绑定,并且我希望多线程来加快速度 - 几乎相当于Amdahl定律所揭示的,其实。你可能想看看的OpenMP或英特尔线程构建模块库,或某种线程队列的做,虽然。new
ing或你的算法malloc
ing大量的内存,你可能会看到从单独开销。 并在许多平台上new
和malloc
不处理多线程以及,所以如果你现在因为malloc
不好是缓慢的,多线程程序会更慢,因为malloc
会加重病情。
虽然这可能会是你非常具有挑战性的,如果你是新的节目,一个非常强大的方式来加快速度是使用GPU的处理能力。不仅是VRAM比平时RAM要快得多,在GPU也可以在平行一些128个或更多内核上运行代码。当然,这个数据量,你将需要有一个相当大的VRAM。
如果您决定检查这种可能性了,你应该看看了NVIDIA CUDA。我还没有亲自去查看,但它意味着这样的问题。
如果你partitionning你的数据正确,则是的,你将不得不在性能提升。如果你现在检查你的CPU使用率,一个核心将是100%,3人应该是接近0%
这一切都取决于你如何组织你的线程和内存使用情况。
另外,不要指望一个x4改善。 X4为最大可实现,它总是低于取决于很多因素。
你的计算机系统通常有一些因素限制的粗糙性能。哪一部分是你的限制因素,取决于具体情况。通常,以下因素之一可能是因为你的表现问题。
磁盘I/O带宽:在大多数企业应用程序的庞大规模数据处理需要它被保存在一些数据库。Acessing这些数据可以减缓通过这两种:最大的传输速度,但很多时候最大的影响将会引起大量的小盘访问读一块在这里和那里。你会看到的延迟时间的首脑磁盘走动,甚至时间盘需要为完全转可能限制应用程序。长时间以前我有一个真正的问题采用一些广阔的太阳E430安装这胜过通过我的小NeXTstation...这是不断同步()ing我的数据库,这是减慢盘不缓存写访问(有很好的理由).通常你可以加速系统通过添加额外的磁盘,以获得更多的I/O每秒。把你的驱动器的具体任务甚至可以做得更好,在某些情况下。
网络的延迟:几乎一切都影响到应用程序的速度所述的磁盘等同于网络I/O.
RAM:如果你的内存是不足够大,以储存完整的应用图像,你需要将其存储在一个外部磁盘。因此盘I/O放缓咬你一次。
CPU处理速度(无论是整数或浮点):CPU处理能力的下一个因素是限制对CPU密集型任务。CPU有一个物理的速度的限制,无法将外展.唯一的方法,以加速是增加更多的CPU。
这些限制可以帮助你找到回答你的具体问题。
你需要的只是更多的处理能力和系统已经超过一个处理器或核心?在这种情况下多线程将会提高你的表现。
你观察到明显的网络或磁盘延迟?如果你看到这个,你的宝贵CPU可以扔掉CPU周期等待一些缓慢的I/O.如果多于一个线程是积极的,这一线可能找到的所有数据用于处理需要在记忆和能捡起这些否则会浪费掉CPU周期。
因此,你需要观察现有的应用程序。尝试extimate存储器的带宽的数据洗牌的周围。如果应用程序活动的一个CPU上下100%,你可能已经达成的存储器的带宽限制。在这种情况下,额外线将不对你有好处,因为这不会给你mor带宽从存储器。
如果CPU是在100%,给它一个尝试,但是看一看的算法。多线程将增加额外开销同步(和复杂性,吨的复杂性),可能会略有减少存储器的带宽。喜欢alorithms,可以避免实施细同步。
如果你看到的I/O等待时间,认为有关聪明的分区或缓存,然后对穿线。还有一个原因为什么GNU的作支持的平行建立在90:-)
问题领域的你已经描述导致我gav看看聪明的算法的第一个。尽量使用的连续读/写作业上的主存储器,尽可能支持CPU和内存子系统尽可能多的。保持运作的"本地"和数据结构为小型和optimzed尽量减少存储需要洗牌周前换的第二核心。
这是一个矩阵问题?
Intel和AMD有各种重型数学难题超优化库。这些库使用线程,安排最佳的高速缓存使用,高速缓存预取,SSE向量指令的数据。一切。
我相信你必须为此付出代价的库,但他们是非常值得的钱。
如果您可以将的方式,线程不写阵列/读/从阵列中应该增加你的速度相同的位置。
我想,如果你只是用比特处理,你可能没有网页或使用交换文件,在这种情况下是多线程会有所帮助。
如果您不能一次加载的一切到内存中,你需要更具体的了解您的解决方案 - 它需要进行调整以适应线程。
例如: 假设你加载在更小的块阵列(大小可能没有多大关系)。如果你在一个立方体1000x1000x1000加载,你可以总结上。结果可以temporarially存储在其自身的三大平原,然后添加到您3“最终结果”飞机,那么1000 ^ 3块可以扔掉永远不会再阅读。
如果你这样做,你就不会耗尽内存,你不会强调交换文件,你将不必担心任何线程同步除了少数非常小的,特定区域(如在全部)。
唯一的问题则是,以确保您的数据是,你可以直接访问一个1000 ^ 3立方这样的格式 - 无求硬盘磁头所有的地方
编辑:评论是正确的,我错了 - 他完全是有道理的。
从昨天我意识到,因为它是在阅读整个问题可以解决 - 每一块读入可以立即被汇总到的结果和数据丢弃的。当我想到这样的说法,你是对的,不会有太大的帮助,除非线程可以读取在同一时间两个流无碰撞。
试试这个代码:
int dim = 1000;
int steps = 7 //ranges from 1 to 255
for (int stage = 1; stage < steps; stage++)
for (int k = 0; k < dim; k++)
for (int i = 0; i < dim; i++)
{
sum = 0;
for (int j = 0; j < dim; j++)
if (partMap[(((i * dim) + k) * dim) + j] >= stage)
projection[i*dim + j] ++ ;
// changed order of i and j
}
transponse(projection)
我改变了循环的顺序,以使代码缓存友好... 您会获得与它magninute性能提升一个数量级......要舒尔。
这是你尝试运行到多线程之前,你应该做的步骤
绝对。至少让一个线程每个内核上的问题,同时努力将帮助。目前尚不清楚,如果有更多的线程会有所帮助,但它是可能的。