【问题标题】:How come this algorithm in Ruby runs faster than in Parallel'd C#?为什么 Ruby 中的这个算法比 Parallel'd C# 运行得更快?
【发布时间】:2012-11-24 14:24:09
【问题描述】:

以下 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 很慢。在算法方面不是这样吗?


修正后的性能:

  • Ruby(非并行):14.62 秒
  • C#(非并行):2.22s
  • C#(使用 TPL):0.64 秒

【问题讨论】:

  • 我在一分钟左右后杀死了 C# 方法。
  • 出于兴趣,您能否在不使用并行性的情况下运行 C# 版本,因此它与 ruby​​ 逻辑 1-1 匹配。因为这将是一个更公平的比较,我很想看看哪个更快。
  • @Grofit 我在做 Parallel.For 之前尝试过,它也很慢(但很难确切地说慢了多少)。我会运行它。
  • 非并行C#版本的时间是几点?只需做你在 Ruby 中所做的事情,但在 C# 中。我认为在这种情况下这是一个有效的问题。

标签: c# ruby performance algorithm task-parallel-library


【解决方案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
    }
});

你应该试试这段代码,我很确定这会更快地完成这项工作。

编辑:说明

好吧,我回答这个问题已经很久了,但我再次面对这个问题,终于明白发生了什么。

我一直在使用 Parallel for 循环的 AForge 实现,看起来,它会为循环的每次迭代触发一个线程,所以,这就是为什么如果循环执行的时间相对较短,你就结束了使用低效的并行性。

所以,正如你们中的一些人所指出的,System.Threading.Tasks.Parallel 方法是基于任务的,它们是线程的higher level of abstraction

“在幕后,任务排队到 ThreadPool,该线程池已通过确定和调整线程数量的算法得到增强,并提供负载平衡以最大化吞吐量。这使得任务相对轻量级,您可以创建许多以实现细粒度的并行性。”

所以是的,如果你使用默认库的实现,你就不需要使用这种“伪造”。

【讨论】:

  • @Baboon:发布了一个例子。
  • 您可能在此处出现整数溢出:[start + (end - start) * (n + 1) / N_CORES] 和此处:[start + (end - start) * n / N_CORES],如果 N_CORES 值很大。
  • 你的意思是溢出@ValtasarIII?如果N_CORES 很大,我真的不明白为什么它会让我溢出,甚至N_CORES 怎么可能这么大!?
  • TPL 没有必要经历这种麻烦,它会按照它认为合适的方式分配工作(您会注意到在大多数情况下它会触发比内核更多的线程)跨度>
  • 比我能想到的任何其他可能的方式都快。顺便说一句,事实证明这不是这个特定问题的最佳答案,但我不明白反对意见。
【解决方案2】:

其实这个bug很微妙,和线程无关。您的 C# 版本需要这么长时间的原因是 collatz 方法计算的中间值最终开始溢出 int 类型,导致负数可能需要很长时间才能收敛。

这首先发生在 i 为 134,379 时,其中第 129th 项(假设从 1 开始计数)为 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

【讨论】:

  • 太棒了,谢谢。我很快就注意到了一个负数,但我没有想到。
  • 哇! +100000! (还有几个零字符限制)
  • 我建议使用检查算法编译 C# 程序,至少在调试时是这样。在这种情况下,一个明显的迹象是它迅速通过is 直到它突然停止。这表明一个无限循环,最可能的原因是 int 溢出。
  • @Linuxios ,当心太多的零,如果溢出,你可能会否决答案......
  • @MarceloBiffara:在删除并行化(为了使测试公平)之后,在 x64 上以 .NET 4 为目标,我得到了 2.5 秒。我没有 Ruby 设置,但 OP 在他们的机器上报告了大约 15 秒。
【解决方案3】:

应该是:

Parallel.For(1L, 1000000L, i =>
    {

否则,您将出现整数溢出并开始检查负值。相同的collatz 方法应该使用长值。

【讨论】:

    猜你喜欢
    • 2013-11-21
    • 1970-01-01
    • 1970-01-01
    • 2011-03-22
    • 2019-11-01
    • 2023-03-05
    • 2019-02-14
    • 2018-12-30
    • 1970-01-01
    相关资源
    最近更新 更多