C# 如何加速列表上的linq查询<&燃气轮机;在列表对象的3个属性上使用搜索条件

C# 如何加速列表上的linq查询<&燃气轮机;在列表对象的3个属性上使用搜索条件,c#,performance,linq,list,C#,Performance,Linq,List,我有以下LINQ查询 这是一份约55000项的清单。我需要搜索项目中的三个属性 这是我的密码: private List<Device> Devices = _db.Devices.ToList(); public Device TryFindDeviceInNetworks(ALL_Sims sim) { var ips = new List<string>(); if (sim.IP1 != null) { ips.Add(s

我有以下LINQ查询

这是一份约55000项的清单。我需要搜索项目中的三个属性

这是我的密码:

private List<Device> Devices = _db.Devices.ToList();

public Device TryFindDeviceInNetworks(ALL_Sims sim)
{
    var ips = new List<string>();
    if (sim.IP1 != null)
    {
        ips.Add(sim.IP1);
    }
    if (sim.IP2 != null)
    {
        ips.Add(sim.IP2);
    }

    var device =
        Devices.FirstOrDefault(
            x => ips.Contains(x.IPaddress1)
                 || ips.Contains(x.IPaddress2)
                 || ips.Contains(x.IPaddress3));

    return device;
}
private List Devices=_db.Devices.ToList();
公用设备尝试查找设备网络(所有sim卡)
{
var ips=新列表();
如果(sim.IP1!=null)
{
ips.Add(sim.IP1);
}
如果(sim.IP2!=null)
{
ips.Add(sim.IP2);
}
无功装置=
设备。第一个或默认值(
x=>ips.Contains(x.IPaddress1)
||ips.Contains(x.IPaddress2)
||包含(x.ipaddress 3));
返回装置;
}
目前,此操作需要一些时间

我有一个for循环,它遍历大约100k个项目,在每次迭代中调用这个函数
TryFindDeviceInNetworks()
。它在英特尔i5上运行8-10个多小时。显然,这是单线程的

我的问题是我怎样才能加快速度?我已经将我的一些列表转换为字典,我可以在应用程序中使用,这有很大帮助,但是在这种情况下,我不能只搜索一个键

是否有某种数据结构比
列表更适合

数据库不在本地局域网上,因此估计每次迭代都会增加至少40毫秒+查询时间的ping

var device =
    Devices.FirstOrDefault(
        x => ips.Contains(x.IPaddress1)
             || ips.Contains(x.IPaddress2)
             || ips.Contains(x.IPaddress3));
在最坏的情况下(无匹配项),将枚举
IP
三次。我将把它改写为:

var device =
    Devices.FirstOrDefault(
        x => ips.Any(y => y == x.IPaddress1
             || y == x.IPaddress2
             || y == x.IPaddress3);
因此,它只枚举一次,在运行时检查每个可能的“匹配条件”,并在找到一个条件后立即返回


正如一些评论者所说,如果可能的话,找到一种进行简单数字比较的方法也比字符串比较快。

编写自己的循环,而不是linq,并查看它的性能:

注意:此代码假设ips集合中始终有2个IP地址,如问题注释中所述

foreach(var device in Devices)
{
      if(device.IPaddress1 == ips[0] || device.IPaddress2 == ips[0] || device.IPaddress3 == ips[0] || device.IPaddress1 == ips[1] || device.IPaddress2 == ips[1] || device.IPaddress3 == ips[1] )
      return device;
}
这将展开其中一个循环,并利用在找到匹配项后立即返回的优势


可以通过让数据库来执行进一步的优化。

而不是使用一个包含所有3个IP地址的字典,您可以使用3个字典:

private List<Device> Devices = new List<Device>();

private Dictionary<string, Device> mapIP1;
private Dictionary<string, Device> mapIP2;
private Dictionary<string, Device> mapIP3;
搜索本身可以使用
TryGetValue

public Device TryFindDeviceInNetworks(ALL_Sims sim)
{
    Device device = null;

    if (sim.IP1 != null)
    {
        if (mapIP1.TryGetValue(sim.IP1, out device))
            return device;
        if (mapIP2.TryGetValue(sim.IP1, out device))
            return device;
        if (mapIP3.TryGetValue(sim.IP1, out device))
            return device;
    }

    if (sim.IP2 != null)
    {
        if (mapIP1.TryGetValue(sim.IP2, out device))
            return device;
        if (mapIP2.TryGetValue(sim.IP2, out device))
            return device;
        if (mapIP3.TryGetValue(sim.IP2, out device))
            return device;
    }

    return device;
}

您必须确保
设备
列表中没有共享相同地址的元素,因为
字典
无法使用重复的键。

为三个键中的每一个创建查找。如果只进行一次搜索,结果不会有多大区别,但如果进行了十万次搜索,能够在固定时间内根据IP查找值将是一个巨大的成功:

公共类Foo
{
私有列表设备=_db.Devices.ToList();
私人IList查找;
公共食物(
{
lookups=new[]{
Devices.ToLookup(device=>device.IPaddress1),
Devices.ToLookup(device=>device.IPaddress2),
Devices.ToLookup(device=>device.IPaddress3),
};
}
公用FSK设备尝试查找设备网络(所有sim卡)
{
var ips=new[]{sim.IP1,sim.IP2}
。其中(ip=>ip!=null);
返回(从ip中的ip返回)
从查找中的查找
让匹配项=查找[ip]
匹配的地方。Any()
选择匹配项。首先()
.FirstOrDefault();
}
}

ips的
ips
大小是否总是最多2个条目,或者这是一个简化的示例?在我的例子中总是2个条目。我只是将其添加到一个列表中,以便在linq查询中进行包含。IPaddress属性是什么类型的?如果它可以进行32位整数比较(如果是IPv6,则为128位),那么它将比字符串比较快得多。看起来您正在从数据库(
\u db
?)中提取所有行,并在内存中进行搜索。你应该改为对DB进行查询。@MarcinJuraszek我认为这样做的缺点是他称之为10万次,这将是大量的DB查询。我想说,在这种情况下,记忆是更好的选择。下层选民愿意发表评论吗?我相信这将有助于OPs的性能。迭代ips 3次,每次迭代做1/3的工作,与迭代ips一次,每次迭代做3倍的工作大致相同;这极不可能改变一件事。当然,考虑到ips的项目太少,这两个项目都会非常小。我们刚刚实现了这个解决方案。它看起来并没有明显的更快,但再一次,除了“调试”菜单中的计数器之外,我还没有任何硬数据可以作为它的基础。@Servy我想我看到一条评论说它可能更大。。。现在看来,它已经消失/被编辑了,所以我同意这一点。根据我的经验,减少枚举计数可能会有所帮助,尽管在这种情况下你很可能是对的。不枚举表示数据库查询的
IQueryable
,不枚举表示进行昂贵计算的LINQ查询,也不枚举表示迭代时不总是产生相同值的查询,这通常是非常重要的。这些事情在这里都没有发生。如果这是一个通用方法,可以接受任意的
IEnumerable
(可以通过执行上面提到的某项操作传入查询),那么只迭代一次就很重要了,以确保它实际工作。这并没有做到这一点。尝试过这一点,但显然增加了IP1和IP2为空的情况的复杂性,因为我没有将它们添加到我的
ips
列表中?foreach不会比FirstOrDefault快很多。它也会在找到第一个匹配项时返回,并且不会枚举整个序列。所以你的代码和FirstOrD完全一样
public Device TryFindDeviceInNetworks(ALL_Sims sim)
{
    Device device = null;

    if (sim.IP1 != null)
    {
        if (mapIP1.TryGetValue(sim.IP1, out device))
            return device;
        if (mapIP2.TryGetValue(sim.IP1, out device))
            return device;
        if (mapIP3.TryGetValue(sim.IP1, out device))
            return device;
    }

    if (sim.IP2 != null)
    {
        if (mapIP1.TryGetValue(sim.IP2, out device))
            return device;
        if (mapIP2.TryGetValue(sim.IP2, out device))
            return device;
        if (mapIP3.TryGetValue(sim.IP2, out device))
            return device;
    }

    return device;
}
public class Foo
{
    private List<FSKDevice> Devices = _db.Devices.ToList();
    private IList<ILookup<string, FSKDevice>> lookups;

    public Foo()
    {
        lookups = new[]{ 
            Devices.ToLookup(device => device.IPaddress1),
            Devices.ToLookup(device => device.IPaddress2),
            Devices.ToLookup(device => device.IPaddress3),
        };
    }

    public FSKDevice TryFindDeviceInNetworks(ALL_Sims sim)
    {
        var ips =  new[] { sim.IP1, sim.IP2 }
            .Where(ip => ip != null);

        return (from ip in ips
                from lookup in lookups
                let matches = lookup[ip]
                where matches.Any()
                select matches.First())
                    .FirstOrDefault();
    }
}