Linq 用得太随意导致的性能问题一则

问题场景

有一个很多条数据的数据库(数据源),在其中找出指定的项,这些项的 ID 位于 给定的列表中,如 TargetList 中。

private readonly IDictionary<string, int> _testSource = new Dictionary<string, int>();
private readonly IList<string> _targetStringList = new List<string>();

直观简洁的 Linq 写法

public long TestFindByLinq()
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    IList<int> theResultData = _testSource.Where(s => _targetStringList.Contains(s.Key)).Select(s=>s.Value).ToList();
    // IList<int> theResultData2 = _targetStringList.Select(f => _testSource[f]).ToList();
    sw.Stop();
    Console.WriteLine($"[TestFindByLinq] ElapsedTicks:{sw.ElapsedTicks}; TotalTargetCount:{_targetStringList.Count} \n");
    return sw.ElapsedTicks;
}

问题在于,如果这么写,将要遍历整个源数据,性能受影响。

看起来麻烦,但性能好很多的写法

public long TestFindByForEach()
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    IList<int> theResultData = new List<int>();
    foreach (string target in _targetStringList)
    {
        int data = _testSource[target];
        theResultData.Add(data);
    }
    sw.Stop();
    Console.WriteLine($"[TestFindByForEach] ElapsedTicks:{sw.ElapsedTicks}; TotalTargetCount:{_targetStringList.Count}\n");
    return sw.ElapsedTicks;
}

性能相差多少呢?

从 100000 条数据中,找 6354 条数据。

差不多是 1000 倍以上的性能差距。

有什么启发吗?

Linq 性能不好,有时候可能只是 Linq 写得不好,Linq 写起来很方便,但如果写法中涉及到的查询方式,需要遍历全部数据,性能必然受到影响,如果还要多次遍历全部内容,那就更可怕了。

修正

以上测试考虑问题确实是不全面的,详情可以看评论区大佬的回复。

1 如果知道一定包含,可以用

var testData = _targetStringList.Select(f => _testSource[f]).ToList();

2 可以使用 hashSet

var testData = _testSource.Where(s => _targetStringList.Contains(s.Key)).ToList();
// 在原有的基础上,改动最小可以用HashSet
var hashSet = _targetStringList.ToHashSet();
var testData = _testSource.Where(s => hashSet.Contains(s.Key)).ToList();
// by 评论区 @玩命夜狼 

感谢各位大佬指正。


原文链接 : https://www.cnblogs.com/jasongrass/p/10797795.html

附:测试完整源码

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace GrassDemoPark.CoreConsoleApp
{
    class LinqTest
    {
        private readonly IDictionary<string, int> _testSource = new Dictionary<string, int>();
        private readonly IList<string> _targetStringList = new List<string>();

        public LinqTest()
        {
            for (int i = 0; i < 100000; i++)
            {
                string guid = Guid.NewGuid().ToString();
                _testSource.Add(guid, i);

                if (guid.StartsWith("5"))
                {
                    _targetStringList.Add(guid);
                }
            }
        }

        public long TestFindByLinq()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            IList<int> theResultData = _testSource.Where(s => _targetStringList.Contains(s.Key)).Select(s=>s.Value).ToList();
            // IList<int> theResultData2 = _targetStringList.Select(f => _testSource[f]).ToList();
            sw.Stop();
            Console.WriteLine($"[TestFindByLinq] ElapsedTicks:{sw.ElapsedTicks}; TotalTargetCount:{_targetStringList.Count} \n");
            return sw.ElapsedTicks;
        }

        public long TestFindByForEach()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            IList<int> theResultData = new List<int>();
            foreach (string target in _targetStringList)
            {
                int data = _testSource[target];
                theResultData.Add(data);
            }
            sw.Stop();
            Console.WriteLine($"[TestFindByForEach] ElapsedTicks:{sw.ElapsedTicks}; TotalTargetCount:{_targetStringList.Count}\n");
            return sw.ElapsedTicks;
        }

    }
}
posted @ 2019-05-13 11:07 J.晒太阳的猫 阅读(...) 评论(...) 编辑 收藏