我正在Haskell中实现拓扑排序,要求尽可能高效。我已经描述了我当前的解决方案,并发现以下方法占用了总时间的60%(以及0额外空间量):
import Control.Monad.ST
import Control.Monad
import Data.Array.ST
import Data.Array.Unboxed
import Data.Word
import Data.Array.Base
zeroElementsAfterDecrement' :: (MArray a e m, Num e, Eq e) => a Int e -> [Int] -> m [Int]
zeroElementsAfterDecrement' arr is = foldr k (return []) is
where k i a = do xs <- a
decremented <- liftM (subtract 1) (unsafeRead arr i)
unsafeWrite arr i decremented
if decremented == 0 then return (i:xs) else return xs
largenum :: Int
largenum = 10000000
test = runST $ do arr <- newArray (1, largenum) 100 :: ST s (STUArray s Int Word32)
zeroElementsAfterDecrement' arr [1..largenum]
main = (putStrLn . show) test
该函数接受一个数组(我使用未装箱的可变数组)和索引列表,按这些索引递减元素,并返回在此操作期间变为零的元素的索引。现在这比优化的C ++代码慢了10多倍,但与Python相比仍然相当不错(或者我可能不知道Python的优化方式)。我理解执行monadic代码会产生一些开销,但也许仍有优化方法我不知道?
修改
GHC:-O -fllvm:0.54s
GHC(使用unsafeWrite / unsafeRead和Word32):0.34s
g ++:0.24s
g ++ -O2:0.05s
python3:2.66s
此外,当我将foldr更改为foldl'时,它开始分配一些内存,结果慢了4倍,为什么会这样?
这是一个C ++版本我把它比作:
#include <iostream>
#include <vector>
using namespace std;
#define LARGENUM 10000000
int main()
{
vector <int> arr;
for (int i = 0; i < LARGENUM; i++) {
arr.push_back(100);
}
for (int i = 0; i < arr.size(); i++) {
arr[i]--;
if (arr[i] == 0)
cout << i << endl;
}
return 0;
}
和Python版本:
arr = [100] * 10000000
for x in range (0, 10000000 - 1):
arr[x] = arr[x] - 1
if arr[x] == 0:
print x