21

请参阅以下表示并行 foreach 完成的工作的并发性能分析:

在此处输入图像描述

在循环内部,每个线程从数据库中读取数据并进行处理。线程之间没有锁,因为每个线程处理不同的数据。

由于未知原因,foreach 的所有线程中似乎都存在周期性锁(参见黑色垂直矩形)。如果您看到选定的锁定段(深红色段),您将看到堆栈显示在 StockModel.Quotation 构造函数中锁定的线程。那里的代码只是构造了两个空列表!

我在某处读到这可能是由 GC 引起的,因此我将垃圾收集更改为在服务器模式下运行:

<runtime>
    <gcServer enabled="true"/>
</runtime>

我有一个小的改进(大约快 10% - 15%),但我仍然到处都有垂直锁。

我还在所有数据库查询中添加了 WITH(NOLOCK),因为我只读取数据而没有任何区别。

关于这里发生了什么的任何提示?

进行分析的计算机有 8 个内核。

编辑:启用 Microsoft 符号服务器后,所有线程都在调用 wait_gor_gc_done 或 WaitUntilGCComplete 时被阻塞。我认为启用 GCServer 我为每个线程都有一个 GC,因此我会避免“垂直”锁定,但似乎并非如此。我错了吗?

第二个问题:由于机器没有内存压力(使用了 8 个 gigs 中的 5 个),有没有办法延迟 GC 执行或暂停它直到并行 foreach 结束(或将其配置为较少触发)?

4

2 回答 2

4

如果您的 StockModel.Quotation 类允许,您可以创建一个池来限制创建的新对象的数量。这是他们有时在游戏中使用的一种技术,以防止垃圾收集器在渲染过程中停止。

这是一个基本的池实现:

    class StockQuotationPool
    {

        private List<StockQuotation> poolItems;
        private volatile int itemsInPool;

        public StockQuotationPool(int poolSize)
        {
            this.poolItems = new List<StockQuotation>(poolSize);
            this.itemsInPool = poolSize;

        }

        public StockQuotation Create(string name, decimal value)
        {
            if (this.itemsInPool == 0)
            {
                // Block until new item ready - maybe use semaphore.
                throw new NotImplementedException();
            }

            // Items are in the pool, but no items have been created.
            if (this.poolItems.Count == 0)
            {
                this.itemsInPool--;
                return new StockQuotation(name, value);
            }

            // else, return one in the pool
            this.itemsInPool--;

            var item = this.poolItems[0];
            this.poolItems.Remove(item);

            item.Name = name;
            item.Value = value;

            return item;
        }

        public void Release(StockQuotation quote)
        {
            if (!this.poolItems.Contains(quote)
            {
                this.poolItems.Add(quote);
                this.itemsInPool++;
            }
        }

    } 

假设 StockQuotation 看起来像这样:

  class StockQuotation
    {
        internal StockQuotation(string name, decimal value)
        {
            this.Name = name;
            this.Value = value;
        }


        public string Name { get; set; }
        public decimal Value { get; set; }
    }

然后,您无需调用新的 StockQuotation() 构造函数,而是向池请求一个新实例。池返回一个现有实例(如果需要,您可以预先创建它们)并设置所有属性,使其看起来像一个新实例。您可能需要反复尝试,直到找到足够大的池大小以同时容纳线程。

这是您从线程中调用它的方式。

    // Get the pool, maybe from a singleton.
    var pool = new StockQuotationPool(100);


    var quote = pool.Create("test", 1.00m);


    try
    {
        // Work with quote

    }
    finally
    {
        pool.Release(quote);
    }

最后,这个类目前不是线程安全的。如果您需要任何帮助,请告诉我。

于 2013-02-26T15:29:15.703 回答
0

您可以尝试GCLatencyMode.LowLatency;在此处使用查看相关问题:Prevent .NET Garbage collection for short period of time

我最近尝试过这个没有运气。在我正在显示的表单上缓存图标大小的位图图像时,仍在调用垃圾收集。对我有用的是使用 Ants 性能分析器和反射器来查找导致 GC.Collect 的确切调用并解决它。

于 2013-02-19T21:17:46.133 回答