Вопрос о производительности хвостовой рекурсии в Haskell для расстояний Левенштейна

Я играюсь с вычислением расстояний Левенштейна на Haskell и немного расстроен следующей проблемой производительности. Если реализовать это самым "нормальным" способом для Haskell, как показано ниже (dist), все работает просто отлично:

dist :: (Ord a) => [a] -> [a] -> Int
dist s1 s2 = ldist s1 s2 (L.length s1, L.length s2)

ldist :: (Ord a) => [a] -> [a] -> (Int, Int) -> Int
ldist _ _ (0, 0) = 0
ldist _ _ (i, 0) = i
ldist _ _ (0, j) = j
ldist s1 s2 (i+1, j+1) = output
  where output | (s1!!(i)) == (s2!!(j)) = ldist s1 s2 (i, j)
               | otherwise = 1 + L.minimum [ldist s1 s2 (i, j)
                                          , ldist s1 s2 (i+1, j)
                                          , ldist s1 s2 (i, j+1)]

Но, если немного пошевелить мозгами и реализовать это как dist', все выполняется НАМНОГО быстрее (примерно в 10 раз).

dist' :: (Ord a) => [a] -> [a] -> Int
dist' o1 o2 = (levenDist o1 o2 [[]])!!0!!0 

levenDist :: (Ord a) => [a] -> [a] -> [[Int]] -> [[Int]]
levenDist s1 s2 arr@([[]]) = levenDist s1 s2 [[0]]
levenDist s1 s2 arr@([]:xs) = levenDist s1 s2 ([(L.length arr) -1]:xs)
levenDist s1 s2 arr@(x:xs) = let
    n1 = L.length s1
    n2 = L.length s2
    n_i = L.length arr
    n_j = L.length x
    match | (s2!!(n_j-1) == s1!!(n_i-2)) = True | otherwise = False
    minCost = if match      then (xs!!0)!!(n2 - n_j + 1) 
                            else L.minimum [(1 + (xs!!0)!!(n2 - n_j + 1))
                                          , (1 + (xs!!0)!!(n2 - n_j + 0))
                                          , (1 + (x!!0))
                                          ]
    dist | (n_i > n1) && (n_j > n2)  = arr 
         | n_j > n2  = []:arr `seq` levenDist s1 s2 $ []:arr
         | n_i == 1 = (n_j:x):xs `seq` levenDist s1 s2 $ (n_j:x):xs
         | otherwise = (minCost:x):xs `seq` levenDist s1 s2 $ (minCost:x):xs
    in dist 

Я пробовал все обычные seq трюки в первой версии, но ничто, кажется, не ускоряет ее. Это меня немного не устраивает, потому что я ожидал, что первая версия будет быстрее, потому что ей не нужно оценивать всю матрицу, только те части, которые ей нужны.

Кто-нибудь знает, возможно ли заставить эти две реализации работать одинаково, или я просто пользуюсь преимуществами оптимизации хвостовой рекурсии в последней версии, и поэтому должен жить с ее нечитабельностью, если мне нужна производительность?

Спасибо, Orion

6
задан jdo 30 September 2010 в 14:35
поделиться