4

我需要搜索包含时间属性的“项目”对象集合。我有一个快速的解决方案,但它非常混乱(如果没有更好的方法会发布)。以下是我自己使用 LINQ 等执行搜索的尝试。

在我的特殊情况下,我知道这些物品是根据时间从低到高排列的。当我遍历它们时,它是 9/12、9/13、9/14。我想找到一个快速的解决方案,即使没有订购,但现在并不重要。

//ICollection c = GetCollection(); //25,000+ items
DateTime TIME = DateTime.Now.AddDays(-1);

EventLog scan = new EventLog("Application", "Server", "N/A");
EventLogCollection c = scan.Entries;
Console.WriteLine(logs.Count); // All entries already in list here

// 64 sec - SLOW
List<Item> l1 = new List<Item>();
foreach (Item i in c) { 
  if (i.time > TIME) { 
    l1.Add(i); }
}

// 93 sec - SLOWER
var l2 = c.Cast<Item>().AsParallel().Select(n => n.time > TIME);
var i = l2.Count();

// 98 sec - EVEN SLOWER!
List<Item> l3 = new List<Item>();
Parallel.ForEach(c.Cast<Item>(), n => {
    if (n.time > TIME) {
        l3.add(n);
    }
});

我当前的解决方案是对开始时间和结束时间进行 BinarySearch,并根据这些索引循环遍历 ICollection。它非常快(1-2 秒)但非常混乱。我不需要不同的解决方案,但我想我会把它扔给你的性能专家。

是否有更快、更优雅的方式来搜索 ICollection?顺便说一句,我无法控制我收到的集合,也无法更改它的结构。.NET 4.0

不,我不能使用 System.Diagnostics.Eventing.Reader 因为我坚持使用 Windows XP

4

3 回答 3

2

您的“查询”不正确。您实际上是在选择一堆布尔值,而不是过滤。你要:

var l2 = c.Cast<EventLogEntry>().AsParallel().Where(n => n.time > TIME);

可能不会更快,但值得一试。哎呀,我什至不会一开始就并行执行。

事实上,Count有一个带有谓词的重载,所以你可以使用这个:

var count = c.Cast<EventLogEntry>().Count(n => n.time > TIME);

或并行版本:

var count = c.Cast<EventLogEntry>().AsParallel().Count(n => n.time > TIME);

像克里斯一样,我很震惊,任何实施都需要一分钟多的时间......

于 2012-10-01T13:08:18.930 回答
1

编辑

关于dasblinkenlight 的回答通常是正确的断言,我写了一个快速的通用辅助函数。

public static int BinaryFirstIndexOf<T>(
    Func<int, T> indexer,
    Func<T, boo> predicate,
    int count)
{
    var low = 0;
    var high = count - 1;

    while (low < (high - 1))
    {
        var mid = low + ((high - low) / 2);

        if (predicate(indexer(mid))
        {
            high = mid;
        }
        else
        {
            low = mid;
        }
    }

    if (predicate(indexer(low)))
    {
        return low;
    }

    if (low != high && predicate(indexer(high)))
    {
        return high;
    }

    return -1;
}

我会这样使用,

var time = DateTime.Now.AddDays(-1);
var c = scan.Entries;

var first = BinaryFirstIndexOf(
     i => c[i], 
     e => e.TimeGenerated > time,
     c.Count);

if (first >= 0)
{
    var result = new List<Item>(c.Count - first);
    Enumerable.Range(first, c.Count - first).AsParallel()
    .ForAll(i => 
        {
            var j = i - first;
            result[j] = (Item)c[i];
        });
}

你不想做这样的事情吗

var time = DateTime.Now.AddDays(-1);
var c = scan.Entries;

var cutOff = 0;
for (var i = c.Count - 1; i > - 1; i--)
{
    if (c[i].TimeGenerated < time)
    {
        cutOff = i;
        break;
    }
}

var result = new List<Item>(c.Count - cutOff - 1);
var j = 0;
for (var i = cutOff + 1; i < c.Count; i ++)
{
    result[j] = (Item)c[i];
    j++;
}

我假设您想要的数据在最后并且占用不到一半的集合。

或者也许使用 linq 并行进行强制转换,

var time = DateTime.Now.AddDays(-1);
var c = scan.Entries;

var cutOff = 0;
for (var i = c.Count - 1; i > - 1; i--)
{
    if (c[i].TimeGenerated < time)
    {
        cutOff = i;
        break;
    }
}

var result = new List<Item>(c.Count - cutOff - 1);
Enumerable.Range(cutOff + 1, c.Count - cutOff - 1).AsParallel()
    .ForAll(i => 
        {
            var j = i - cutOff - 1;
            result[j] = (Item)c[i];
        });
于 2012-10-01T13:52:37.363 回答
0

这第一个声明:

List<Item> l1 = new List<Item>();
foreach (Item i in c) { 
  if (i.time > TIME) { 
    l1.Add(i); }
}

它是否会提高性能更改为(将在运行 foreach 之前过滤列表):

List<Item> l1 = new List<Item>();
foreach (Item i in c.Where(a => a.time > TIME)) { 
    l1.Add(i);
}
于 2012-10-01T13:15:49.063 回答