当前位置: 首页 > 面试题库 >

多线程循环的效率

西门伟
2023-03-14
问题内容

问候贵族社区,

我想要以下循环:

for(i = 0; i < MAX; i++)
    A[i] = B[i] + C[i];

这将在使用线程的共享内存四核计算机上并行运行。对于这些线程要执行的代码,正在考虑以下两种选择,其中线程tid的ID是:0、1、2或3。

(为简单起见,假设MAX为4的倍数)

选项1:

for(i = tid; i < MAX; i += 4)
    A[i] = B[i] + C[i];

选项2:

for(i = tid*(MAX/4); i < (tid+1)*(MAX/4); i++)
    A[i] = B[i] + C[i];

我的问题是,是否有一种方法比另一种方法更有效,为什么?


问题答案:

第二个比第一个更好。简单的答案:第二个最小化错误共享

现代CPU不会将字节一一加载到缓存中。它在称为缓存行的批处理中读取一次。当两个线程试图修改同一高速缓存行上的不同变量时,一个线程必须在修改一个高速缓存后重新加载它。

什么时候会发生?

基本上,内存中附近的元素将位于同一缓存行中。因此,数组中的相邻元素将位于同一缓存行中,因为数组只是一块内存。并且foo1和foo2可能也位于同一缓存行中,因为它们在同一类中定义为紧密。

class Foo {

private int foo1;
private int foo2;

}

虚假分享有多糟糕?

我从处理器缓存效果库中引用示例6

private static int[] s_counter = new int[1024];
private void UpdateCounter(int position)
{
    for (int j = 0; j < 100000000; j++)
    {
        s_counter[position] = s_counter[position] + 3;
    }
}

在我的四核计算机上,如果我从四个不同的线程中调用带有参数0、1、2、3的UpdateCounter,则需要4.3秒才能完成所有线程。另一方面,如果我使用参数16,32,48,64调用UpdateCounter,则该操作将在0.28秒内完成!

如何检测虚假共享?

Linux Perf可用于检测高速缓存未命中,因此可以帮助您分析此类问题。

请参考来自CPU Cache Effects和Linux Perf的分析,使用perf从上面几乎相同的代码示例中找出L1缓存未命中:

Performance counter stats for './cache_line_test 0 1 2 3':
10,055,747 L1-dcache-load-misses     #    1.54% of all L1-dcache hits

[51.24%]

Performance counter stats for './cache_line_test 16 32 48 64':
  36,992 L1-dcache-load-misses     #    0.01% of all L1-dcache hits   [50.51%]

此处显示,没有错误共享,L1缓存命中的总数将从10,055,747下降至36,992。而且性能开销不在这里,而是在加载L2,L3高速缓存,在错误共享之后加载内存的系列中。

行业中有一些好的做法吗?

LMAX Disruptor是一个高性能线程间消息传递库,它是Apache
Storm中
工作人员内部通信的默认消息传递系统
。基础数据结构是一个简单的环形缓冲区。但是为了使其快速,它使用了许多技巧来减少错误共享。

例如,它定义了超类RingBufferPad以在RingBuffer中的元素之间创建填充:

abstract class RingBufferPad
{
    protected long p1, p2, p3, p4, p5, p6, p7;
}

同样,当它为缓冲区分配内存时,它会在缓冲区的前面和后面创建填充,以便不会受到相邻存储空间中数据的影响:

this.entries   = new Object[sequencer.getBufferSize() + 2 * BUFFER_PAD];

资源

您可能想更多地了解所有魔术。看看作者的其中一篇文章:剖析干扰者:为什么这么快



 类似资料:
  • 我已经写了这个生产者/消费者问题解决方案。它似乎在工作,而不是无限循环。我的印象是,pthread\u exit(NULL) 会让它停止,但老实说,我已经迷路了。有人能告诉我如何阻止循环的正确方向吗?

  • 我需要编写一个扩展Thread类的应用程序。我的类在实例化时接受一个整数(即100)<代码>(MyThread myt=新的MyThread(100);) 这个整数将是这个类循环并打印消息的次数。消息应该是“线程正在运行…100”。100将是我传入构造函数的任何数字。如果数字是150,那么输出应该是“线程正在运行…100”。我应该使用main方法来测试这个类。在主线程中,我将启动两个线程,一个线程

  • 问题内容: 我正在使用从队列中连续读取的线程。 就像是: 停止此线程的最佳方法是什么? 我看到两个选择: 1-由于已弃用,因此我可以实现使用原子检查条件变量的方法。 2-将死亡事件对象或类似的对象发送到队列。当线程获取死亡事件时,它退出。 我更喜欢第一种方法,但是,我不知道何时调用该方法,因为可能有某种方法进入队列,并且停止信号可能首先到达(这是不希望的)。 有什么建议? 问题答案: 该(或因为它

  • 我正在尝试编写一个线程,该线程将在我的主程序的后台运行,并监视某些内容。在某个时候,主程序应该向线程发出安全退出的信号。下面是一个以固定间隔将本地时间写入命令行的最小示例。 当“on”变量没有通过引用传递时,此代码会编译并产生预期的结果,只是线程永远不会终止。一旦变量通过引用传递,我就会收到编译器错误 您能建议一种修复此代码的方法吗? 额外的问题:哪里出了问题,为什么它可以与std::ref一起工

  • 所以我正在编写代码,它将解析文件夹中的多个文本文件,收集它们的信息,并将这些信息保存在两个静态列表实例变量中。信息存放的顺序并不重要,因为我最终将对其进行排序。但由于某些原因,增加线程数不会影响速度。这是我的run方法和主方法中利用多线程的部分。 如果需要额外的信息,我基本上有一个静态实例变量作为我需要浏览的文件的数组,还有一个常量是线程数(为了测试目的手动更改)。如果我有4个线程和8个文件,每个

  • 问题内容: 这可能是我误读了我所读内容的一种情况,但是在Java中杀死线程的所有示例似乎都表明您必须发出信号以杀死自己。您不能在没有严重风险的情况下从外面杀死它。问题是,所有有关如何“礼貌地”要求线程死亡的示例都有某种循环,因此您要做的就是观察每次迭代中的标志。 因此,我得到的是一个线程,该线程执行的操作仅需要一段时间(一系列SQL查询)。我当然可以在每个步骤之后进行检查,但是它们并没有处于循环中