奇怪的Powershell性能问题

时间:2009-05-04 10:00:17

标签: c# performance powershell

注意:这是Project Euler Problem 14的解决方案。如果您仍想自己解决,请不要继续阅读。

问题是找到低于一百万的数字,作为Collatz sequence的起始数,产生最长的这样的序列。我的初始代码如下:

$r = @{}

for($i = 1; $i -lt 1000000; $i++) {
    $n = 0
    $a = $i
    while ($a -gt 1) {
        if ($r[$a]) {
            $n += $r[$a]
            break
        }
        if ($a % 2 -eq 0) {
            $a /= 2
        } else {
            $a = $a * 3 + 1
        }
        $n++
    }
    $r[$i] = $n
}

$r.GetEnumerator() | sort Value | select -l 1 | %{$_.Key}

尝试将哈希表用作已经遇到的子序列的缓存以节省时间。当该脚本在我的机器上运行超过八分钟时,我感到非常惊讶。在C#中重新创建相同的代码:

using System;
using System.Collections.Generic;

class Problem14
{
    public static void Main()
    {
        var ht = new Dictionary<long, int>();

        for (int i = 1; i < 1000000; i++)
        {
            int count = 0;
            long j = i;

            while (j > 1)
            {
                if (ht.ContainsKey(j))
                {
                    count += ht[j];
                    break;
                }
                if (j % 2 == 0)
                    j /= 2;
                else
                    j = 3 * j + 1;

                count++;
            }
            ht[i] = count;
        }

        KeyValuePair<long, int> max = new KeyValuePair<long, int>();
        foreach (var n in ht)
        {
            if (n.Value > max.Value)
                max = n;
        }
        Console.WriteLine(max.Key);
    }
}

运行时间超过一秒钟。我知道执行速度不是Powershell的主要目标。它是一种管理语言,对于那些任务,PS代码与cmdlet的比例可能与我在这里做的非常不同。

不过,我不知道究竟是什么导致了经济放缓。

怀疑哈希表,我将其替换为使用数组进行缓存。这导致C#中的执行时间大约为200毫秒,而Powershell中的执行时间大约为32分钟。代码如下:

$r = ,0*1000000

for($i = 1; $i -lt 1000000; $i++) {
    $n = 0
    $a = $i
    while ($a -gt 1) {
        if ($r[$a]) {
            $n += $r[$a]
            break
        }
        if ($a % 2 -eq 0) {
            $a /= 2
        } else {
            $a = $a * 3 + 1
        }
        $n++
    }
    if ($i -lt 1000000) {
        $r[$i] = $n
    }
}

$max = 0
for($i=1; $i -lt 1000000; $i++) {
    if ($r[$i] > $r[$max]) {
        $max = $i
    }
}
$max

using System;

class Problem14
{
    public static void Main()
    {
        var cache = new int[1000000];

        for (int i = 1; i < 1000000; i++)
        {
            int count = 0;
            long j = i;

            while (j > 1)
            {
                if (j < 1000000 && cache[j] != 0)
                {
                    count += cache[j];
                    break;
                }
                if (j % 2 == 0)
                    j /= 2;
                else
                    j = 3 * j + 1;

                count++;
            }
            cache[i] = count;
        }

        var max = 0;
        for (int i = 1; i < cache.Length; i++)
        {
            if (cache[i] > cache[max])
                max = i;
        }

        Console.WriteLine(max);
    }
}

完全无缓存的变体在C#中大约是1.2秒。还没试过Powershell。

有什么想法吗?

1 个答案:

答案 0 :(得分:3)

首先,PowerShell是一种解释性语言(不是jitted,也不是编译)。那总是会受到伤害。 ; - )

其次,您可以使用一些可以避免多个解释步骤的语言结构。例如,不使用for(;;)语句,而是使用范围:

0..1000000 | foreach-object { foo $_ 

可能会有所帮助。

最重要的是,避免在循环中过度使用break和continue关键字 - 尽可能反转逻辑以避免这种情况。在内部,这些关键字使用.NET异常发出信号,因此是昂贵的操作。

希望这有助于你的理解。

编辑:这些关键字使用.NET例外信令

来自System.Management.Automation.FlowControlNode.Execute(...):

switch (this._tokenId)
    {
        case TokenId.ExitToken:
        {
            int exitCode = this.GetExitCode(result);
            throw new ExitException(base.NodeToken, exitCode);
        }
        case TokenId.ReturnToken:
            throw new ReturnException(base.NodeToken, result);

        case TokenId.BreakToken:
            label = this.GetLabel(result, context);
            throw new BreakException(base.NodeToken, label);

        case TokenId.ContinueToken:
            label = this.GetLabel(result, context);
            throw new ContinueException(base.NodeToken, label);

-Oisin