生成质数序列,Loop Break vs LINQ TakeWhile
Generate prime number sequences, Loop Break vs LINQ TakeWhile
我尝试用 C# 生成素数序列。
到目前为止,代码似乎运行良好。
List<int> _primes = new List<int>();
bool IsPrime(int num)
{
if (num < 2)
return false;
int chkEnd = (int)Math.Sqrt(num);
foreach (var p in _primes)
{
if (p > chkEnd)
break;
if (num % p == 0)
return false;
}
return true;
}
for (int i=2; i<1000000; i++)
{
if (IsPrime(i))
_primes.Add(i);
}
然后我将 IsPrime() 更改为使用 LINQ TakeWhile(),
我认为这两种方法具有相同的时间复杂度,
但是执行时间明显比前者长。
bool IsPrime(int num)
{
if (num < 2)
return false;
int chkEnd = (int)Math.Sqrt(num);
foreach (var p in _primes.TakeWhile(x => x <= chkEnd))
{
if (num % p == 0)
return false;
}
return true;
}
或
bool IsPrime(int num)
{
if (num < 2)
return false;
int chkEnd = (int)Math.Sqrt(num);
return _primes.TakeWhile(x => x <= chkEnd).All(p => num % p != 0);
}
有人知道吗?
编辑:
我将循环迭代增加到 5000000,运行 consoleApplication 中的代码并输出执行时间
00:00:01.1841593 -- IsPrime_PureLoopBreak
00:00:03.2560654 -- IsPrime_TakeWhileAndLoop
00:00:03.4178782 -- IsPrime_TakeWhileAndAll
static void Main(string[] args)
{
List<int> _primes;
bool IsPrime_PureLoopBreak(int num)
{
if (num < 2)
return false;
int chkEnd = (int)Math.Sqrt(num);
foreach (var p in _primes)
{
if (p > chkEnd)
break;
if (num % p == 0)
return false;
}
return true;
}
bool IsPrime_TakeWhileAndLoop(int num)
{
if (num < 2)
return false;
int chkEnd = (int)Math.Sqrt(num);
foreach (var p in _primes.TakeWhile(x => x <= chkEnd))
{
if (num % p == 0)
return false;
}
return true;
}
bool IsPrime_TakeWhileAndAll(int num)
{
if (num < 2)
return false;
int chkEnd = (int)Math.Sqrt(num);
return _primes.TakeWhile(x => x <= chkEnd).All(p => num % p != 0);
}
var t1 = Measure(() =>
{
_primes = new List<int>();
for (int i = 2; i < 5000000; i++)
{
if (IsPrime_PureLoopBreak(i))
_primes.Add(i);
}
});
Console.WriteLine($"{t1} -- IsPrime_PureLoopBreak");
var t2 = Measure(() =>
{
_primes = new List<int>();
for (int i = 2; i < 5000000; i++)
{
if (IsPrime_TakeWhileAndLoop(i))
_primes.Add(i);
}
});
Console.WriteLine($"{t2} -- IsPrime_TakeWhileAndLoop");
var t3 = Measure(() =>
{
_primes = new List<int>();
for (int i = 2; i < 5000000; i++)
{
if (IsPrime_TakeWhileAndAll(i))
_primes.Add(i);
}
});
Console.WriteLine($"{t3} -- IsPrime_TakeWhileAndAll");
Console.ReadLine();
}
public static TimeSpan Measure(Action action)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
action?.Invoke();
stopwatch.Stop();
return stopwatch.Elapsed;
}
使用 LINQ 有一些额外的开销。您正在创建和调用 lambda 表达式。
但是,在得出结论之前,请务必尝试发布版本。
考虑编译器需要生成的代码。像 Linq 这样的“高级”代码简单易写,但编译器通常更难优化。
您之前的示例可以进一步简化为使用普通的 for 循环而不是 foreach。所以每次迭代应该只需要一些指令。
使用 linq 时,编译器将为迭代器生成一个代理对象。这将需要调用您的 lambda 以检查它是否需要继续迭代,并且编译器可能无法内联方法调用。方法调用很便宜,但仍然比简单的算术指令贵几倍。
因此,根据经验,使用 Linq 和其他高级模式来提高可读性。如果您发现代码的某些部分没有达到性能目标,请使用分析器准确确定哪个部分需要时间,然后重写该部分,直到它足够快或者您已使其尽可能快。
我尝试用 C# 生成素数序列。 到目前为止,代码似乎运行良好。
List<int> _primes = new List<int>();
bool IsPrime(int num)
{
if (num < 2)
return false;
int chkEnd = (int)Math.Sqrt(num);
foreach (var p in _primes)
{
if (p > chkEnd)
break;
if (num % p == 0)
return false;
}
return true;
}
for (int i=2; i<1000000; i++)
{
if (IsPrime(i))
_primes.Add(i);
}
然后我将 IsPrime() 更改为使用 LINQ TakeWhile(), 我认为这两种方法具有相同的时间复杂度, 但是执行时间明显比前者长。
bool IsPrime(int num)
{
if (num < 2)
return false;
int chkEnd = (int)Math.Sqrt(num);
foreach (var p in _primes.TakeWhile(x => x <= chkEnd))
{
if (num % p == 0)
return false;
}
return true;
}
或
bool IsPrime(int num)
{
if (num < 2)
return false;
int chkEnd = (int)Math.Sqrt(num);
return _primes.TakeWhile(x => x <= chkEnd).All(p => num % p != 0);
}
有人知道吗?
编辑: 我将循环迭代增加到 5000000,运行 consoleApplication 中的代码并输出执行时间
00:00:01.1841593 -- IsPrime_PureLoopBreak
00:00:03.2560654 -- IsPrime_TakeWhileAndLoop
00:00:03.4178782 -- IsPrime_TakeWhileAndAll
static void Main(string[] args)
{
List<int> _primes;
bool IsPrime_PureLoopBreak(int num)
{
if (num < 2)
return false;
int chkEnd = (int)Math.Sqrt(num);
foreach (var p in _primes)
{
if (p > chkEnd)
break;
if (num % p == 0)
return false;
}
return true;
}
bool IsPrime_TakeWhileAndLoop(int num)
{
if (num < 2)
return false;
int chkEnd = (int)Math.Sqrt(num);
foreach (var p in _primes.TakeWhile(x => x <= chkEnd))
{
if (num % p == 0)
return false;
}
return true;
}
bool IsPrime_TakeWhileAndAll(int num)
{
if (num < 2)
return false;
int chkEnd = (int)Math.Sqrt(num);
return _primes.TakeWhile(x => x <= chkEnd).All(p => num % p != 0);
}
var t1 = Measure(() =>
{
_primes = new List<int>();
for (int i = 2; i < 5000000; i++)
{
if (IsPrime_PureLoopBreak(i))
_primes.Add(i);
}
});
Console.WriteLine($"{t1} -- IsPrime_PureLoopBreak");
var t2 = Measure(() =>
{
_primes = new List<int>();
for (int i = 2; i < 5000000; i++)
{
if (IsPrime_TakeWhileAndLoop(i))
_primes.Add(i);
}
});
Console.WriteLine($"{t2} -- IsPrime_TakeWhileAndLoop");
var t3 = Measure(() =>
{
_primes = new List<int>();
for (int i = 2; i < 5000000; i++)
{
if (IsPrime_TakeWhileAndAll(i))
_primes.Add(i);
}
});
Console.WriteLine($"{t3} -- IsPrime_TakeWhileAndAll");
Console.ReadLine();
}
public static TimeSpan Measure(Action action)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
action?.Invoke();
stopwatch.Stop();
return stopwatch.Elapsed;
}
使用 LINQ 有一些额外的开销。您正在创建和调用 lambda 表达式。
但是,在得出结论之前,请务必尝试发布版本。
考虑编译器需要生成的代码。像 Linq 这样的“高级”代码简单易写,但编译器通常更难优化。
您之前的示例可以进一步简化为使用普通的 for 循环而不是 foreach。所以每次迭代应该只需要一些指令。
使用 linq 时,编译器将为迭代器生成一个代理对象。这将需要调用您的 lambda 以检查它是否需要继续迭代,并且编译器可能无法内联方法调用。方法调用很便宜,但仍然比简单的算术指令贵几倍。
因此,根据经验,使用 Linq 和其他高级模式来提高可读性。如果您发现代码的某些部分没有达到性能目标,请使用分析器准确确定哪个部分需要时间,然后重写该部分,直到它足够快或者您已使其尽可能快。