我正在尝试优化一个从给定指数计算完美数字的小程序。
该程序(几乎)完美运行,但是当我打开任务管理器时,它仍然在单个线程上运行。这意味着我一定做错了什么,但我对 F# 的了解仍处于“开始”阶段。
我会尽量把这个问题说清楚,但如果我没有这样做,请告诉我。
完美数是所有除数之和(除数字本身)等于数字本身的数字(例如,6 是完美的,因为它的除数 1、2 和 3 的总和是 6)。
我使用素数来加快计算速度,也就是说我对存储所有除数的(巨大的)列表不感兴趣。为此,我使用欧几里得证明是正确的公式: (2*(num - 1)) * ( 2* (num - 1)) 其中后者是梅森素数。我使用了来自stackoverflow(@Juliet)的一个非常快速的算法来确定给定的数字是否是素数。
当我在网上阅读了几篇文章(我还没有买一本好书,真丢脸)时,我发现序列比列表表现更好。所以这就是为什么我首先开始创建一个生成完美数字序列的函数:
let perfectNumbersTwo (n : int) =
seq { for i in 1..n do
if (PowShift i) - 1I |> isPrime
then yield PowShift (i-1) * ((PowShift i)-1I)
}
辅助函数 PowShift 实现如下:
let inline PowShift (exp:int32) = 1I <<< exp ;;
我使用位移运算符,因为所有功率计算的基础都是从 2 开始的,因此这可能是一种简单的方法。当然,我仍然很感谢我在以下问题上提出的问题的贡献:F# Power 问题,它接受两个参数都是 bigints> F# Power 问题,它接受两个参数都是 bigints
Juliet 创建的函数(这里借用)如下:
let isPrime ( n : bigint) =
let maxFactor = bigint(sqrt(float n))
let rec loop testPrime tog =
if testPrime > maxFactor then true
elif n % testPrime = 0I then false
else loop (testPrime + tog) (6I - tog)
if n = 2I || n = 3I || n = 5I then true
elif n <= 1I || n % 2I = 0I || n % 3I = 0I || n % 5I = 0I then false
else loop 7I 4I;;
使用此代码,无需并行,在我的笔记本电脑上大约需要 9 分钟才能找到第 9 个完美数字(由 37 位数字组成,可以找到指数值为 31)。由于我的笔记本电脑有一个带有两个内核的 CPU,并且只有一个以 50% 的速度运行(一个内核的满载),我认为我可以通过并行计算结果来加快计算速度。
所以我改变了我的完美数字功能如下:
//Now the function again, but async for parallel computing
let perfectNumbersAsync ( n : int) =
async {
try
for x in 1.. n do
if PowShift x - 1I |> isPrime then
let result = PowShift (x-1) * ((PowShift x)-1I)
printfn "Found %A as a perfect number" result
with
| ex -> printfn "Error%s" (ex.Message);
}
为了调用这个函数,我使用了一个小的辅助函数来运行它:
let runPerfects n =
[n]
|> Seq.map perfectNumbersAsync
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
异步计算的结果被忽略,因为我在 perfectNumbersAsync 函数中显示它。
上面的代码编译并运行,但它仍然只使用一个内核(尽管在计算第 9 个完美数时它运行速度快了 10 秒)。恐怕它与辅助函数 PowShift 和 isPrime 有关系,但我不确定。我是否必须将这些辅助函数的代码放在 perfectNumbersAsync 的异步块中?它不会提高可读性...
我玩 F# 的次数越多,我就越学会欣赏这种语言,但在这种情况下,我有时需要一些专家 :)。
提前感谢您阅读本文,我只希望我让自己有点清楚......
罗伯特。