最有效的多线程对象重复过滤(使用自定义键)?

时间:2012-02-29 10:32:12

标签: c# .net collections duplicates parallel-processing

我正在编写一个服务客户端,它会回忆起包含远程服务中各个记录的巨大分隔字符串。由于这些字符串的大小,我将远程服务调用划分为块(日期范围)并并行地循环日期范围以调用远程服务并解析数据。问题是,50%以上的记录是重复的,所以我想过滤那些...

这是我目前的做法:

// We want to filter out duplicate markets by using the MarketId field...
HashSet<ParsedMarketData> exchangeFixtures = 
    new HashSet<ParsedMarketData>(
        new GenericEqualityComparer<ParsedMarketData, int>(pmd => pmd.MarketId));

DateTime[][] splitTimes = 
    SplitDateRange(startDate, endDate, TimeSpan.FromDays(1));

// Effectively a Tasks.Parallel.ForEach call...
_parallel.ForEach(splitTimes, startEndTime =>
{
    DateTime start = startEndTime[0];
    DateTime end = startEndTime[1];

    string marketDataString = remoteServiceProxy.GetMarketData(start, end);
    IEnumerable<ParsedMarketData> rows = 
        _marketDataParser.ParseMarketData(marketDataString);

    foreach (ParsedMarketData marketDataRow in rows)
    {
        lock (_syncObj)
        {
            // Ignore the return value as we don't care 
            // if it gets added or not...
            marketDataList.Add(exchangeFixture);
        }
    }
});

从根本上说,锁定数据结构(找到重复数据)是解决此问题的最有效方法还是可以改进?

可能值得知道,大多数(95%以上)'重复'项目都出现在每个时间段内。即如果我们并行检索“A日”和“B日”,那么在第A天和第B天之间将不会有很多(或任何)重复(但在每天内很多 - 并且在我的解决方案中,每个线程)。

2 个答案:

答案 0 :(得分:1)

您需要调整代码以利用数据和服务中的并发机会。听起来像每天一个帖子可能是一个选择。

实际上看到改善应该是罕见的。多线程购买更多的cpu周期,而不是更多的互联网连接,网卡或服务机器。只有两个线程是最佳的几率很高。一个用于从服务获取数据,另一个用于处理数据。允许这两个操作重叠,它们之间是一个线程安全的生产者/消费者队列。如果处理线程比数据检索线程需要更多时间,您只能从更多线程中获益。此外,您可以轻松地对代码进行概要分析,您可以加快处理速度,但不能加快检索速度。您甚至不需要分析器进行初步估算。如果数据处理线程没有烧掉100%核心,那么你就完成了。

答案 1 :(得分:0)

鉴于数据,并且每个线程中重复的可能性很高(线程之间重复的可能性很小),我决定采用以下解决方案,它允许每个线程执行其内容而不会受到锁定的阻碍并进行一些过滤在调用程序线程的最后,以确保正确完成筛选。

它还有一个额外的好处,就是可以跨线程维护从服务调用(日期顺序)返回对象的顺序,因此不需要在最后对它进行排序。

public IEnumerable<Stuff> GetStuffs(DateTime startDate, DateTime endDate)
{
    if (startDate >= endDate)
        throw new ArgumentException("startDate must be before endDate", "startDate");

    IDateRange dateRange = new DateRange(startDate, endDate);

    IDateRange[] dateRanges = _dateRangeSplitter.DivideRange(dateRange, TimeSpan.FromDays(1)).ToArray();

    IEnumerable<Stuff>[] resultCollections = new IEnumerable<Stuff>[dateRanges.Length];

    _parallel.For(0, dateRanges.Length, i =>
    {
        IDateRange splitRange = dateRanges[i];

        IEnumerable<Stuff> stuffs = GetMarketStuffs(splitRange);

        resultCollections[i] = stuffs;
    });

    Stuff[] marketStuffs = resultCollections.SelectMany(ef => ef).Distinct(ef => ef.EventId).ToArray();

    return marketStuffs;
}

private IEnumerable<Stuff> GetMarketStuffs(IDateRange splitRange)
{
    IList<Stuff> stuffs = new List<Stuff>();
    HashSet<int> uniqueStuffIds = new HashSet<int>();

    string marketStuffString = _slowStuffStringProvider.GetMarketStuffs(splitRange.Start, splitRange.End);

    IEnumerable<ParsedStuff> rows = _stuffParser.ParseStuffString(marketStuffString);

    foreach (ParsedStuff parsedStuff in rows)
    {
        if (!uniqueStuffIds.Add(parsedStuff.EventId))
        {
            continue;
        }

        stuffs.Add(new Stuff(parsedStuff));
    }
    return stuffs;
}