我需要从中提取子集的大量GUID和其他数据,最快的方法是什么?

Ric*_*ell 5 .net c# dynamics-crm

我有一个已反序列化并粘贴到HashSet中的Dynamics CRM 2013审核数据列表,定义为:

private class AuditCache
{
    public Guid ObjectId;
    public int HistoryId;
    public DateTime? DateFrom;
    public DateTime? DateTo;
    public string Value;
};
private HashSet<AuditCache> _ac = new HashSet<AuditCache>();
Run Code Online (Sandbox Code Playgroud)

我添加这样的数据(来自SQL Server记录集):

_ac.Add(new AuditCache{ 
    ObjectId = currentObjectId,
    HistoryId = Convert.ToInt32(dr["HistoryId"]),
    DateTo = Convert.ToDateTime(dr["CreatedOn"]),
    Value = value});
Run Code Online (Sandbox Code Playgroud)

我最终得到了大约一百万条记录。

接下来,我需要遍历每个Guid,并从匹配的审计数据中提取数据的子集。我有一份我在其他地方生成的Guid列表,大约有30万个要处理。我将它们存储在:

var workList = new Dictionary<Guid, DateTime>();
Run Code Online (Sandbox Code Playgroud)

...并像这样遍历它们:

foreach (var g in workList)
Run Code Online (Sandbox Code Playgroud)

然后,我需要执行此操作以提取每个Guid的子集:

List<AuditCache> currentSet = _ac.Where(v => v.ObjectId == g.Key).ToList();
Run Code Online (Sandbox Code Playgroud)

但这很慢。

填充我的初始审核数据列表大约需要1分钟,但要花费几个小时(我从未运行过它才能完成,所以这是基于处理1%数据的时间)来取出每个数据集,对其进行处理并将其喷回进入数据库表。

单步执行代码,我可以看到瓶颈似乎正在从每个Guid的列表中拉出子集。所以我的问题是,是否有更好/更有效的方法(架构?)来存储/检索我的数据集?

需要注意的一件事是,我知道Guid本质上对索引/搜索的速度很慢,但是由于Dynamics CRM的工作方式,我在使用它们方面受到了很大的限制。我想我可以创建一个词典来查找Guid并将其“转换”为整数值或类似的东西,但是我不相信这会有所帮助吗?

编辑

好的,我使用实时数据(371,901吉德)测试了三种解决方案,这些结果是每1,000吉德的平均时间。请注意,这包括对SQL Server的处理/ INSERT,因此它不是适当的基准。

Method #0 - List with Lambda    ~30.00s per 1,000 rows (I never benchmarked this precisely)
Method #1 - IntersectWith        40.24s per 1,000 rows (cloning my Hashset spoilt this)
Method #2 - BinarySearch          3.20s per 1,000 rows
Method #3 - Generic Dictionary    2.19s per 1,000 rows
Run Code Online (Sandbox Code Playgroud)

在此基础上,我可能会从头开始重写代码,因为我认为我采用的整个方法都不正确。

但是,这是一个非常有用的学习活动,非常感谢所有贡献者。我将接受BinarySearch作为正确的答案,因为它可以满足我的要求,并且比原始代码要快得多。

只需在此处明确说明,IntersectWith确实“抽烟”得很快,但是它对我的特定问题不起作用,因为我需要不断返回到原始哈希集。

pap*_*zzo 5

P4在1.2秒内达到100万

HashSet具有IntersectWith
吸烟速度很快

如果由另一个参数表示的集合是具有与当前HashSet对象相同的相等比较器的HashSet集合,则此方法是O(n)操作。否则,此方法是O(n + m)运算,其中n是Count,m是其他元素的数量。

但是要使其正常工作,您需要AuditCache来实现Object并重写GetHashCode和Equals
Object.GetHashCode方法

GUID很好地进行了哈希处理(最小冲突),因此这将非常快。

WorkList也将需要是AuditCache(即使它实际上不是AuditCache),
也可以让它们都实现一个使用Guid ObjectId作为键的类(Equal和GetHashCode)。

如果将GUID用作哈希键(字典和哈希集),则其索引编制不会天生就很慢-它是一个很好的键,因为它几乎没有(或没有)冲突。即使您同时将这两个字典作为具有Guid键的字典,也会更快。但是Dictionary没有IntersectWith。

在P4上的1.2秒内达到100万个,克隆0.5秒,相交0.7秒

using System.Diagnostics;
namespace HashSetIntersect
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Stopwatch sw = new Stopwatch();
            sw.Start();
            HashSet<AuditCache> TestHashKeys1 = new HashSet<AuditCache>();
            HashSet<AuditCache> TestHashKeys2 = new HashSet<AuditCache>();
            for (UInt32 i = 0; i < 1000000; i++)
            {
                Guid g = Guid.NewGuid();
                TestHashKeys1.Add(new AuditCache(g, 1, (DateTime?)null, (DateTime?)null, "value1"));
                if (i % 2 == 0) TestHashKeys2.Add(new AuditCache(g, 0, (DateTime?)null, (DateTime?)null, "value2"));
            }            
            Debug.WriteLine(TestHashKeys1.Count.ToString() + " " + TestHashKeys2.Count.ToString());
            sw.Stop();
            Debug.WriteLine(sw.ElapsedMilliseconds.ToString());
            sw.Restart();
            HashSet<AuditCache> TestHashKeys3 = new HashSet<AuditCache>(TestHashKeys1);
            sw.Stop();
            Debug.WriteLine(sw.ElapsedMilliseconds.ToString());
            sw.Restart();
            TestHashKeys3.IntersectWith(TestHashKeys2);
            sw.Stop();
            Debug.WriteLine(sw.ElapsedMilliseconds.ToString());
            foreach (AuditCache ac in TestHashKeys3)
            {
                Debug.WriteLine(ac.Value);
            }
        }
    }
    public abstract class HashKey : Object
    {
        public Guid ObjectId { get; private set; }
        public override bool Equals(object obj)
        {
            if (!(obj is HashKey)) return false;
            HashKey comp = (HashKey)obj;
            return this.ObjectId == comp.ObjectId;
        }

        public override int GetHashCode()
        {
            return ObjectId.GetHashCode();
        }
        public HashKey(Guid objectId)
        {
            ObjectId = objectId;
        }
    }
    public class TestHashKey : HashKey
    {
        public TestHashKey(Guid ObjectId)
            : base(ObjectId)
        { }
    }
    public class AuditCache : HashKey
    {
        public int HistoryId { get; private set; }
        public DateTime? DateFrom { get; private set; }
        public DateTime? DateTo { get; private set; }
        public string Value { get; private set; }
        public AuditCache(Guid objectId, int historyId, DateTime? dateFrom, DateTime? dateTo, string value)
            : base(objectId)
        {
            HistoryId = historyId;
            DateFrom = dateFrom;
            DateTo = dateTo;
            Value = value;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Mar*_* N. 4

如果您按 GUID(毕竟是一个大整数)对 AuditCache 列表进行排序,然后使用List<T>.BinarySearch它,怎么样?

我得到了相当好的结果(在 i3-3110M @2.4Ghz 上不到 15 秒)。累计次数如下:

  1. 创建集合:892 毫秒
  2. 集合已排序:9285 毫秒
  3. 查找:12055 毫秒

下面我使用BigIntegerfromSystem.Numerics将 Guid 解释为 128 位整数。

如果我没有遗漏什么,那么这应该可行。请注意,查找 for 循环实际上是最坏的情况,因为不太可能发生冲突(因此索引始终为 -1)。在你的情况下,它可能会更快:

class AuditCache
{
    public Guid ObjectId;
    public int HistoryId;
    public DateTime? DateFrom;
    public DateTime? DateTo;
    public string Value;
};

class AuditCacheComparer : IComparer<AuditCache>
{
    public int Compare(AuditCache x, AuditCache y)
    {
        BigInteger intx = new BigInteger(x.ObjectId.ToByteArray());
        BigInteger inty = new BigInteger(y.ObjectId.ToByteArray());
        if (intx < inty)
        {
            return -1;
        }
        else if (intx > inty)
        {
            return 1;
        }

        return 0;
    }
}

class Program
{

    static void Main(string[] args)
    {
        List<AuditCache> testCollection = new List<AuditCache>();
        Stopwatch sw = Stopwatch.StartNew();

        for (int i = 0; i != 1000000; ++i)
        {
            testCollection.Add(new AuditCache() { ObjectId = Guid.NewGuid(), HistoryId = i });
        }

        Console.WriteLine("Collection created: {0} ms", sw.ElapsedMilliseconds);

        AuditCacheComparer comparer = new AuditCacheComparer();
        testCollection.Sort(comparer);

        Console.WriteLine("Collection sorted: {0} ms", sw.ElapsedMilliseconds);

        for(int i = 0; i != 300000; ++ i)
        {
            var index = testCollection.BinarySearch(new AuditCache() {ObjectId = Guid.NewGuid()}, comparer);
            if (index > 0)
            {
                Console.WriteLine("Found: {0} ms", sw.ElapsedMilliseconds);
            }
        }

        Console.WriteLine("Lookup: {0} ms", sw.ElapsedMilliseconds);
        Console.ReadLine();
    }
}
Run Code Online (Sandbox Code Playgroud)