了解VS2010 C#并行分析结果

Hag*_*gai 15 c# parallel-processing profiling visual-studio-2010

我有一个程序有很多独立的计算,所以我决定并行化它.

我使用Parallel.For/Each.

双核机器的结果还可以 - 大多数时候CPU利用率约为80%-90%.但是,使用双Xeon机器(即8个内核),我只获得了大约30%-40%的CPU利用率,尽管该程序在并行部分上花费了相当多的时间(有时超过10秒),我看到它使用了与串行部分相比,这些部分中大约有20-30个线程.每个线程需要1秒以上才能完成,所以我认为它们没有理由不能并行工作 - 除非存在同步问题.

我使用了VS2010的内置分析器,结果很奇怪.即使我只在一个地方使用锁,分析器报告大约85%的程序时间用于同步(也是5-7%睡眠,5-7%执行,低于1%IO).

锁定的代码只是一个缓存(字典)get/add:

bool esn_found;
lock (lock_load_esn)
    esn_found = cache.TryGetValue(st, out esn);
if(!esn_found)
{
    esn = pData.esa_inv_idx.esa[term_idx];
    esn.populate(pData.esa_inv_idx.datafile);
    lock (lock_load_esn)
    {
        if (!cache.ContainsKey(st))
            cache.Add(st, esn);
    }
}
Run Code Online (Sandbox Code Playgroud)

lock_load_esn是Object类型的静态成员.
esn.populate使用单独的StreamReader为每个线程从文件中读取.

但是,当我按下同步按钮以查看导致最大延迟的原因时,我看到探查器报告的是作为功能入口线的线,并且不报告锁定的部分本身.
它甚至没有报告包含上述代码的功能(提醒 - 程序中唯一的锁定)作为阻塞配置文件的一部分,噪声级别为2%.当噪音水平为0%时,它会报告程序的所有功能,我不明白为什么它们被视为阻塞同步.

所以我的问题是 - 这里发生了什么?
85%的时间花在同步上怎么样?
如何找出程序中并行部分的实际问题?

谢谢.

更新:深入研究线程(使用极其有用的可视化工具)后,我发现大部分同步时间都花在等待GC线程完成内存分配上,并且由于通用数据结构调整大小操作需要频繁的分配.

我将不得不看看如何初始化我的数据结构,以便它们在初始化时分配足够的内存,可能避免GC线程的这种竞争.

我今天晚些时候会报告结果.

更新:看起来内存分配确实是问题的原因.当我在并行执行的类中使用所有词典和列表的初始容量时,同步问题更小.我现在只有大约80%的同步时间,CPU利用率达到70%(先前的峰值仅为40%左右).

我进一步钻进每个线程,发现现在很多调用GC分配用于分配不属于大字典的小对象.

我通过为每个线程提供一个预先分配的这类对象池来解决这个问题,我使用它而不是调用"new"函数.

所以我基本上为每个线程实现了一个单独的内存池,但是以非常粗糙的方式,这非常耗时,实际上并不是很好 - 我仍然需要使用很多新的来初始化这些对象,只有现在我全局执行一次,即使不得不增加池的大小,GC线程上的争用也会减少.

但这绝对不是我喜欢的解决方案,因为它不容易推广,我不想写自己的内存管理器.
有没有办法告诉.NET为每个线程分配预定义的内存量,然后从本地池中获取所有内存分配?

Bri*_*ian 4

能不能少分配一点?

我有过几次类似的经历,在查看糟糕的性能时发现问题的核心是 GC。不过,在每种情况下,我都发现我在某些内部循环中意外地耗尽了内存,不必要地分配了大量的临时对象。我会仔细查看代码,看看是否有可以删除的分配。我认为程序很少“需要”在内循环中进行大量分配。

  • 顺便说一句,你运行过内存分析器吗?有时它会指出严重错误,就像您看到分配了十亿个字符串,并意识到“哎呀”,我不应该在字符串上使用 +,而应该使用 StringBuilder 或其他什么。我很想确切地知道正在分配哪些数据结构,然后在内部循环中快速丢弃。 (2认同)