【问题标题】:Parallel Code CPU utilization issue并行代码 CPU 利用率问题
【发布时间】:2015-06-30 17:59:34
【问题描述】:
open System
open System.Diagnostics
open System.Threading.Tasks
open System.Collections

let computation input =
  input|> Array.map (fun x-> sqrt(float x))|> ignore

let computation1 (input:int[]) =
  input|> Array.map (fun x-> Convert.ToString(x,2)) |> ignore


let abc p = 

        for i in 1..50000 do
            [|for i in 1..100000 -> i|]
            |> computation 

Parallel.For(0,100,fun x->abc 0) |>ignore

嗨, 在上述程序中,如果我在“abc()”中使用“computation()”运行应用程序,我会看到 CPU 利用率为 70-80%。

但是在“abc()”中使用“computation1()”的相同代码,我看到的 CPU 利用率只有 20%,而且我看到很多(一半的内核) 核心处于空闲状态。

有人知道这种行为吗?

我的 CPU 规格: Intel Xeon CPU @3.40Hz 启用超线程 核心总数:2 个 CPU,每个 8 个核心 = 16 HT 的总核心数 = 2*16 = 32

【问题讨论】:

  • 1 大不同 - string 是 ref 类型,int 是 value 类型。第一个的缓存位置和内存带宽要好得多。
  • 文体观点:为什么您使用 Parallel.For 而不是 Array.Parallel.map?

标签: f# task-parallel-library


【解决方案1】:

使用分析器应该很容易找到,但在这种情况下,答案相当明显:

Convert.ToString

会大大限制您的性能,使其受内存限制和(可能更重要的是)堆/GC 限制。

computation 几乎是纯 CPU 工作。 computation1 在你做的每一步都使用堆——这意味着频繁的内存访问(事实上,它甚至不能很好地使用 CPU 缓存,尽管 GC 压缩抵消了很多),并且经常在堆。堆和 GC 的性能相当高,但每秒数百万/数十亿的字符串确实在推动它。大多数情况下,您的代码会因为 GC 正在运行而暂停。

【讨论】:

  • @luaan-是的,我同意,这可能是因为字符串引用类型。如果我希望在计算中使用引用类型,是否可以创建一个预分配的引用类型池?
  • @Mahesh 当然,这是一种方法。另一种方法是完全避免引用类型,或者使用可变类型。这实际上很大程度上取决于您要做什么 - 但是在循环中创建数百万个引用类型的实例几乎总是一个糟糕的主意。请记住,使用对象池可能比看起来要复杂得多 - 您需要自己处理分配和释放,您必须考虑多线程问题(池必须是线程安全的,并且您必须保留禁止在对象被释放后使用等)。
  • @luaan- 同意。可变类型?你的意思是值类型吗?
  • @Mahesh 相反——当你已经有值类型时,你并不真正需要可变性(当然,除非值类型是引用类型的一部分)。在某种程度上,它只是对象池概念的扩展,除了没有通用的 Acquire-Release 语义,您可以通过其他方式将引用类型绑定到适当的工作程序 - 例如,如果您总是有四个线程,每次迭代只需要一次 ref 类型,每个线程可以有一个实例,该实例将在该线程上重用。当然,当地人很容易做到这一点。
  • @luaan- 知道了。感谢您的详细回复。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-11-22
  • 2020-09-17
  • 2012-03-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-30
  • 1970-01-01
相关资源
最近更新 更多