C# 为什么Linq的速度要慢得多?
我试着用Linq和非Linq方法做同样的事情,发现Linq的速度要慢很多(~3000x) 为什么呢 林奇道:C# 为什么Linq的速度要慢得多?,c#,performance,linq,C#,Performance,Linq,我试着用Linq和非Linq方法做同样的事情,发现Linq的速度要慢很多(~3000x) 为什么呢 林奇道: for (int i = 0; i < totalElements; i += stepSize) { var currentBlock = testList .Skip(i) .Take(stepSize); result.Add(currentBlock.Sum()); } result.ToList(); for(int
for (int i = 0; i < totalElements; i += stepSize)
{
var currentBlock = testList
.Skip(i)
.Take(stepSize);
result.Add(currentBlock.Sum());
}
result.ToList();
for(int i=0;i
非Linq方式:
for (int i = 0; i < totalElements; i += stepSize)
{
var currentBlock = testList.GetRange(i, stepSize);
result2.Add(currentBlock.Sum());
}
result2.ToList();
for(int i=0;i
结果:
- 方法:Linq,时间:26667ms,元素:1000000,步长:100
- 方法:GetRange,所用时间:9毫秒,元素:1000000,步长:100
static void Main(string[] args)
{
var totalElements = 1000000;
var testList = new List<int>(totalElements);
var rand = new Random();
// Initialize the list to random integers between 1 and 1000
for (int i = 0; i < totalElements; i++)
{
testList.Add(rand.Next(1, 1000));
}
var result = new List<int>();
var stepSize = 100;
var stp = new Stopwatch();
stp.Start();
for (int i = 0; i < totalElements; i += stepSize)
{
var currentBlock = testList
.Skip(i)
.Take(stepSize);
result.Add(currentBlock.Sum());
}
result.ToList();
stp.Stop();
Console.WriteLine($"Method: Linq, Time taken: {stp.ElapsedMilliseconds} ms, Elements: {totalElements}, Step Size: {stepSize}");
stp.Reset();
var result2 = new List<int>();
stp.Start();
for (int i = 0; i < totalElements; i += stepSize)
{
var currentBlock = testList.GetRange(i, stepSize);
result2.Add(currentBlock.Sum());
}
result2.ToList();
stp.Stop();
Console.WriteLine($"Method: GetRange, Time taken: {stp.ElapsedMilliseconds} ms, Elements: {totalElements}, Step Size: {stepSize}");
}
static void Main(字符串[]args)
{
var totalElements=1000000;
var testList=新列表(totalElements);
var rand=new Random();
//将列表初始化为1到1000之间的随机整数
对于(int i=0;i
问题在于跳过
的工作原理,这与GetRange
截然不同<代码>跳过始终从枚举开始,这意味着您正在执行以下操作:
Iteration #1: Skip 0
Iteration #2: Skip 1 * step
Iteration #3: Skip 2 * step
Iteration #4: Skip 3 * step
Iteration #5: Skip 4 * step
....
Iteration #1.000: Skip 9.999 * step
如果您对1.000.000个元素进行数学运算,并在100
中执行步骤,您将得到:
sum = 1 + 2 + 3 + .... + 9.999 = 9.999 * (9.999 + 1) / 2 = 49.995.000
total elements skipped: 49.995.000 * 100 = 4.999.500.000
因此,您的Linq版本有一个惊人的4.999.500.000
不必要的迭代
这里的一个好问题是:Skip
为什么没有针对source
实现IList
的情况进行优化,因为很明显,这是可能的。问题是Skip
如何工作,这与GetRange
有根本不同<代码>跳过
始终从枚举开始,这意味着您正在执行以下操作:
Iteration #1: Skip 0
Iteration #2: Skip 1 * step
Iteration #3: Skip 2 * step
Iteration #4: Skip 3 * step
Iteration #5: Skip 4 * step
....
Iteration #1.000: Skip 9.999 * step
如果您对1.000.000个元素进行数学运算,并在100
中执行步骤,您将得到:
sum = 1 + 2 + 3 + .... + 9.999 = 9.999 * (9.999 + 1) / 2 = 49.995.000
total elements skipped: 49.995.000 * 100 = 4.999.500.000
因此,您的Linq版本有一个惊人的4.999.500.000
不必要的迭代
这里的一个好问题是:Skip
为什么没有针对source
实现IList
的情况进行优化,因为很明显,这是可能的。GetRange使用Skip()。它总是从一开始就开始枚举。您想要的是一个函数,它可以将您的序列划分为块,而不需要对序列进行超出实际需要的迭代
这意味着,如果您只需要第一个块,那么函数的迭代次数不应该超过这个块,如果我需要第九个块之后的第十个块,那么它就不应该开始迭代
这个扩展功能怎么样
public static IEnumerable<IEnumerable<Tsource>> ToChuncks<TSource>(
this IEnumerable<TSource> source, int chunkSize)
{
while (source.Any()) // while there are elements left
{ // still something to chunk
// yield return a chunk
yield return source.Take(chunkSize); // return a chunk of chunkSize
// remove the chunk from the source
source = source.Skip(chunkSize); // skip the returned chunk
}
}
公共静态IEnumerable ToChuncks(
此IEnumerable源,int chunkSize)
{
while(source.Any())//还有元素时
{//还是有东西要拼吗
//返回一大块
yield返回source.Take(chunkSize);//返回chunkSize的块
//从源代码中删除块
source=source.Skip(chunkSize);//跳过返回的块
}
}
此函数重复检查源序列中是否还有剩余内容。如果是这样,它将返回一个数据块并从源代码中删除该数据块
这样,整个源代码最多迭代两次:一次是迭代块中的元素,一次是迭代块。GetRange使用Skip()。它总是从一开始就开始枚举。您想要的是一个函数,它可以将您的序列划分为块,而不需要对序列进行超出实际需要的迭代
这意味着,如果您只需要第一个块,那么函数的迭代次数不应该超过这个块,如果我需要第九个块之后的第十个块,那么它就不应该开始迭代
这个扩展功能怎么样
public static IEnumerable<IEnumerable<Tsource>> ToChuncks<TSource>(
this IEnumerable<TSource> source, int chunkSize)
{
while (source.Any()) // while there are elements left
{ // still something to chunk
// yield return a chunk
yield return source.Take(chunkSize); // return a chunk of chunkSize
// remove the chunk from the source
source = source.Skip(chunkSize); // skip the returned chunk
}
}
公共静态IEnumerable ToChuncks(
此IEnumerable源,int chunkSize)
{
while(source.Any())//还有元素时
{//还是有东西要拼吗
//返回一大块
yield返回source.Take(chunkSize);//返回chunkSize的块
//从源代码中删除块
source=source.Skip(chunkSize);//跳过返回的块
}
}
此函数重复检查源序列中是否还有剩余内容。如果是这样,它将返回一个数据块并从源代码中删除该数据块
这样,您的完整源代码最多迭代两次:一次是迭代块中的元素,另一次是迭代块。仅用很小的片段很难判断或进一步研究。请提供答案。我不认为这是一般“linq性能”问题的重复。26秒对9毫秒是极端的。有些奇怪的事情正在发生,可能是在我们看不到的代码中。@JonSkeet我已经添加了完整的源代码