良好的多线程设计是否过早优化?

时间:2010-01-22 22:02:47

标签: multithreading optimization

虽然我喜欢来自多核系统设计的智力挑战,但我意识到其中大多数只是不必要的过早优化。

但另一方面,通常所有系统都有一些性能需求,稍后将其重构为多线程安全操作很难甚至只是在经济上不可能,因为它将是另一种算法的完全重写。

在优化和完成任务之间保持平衡的方法是什么?

10 个答案:

答案 0 :(得分:9)

如果您遵循管道 Map-Reduce 设计模式,那就足够了。
分解内容,以便可以在OS级别的多处理管道中运行。

然后,您可以实际在实际管道中运行。没有额外的工作。 OS处理一切。巨大的加速机会。

您也可以切换到线程。一点工作。操作系统处理其中的一些部分,线程库处理其余部分。但是,由于您在设计时考虑“进程”,因此您的线程没有任何令人困惑的数据共享问题。一点思考就大赢了。

答案 1 :(得分:4)

介绍线程不会自动提高性能。

答案 2 :(得分:3)

如果你正在做任何有点复杂的多线程,你最好先考虑它/设计得好。否则,你的程序要么完全是灾难,要么完全 MOST ,并在其他时间做疯狂的事情。使用多线程很难设计出可证明正确的东西,但这非常重要。所以不,我不认为好的多线程设计是不成熟的优化。

答案 3 :(得分:2)

他们说,编码的日子可以节省数小时的设计。

并非所有问题和框架都是多线程的。例如,您依赖的库可能不是线程安全的。许多过程自然是顺序的,不能分解成可并行化的部分。

进行多线程/多处理只是一种并行化的方法。您也可以使用异步IO。

根据我的经验,从单个线程转向异步比使用多线程更加安全。但是,我写的程序解决了不同的问题,几乎所有其他问题。

答案 4 :(得分:1)

也许好事是设计具有某些特性的系统 所以如果你想引入多线程你可以优雅地做到

我不确定这些特征是什么,但我想到了一个模拟示例:缩放。如果您设计的是无状态系统可以执行的小型操作,您将能够更自然地扩展。

这对我来说很重要。

如果它是为多线程设计......那么过早的方法很重要。

如果它只是确保某些特性允许未来扩展或多线程:那么它就不那么重要了:)

编辑 oops,我再次阅读:过早优化?

优化:在你让系统工作之前我没有好处(并且没有来自试图优化事物的恶习)。做一个干净的设计,最灵活,最简单的设计。接下来,您可以在看到真正需要的时候进行优化。

答案 5 :(得分:1)

我认为线程也遵循优化法则 也就是说,不要浪费你的时间使快速操作平行 相反,将线程应用于需要很长时间才能执行的任务。

当然,如果系统开始拥有1000多个核心,那么这个答案可能已经过时,需要进行修改。但话说回来,如果你要“完成任务”,那么你肯定会在此之前发货。

答案 6 :(得分:1)

我绝不会考虑在纯粹出于推测性能考虑的应用程序中设计多线程。这是因为有一些技术对任何应用程序都有好处,以后很容易使操作成为多线程。我正在考虑的技术是:

  • 硬const合约
    • 在C ++中,您可以将方法标记为const,这意味着它不会更改实例变量的值。您还可以将方法的输入参数标记为const,这意味着只能在该参数上调用const方法。使用这两种技术(并且不使用“技巧”来绕过这些编译器强制执行),您可以减少需要识别多线程的操作。
  • 依赖性倒置
    • 这是一种通用技术,其中对象所需的任何外部对象在构造/初始化时传递给它,或者作为特定方法的方法签名的一部分。使用这种技术,100%清楚哪些对象可能被操作改变(非const实例变量加上非const参数到操作。)知道,你知道非功能方面的范围操作,您可以将互斥锁等添加到可以在并行操作之间共享的对象。然后,您可以将并行性设计为正确和有效。
  • 赞成功能性而非程序性
    • 具有讽刺意味的是,这意味着,不要过早地优化。使值对象不可变。例如,在C#中,字符串是不可变的,这意味着对它们的任何操作都返回字符串对象的新实例,而不是现有字符串的已修改实例。唯一不应该是不可变的对象是无界数组或包含无界数组的对象,如果这些数组可能经常被修改。我认为不可变对象更容易理解。许多程序员都学过程序技巧,所以这对我们来说有些陌生,但是当你开始用不可改变的术语思考时,先前编程的可怕方面,如操作依赖和副作用的顺序就会消失。这些方面在多线程编程中更加可怕,因此在类设计中使用功能样式有很多方面的帮助。随着机器增长更快,不可变对象的更高成本变得更容易和更容易证明。今天,这是一个平衡点。

答案 7 :(得分:1)

存在线程以使多个代理的使用更容易编程。

  • 如果代理是用户,就像您拥有每个用户的线程一样,他们可以更轻松地编写程序。这不是性能问题,而是一个易于编写的问题。

  • 如果代理是I / O设备,则可以轻松编写并行执行I / O的程序。这可能是也可能不是为了表现。

  • 如果代理是CPU内核,则可以轻松编写可以并行启动多个内核的程序。这就是线程与性能相关的时候。

换句话说,如果你认为threads == parallelism == performance,那只会遇到一个线程的使用。

答案 8 :(得分:1)

有三种基本设计选择:同步,异步或同步+多线程。如果你是一个疯狂的天才,请选择一个或多个。

是的,您需要在应用程序的设计阶段了解客户可接受的性能预期,以便能够提前做出正确的选择。对于任何非平凡的项目来说,将高水平的绩效预期视为事后的想法是非常危险和耗时的。

如果同步不符合客户要求:

CPU限制系统需要选择多线程/进程

IO有限系统(最常见)通常可以是Async或MT。

对于IO有限的杠杆技术,例如状态线程可以让你吃蛋糕并吃掉它。 (同步设计/ w异步执行)

答案 9 :(得分:0)

  

在优化和获取之间保持平衡的方法是什么?   做的事情?

在实施细节上轻松一点,但想出具有充足喘息空间的设计进行优化。现在这是一个棘手的部分,但是一旦你习惯了,它就不像听起来那么难。人们发现自己陷入瓶颈设计的一般原因通常是因为设计太过细化。

由此,作为一个极端的例子,我们选择一个视频处理应用程序,其设计围绕一个抽象的IPixel。抽象是为了让软件能够轻松处理具有不同像素格式的视频,并且仍然可以编写适用于所有像素格式的统一代码。

这样的应用程序在中央设计级别的性能方面受到了限制,在没有史诗般的架构重写的情况下,不太可能为编辑,编码,解码和回放提供有竞争力的性能。而那是因为它选择了过于细化的层次。

通过选择在像素级别进行抽象,可以在每个像素的基础上产生动态调度开销。允许虚拟调度,运行时类型信息(反射,例如)等功能的类比虚拟指针(或任何语言使用的)可能通常比整个像素本身更大,使其内存使用和缓存未命中的次数增加一倍或三倍处理。此外,如果您想在后见之明的多个领域进行多线程图像处理,则必须重写每次使用一个IPixel的每个地方。

同时,如果软件只是将其抽象设计在较粗糙的水平(如IImage),并避免将单个像素对象暴露给系统的其余部分,则可以避免所有这些。图像实际上是像素的集合(通常是数百万像素),并且它可以提供一次处理许多像素的操作。现在,与处理像素相关的处理和存储器开销减少了一百万像素图像到1 / 1,000,000th,此时它变得微不足道。这也使得图像操作有足够的喘息空间来处理并行处理像素并现在在中央级别进行矢量化而不重写大量代码,因为客户端代码不是一次单独处理一个像素而是请求执行整个图像操作。

虽然对于图像处理来说这似乎是一个明智的选择,而这在本质上是一个非常关键的性能领域,但在其他领域还有很大的空间。使用经典继承示例,您不必使Dog继承Mammal。您可以Dogs继承Mammals

回到完成工作,我开始采用面向数据的思维模式,不要获得最有效的缓存友好,令人尴尬的并行,线程安全,SIMD友好的数据表示以及尖端的数据结构和算法第一次尝试。否则我可以用一整周的时间来调整VTune的功能,同时观看基准测试的速度越来越快(我喜欢这样做,但它无处不在,无处不在,无处不在)。我只考虑了足够的想法来确定我应该用来设计事物的适当粒度级别:"我应该让系统依赖于Dog还是Dogs?&#34 ; ,这种事情。它甚至不需要那么多想法。对于OOP而言,"系统每帧处理十万只狗?是/否&#34?;如果"是",请不要设计中心Dog界面,也不要设计中心IMammal界面。设计Dogs以继承IMammals,就像我们在上面的模拟图像处理场景中避开IPixel界面一样,如果我们一次要处理数百万像素。 / p>

数据的大小也应该给你一个打击。如果数据很小,如64字节或更少,那么它可能不会暴露一个累积依赖关系的接口,除非它绝对不是性能关键的。相反,它应该公开一个集合接口来同时处理许多这些事情。同时如果数据很大,比如4千字节,那么很可能它几乎没有帮助公开一个集合接口,你可能只是为了方便而设计一个标量接口来处理这些事情之一。

多线程是一回事。例如,您不希望锁定过于细微的级别,并且您不希望您的访问模式继续影响共享资源。对于线程安全,您还希望能够获取一段代码并轻松地推断出哪个线程正在访问哪个状态。要做到这一点,你需要一个更粗糙的设计,在其中有更多的同类处理,这样你就可以在设计本身的实现中轻松控制和推理内存访问模式,最大限度地减少对共享资源的访问,避免锁定在过于细化的级别或甚至可能避免完全锁定。只要你的设计留有足够的呼吸空间,你就可以事后看到很多东西,但关键是让你自己喘息。

在整个系统中,通过非均匀处理的大量不同事物所依赖的少数事物不会留下这样的空间。在那里你可能会得到类比赛车的情景,只有10米的道路可供使用。处理大量存储的少量东西的大量事情留下了无穷无尽的空间,以便稍后进行优化。