以下ruby代码在〜15秒内运行。它几乎不使用任何CPU /内存(大约占一个CPU的25%):
def collatz(num)
num.even? ? num/2 : 3*num + 1
end
start_time = Time.now
max_chain_count = 0
max_starter_num = 0
(1..1000000).each do |i|
count = 0
current = i
current = collatz(current) and count += 1 until (current == 1)
max_chain_count = count and max_starter_num = i if (count > max_chain_count)
end
puts "Max starter num: #{max_starter_num} -> chain of #{max_chain_count} elements. Found in: #{Time.now - start_time}s"
以下TPL C#将我的所有4个内核的使用率提高到100%,并且比ruby版本慢了几个数量级:
static void Euler14Test()
{
Stopwatch sw = new Stopwatch();
sw.Start();
int max_chain_count = 0;
int max_starter_num = 0;
object locker = new object();
Parallel.For(1, 1000000, i =>
{
int count = 0;
int current = i;
while (current != 1)
{
current = collatz(current);
count++;
}
if (count > max_chain_count)
{
lock (locker)
{
max_chain_count = count;
max_starter_num = i;
}
}
if (i % 1000 == 0)
Console.WriteLine(i);
});
sw.Stop();
Console.WriteLine("Max starter i: {0} -> chain of {1} elements. Found in: {2}s", max_starter_num, max_chain_count, sw.Elapsed.ToString());
}
static int collatz(int num)
{
return num % 2 == 0 ? num / 2 : 3 * num + 1;
}
为什么ruby的运行速度比C#快?我被告知Ruby很慢。在算法方面,这不是真的吗?
Perf AFTER校正:
答案 0 :(得分:30)
实际上,这个bug非常微妙,与线程无关。你的C#版本花了这么长时间的原因是collatz
方法计算的中间值最终开始溢出int
类型,导致负数可能需要很长时间才能收敛。
第一次发生在i
为134,379时,129 th 项(假设一次计数)为2,482,111,348。这超过了最大值2,147,483,647,因此存储为-1,812,855,948。
要在C#版本上获得良好的性能(和正确的结果),只需更改:
int current = i;
...为:
long current = i;
...和
static int collatz(int num)
...为:
static long collatz(long num)
这将使你的表现降低到可观的1.5秒。
编辑:在调试面向数学的应用程序时,CodesInChaos提出了一个关于启用溢出检查的非常有效的观点。这样做可以立即识别错误,因为运行时会抛出OverflowException
。
答案 1 :(得分:6)
应该是:
Parallel.For(1L, 1000000L, i =>
{
否则,您有整数溢出并开始检查负值。相同的collatz
方法应该使用长值。
答案 2 :(得分:-1)
我经历过类似的事情。我发现这是因为你的每个循环迭代都需要启动其他线程,这需要一些时间,在这种情况下,它与你在循环体中执行的操作相当(我认为这是更多的时间)。
还有一个替代方案:您可以获得多少CPU内核,而不是使用具有相同内核迭代次数的并行循环,每个循环将评估您想要的部分实际循环,它由制作一个取决于并行循环的内部for循环。
编辑:示例
int start = 1, end = 1000000;
Parallel.For(0, N_CORES, n =>
{
int s = start + (end - start) * n / N_CORES;
int e = n == N_CORES - 1 ? end : start + (end - start) * (n + 1) / N_CORES;
for (int i = s; i < e; i++)
{
// Your code
}
});
你应该试试这个代码,我很确定这会更快地完成这项工作。
编辑:ELUCIDATION
好吧,自从我回答这个问题以来已经很长时间了,但我再次面对这个问题,最后明白了发生了什么。
我一直在使用并行循环的AForge实现,看起来,它会为循环的每次迭代触发一个线程,所以,这就是为什么如果循环需要相对较少的时间来执行,那么你就结束了低效的并行性。
因此,正如你们中的一些人指出的那样,System.Threading.Tasks.Parallel方法基于Tasks,它们是一个线程的higher level of abstraction:
“在幕后,任务排队到ThreadPool,后者已经使用算法进行了增强,这些算法可以确定并调整线程数,并提供负载平衡以最大化吞吐量。这使得任务相对轻量级,并且您可以创建许多它们可以实现细粒度的并行性。“
所以是的,如果你使用默认库的实现,你将不需要使用这种“虚假”。