Нужна помощь в анализе кода и результатов профилирования - PullRequest
3 голосов
/ 08 января 2012

Я пытаюсь сделать функцию более эффективной, но я сделал это хуже, и я не мог понять, почему.Может кто-то увидеть почему и объяснить мне, пожалуйста?

Исходная функция:

substringsSB s = substringsSB' Set.empty s
substringsSB' m s = substrings' m s
  where
    substrings' m s  = {-# SCC "substrings'" #-}if (Set.member s m) then m else foldl' insertInits m (init . B.tails $ s)
    insertInits m s = {-# SCC "insertInits" #-}if (Set.member s m) then m else foldl' doInsert m (tail . B.inits $ s)
    doInsert m k = {-# SCC "doInsert" #-}Set.insert k m

Результат профилирования:

    total time  =        3.14 secs   (157 ticks @ 20 ms)
    total alloc = 1,642,067,360 bytes  (excludes profiling overheads)

COST CENTRE                    MODULE               %time %alloc

doInsert                       Main                  95.5   92.1
insertInits                    Main                   2.5    7.8
substringsSB'                  Main                   1.9    0.0


                                                                                               individual    inherited
COST CENTRE              MODULE                                               no.    entries  %time %alloc   %time %alloc

MAIN                     MAIN                                                   1           0   0.0    0.0   100.0  100.0
 main                    Main                                                 280           1   0.0    0.0   100.0  100.0
  substringsSB           Main                                                 281           1   0.0    0.0   100.0  100.0
   substringsSB'         Main                                                 282           1   1.9    0.0   100.0  100.0
    doInsert             Main                                                 285     1233232  95.5   92.1    95.5   92.1
    insertInits          Main                                                 284        1570   2.5    7.8     2.5    7.8
    substrings'          Main                                                 283           1   0.0    0.0     0.0    0.0
 CAF                     GHC.IO.Handle.FD                                     211           3   0.0    0.0     0.0    0.0
 CAF                     GHC.IO.Encoding.Iconv                                169           2   0.0    0.0     0.0    0.0
 CAF                     GHC.Conc.Signal                                      166           1   0.0    0.0     0.0    0.0

Насколько я знаю, мы не можем иметь рано- выйти в fold foldl, чтобы функция могла тратить много времени, просто вызывая Set.member s m и возвращая m в substrings'.Итак, я преобразовал функцию для использования рекурсии:

substringsSB s = substringsSB' Set.empty s
substringsSB' m str = substrings' m (init . B.tails $ str)
  where
    substrings' m [] = m
    substrings' m (s:ss) | Set.member s m = m
                         | otherwise      = {-# SCC "substrings'" #-}substrings' insertTail ss
                         where insertTail = insertInits m $ reverse $ (tail . B.inits $ s)
    insertInits m [] = m
    insertInits m (s:ss) | Set.member s m = m
                         | otherwise      = {-# SCC "insertInits" #-}insertInits (doInsert s m) ss
    doInsert k m = {-# SCC "doInsert" #-}Set.insert k m

Результат профилирования:

    total time  =        5.16 secs   (258 ticks @ 20 ms)
    total alloc = 1,662,535,200 bytes  (excludes profiling overheads)

COST CENTRE                    MODULE               %time %alloc

doInsert                       Main                  54.7   90.5
substringsSB'                  Main                  43.8    9.5
insertInits                    Main                   1.6    0.0


                                                                                               individual    inherited
COST CENTRE              MODULE                                               no.    entries  %time %alloc   %time %alloc

MAIN                     MAIN                                                   1           0   0.0    0.0   100.0  100.0
 main                    Main                                                 280           1   0.0    0.0   100.0  100.0
  substringsSB           Main                                                 281           1   0.0    0.0   100.0  100.0
   substringsSB'         Main                                                 282           1  43.8    9.5   100.0  100.0
    doInsert             Main                                                 285     1225600  54.7   90.5    54.7   90.5
    insertInits          Main                                                 284     1225600   1.6    0.0     1.6    0.0
    substrings'          Main                                                 283        1568   0.0    0.0     0.0    0.0
 CAF                     GHC.IO.Handle.FD                                     211           3   0.0    0.0     0.0    0.0
 CAF                     GHC.IO.Encoding.Iconv                                169           2   0.0    0.0     0.0    0.0
 CAF                     GHC.Conc.Signal                                      166           1   0.0    0.0     0.0    0.0

Но это займет больше времени, чем в исходной версии.Почему он так много времени проводит в substringsSB'?Это только делает init . B.tails $ str, что также называется в оригинальной версии ... Или я допустил ошибку, и эти две функции не являются логически эквивалентными?

main = do
  s <- getLine
  let m = substringsSB $ B.pack s
  print $ Set.size m
  return ()

с вводом:

asjasdfkjasdfjkasdjlflaasdfjklajsdflkjasvdadufhsaodifkljaiduhfjknhdfasjlkdfndbhfisjglkasnjjfgklsadmsjnhsjdflkmsnajjkdlsmfnjsdkfljasd;fjlkasdjfklasjdfnasdfjjnsadfjsadfhasjdfjlaksdfjlkasdfjljkasdflasidfjlaisjdflaisdjflaisjdfliasjdgfouqhagdfsia;klsjdfnklajsdfkhkasfhjdasdfhaskdflhjaklsdfh;kjlasdfh;jlaksdflkhajsdfkjahsdfkjhasdfkkasdfkjlkasfdkljasdfkhljkasdkflkjasdfasdlfkajsdlfkjaslkdfjjaksdjgujhgjhghjbjnbghjghhgfghfghvfgfgjhgjhdfjfjhgfjgvjhgvjhgvjhgvjhgvjhgvjhasdkfjkasdjfklajsdfklkahsdfjklhjklhghjhkhgfvcghjkjhghjkjhhvjkl/ljklkjlkjlkjlkjaslkdfjasd;lkfjas;dlfkjas;dflkjas;dflkjas;dflkjas;dflkja;slkdfja;sdlkjfa;sdlkfja;lsdfkjas;ldkfja;sdlkfja;skldfja;slkdjfa;slkdfja;sdklfjas;dlkfjas;dklfjas;dlkfjas;dfkljas;dflkjas;lkdfja;sldkfj;aslkdfja;sldkfja;slkdfj;alksdjf;alsdkfj;alsdkfja;sdflkja;sdflkja;sdlfkja;sdlfkja;sldkfja;sdlkfja;sldfkj;asldkfja;sldkfja;lsdkfja;sldfkja;sdlfjka;sdlfjkas;dlkfjas;ldkfjas;dlfkjasfd;lkjasd;fljkads;flkjasdf;lkjasdf;lkajsdf;lkajsdf;aksljdf;alksjdfa;slkdjfa;slkdjfa;slkdfja;sdflkjas;dflkjasd;flkjasd;flkjasdf;lkjasdf;ljkasdf;lkajdsf;laksjf;asldfkja;sdfljkads;flkjasd;fljkasdf;lkjasdf;ljkadfs;fljkadfs;ljkasdf;lajksdf;lkajsdf;lajsfd;laksdfgvjhgvjhgvjhcfjhgcjfgvjkgvjjgfjghfhgkhkjhbkjhbkjhbkybkkugtkydfktyufctkyckxckghfvkuygjkhbykutgtvkyckjhbliuhgktuyfkvuyjbjkjygvkuykjdjflaksdjflkajsdlkfjalskdjflkasjdflkjasdlkfjalksdjfklajsdflkjasdlkjfalksdjflkasjdflkjasdlfkjaslkdjflaksjdflkajsdlfkjasdlkfjalsdjflkasjdflkasjdflajsdfjsfuhaduvasdyhaweuisfnaysdfiuhasfdnhaksjdfahsdfiujknsadfhbaiuhdfjknahbdshfjksnashdfkjnsadfiukjfnhsdfkjnasdfikjansdfhnaksdjfaisdfkn

1 Ответ

1 голос
/ 08 января 2012

Печальная правда в том, что Set.member тоже дорогой.

В первой версии вы проверяете для каждого хвоста, был ли он замечен ранее, и если да, игнорируйте его, в противном случае вставьте все его непустые элементы. Если входные данные достаточно нерегулярны, то это O (n) тестов членства и O (n ^ 2) вставок, всего O (n ^ 2 * log n) (при условии, что O (1) средняя стоимость для сравнений). Если вход является периодическим с самым коротким (положительным) периодом p, только первые хвосты p приводят к вставкам, так что это O (n) тесты и O (p * n) вставки, O (p * n * log n) в целом (это немного обманут, средняя стоимость для сравнений может быть до O (p), если p> 1, и O (n), если p == 1, но если сам период нерегулярен, O (1) для сравнений в порядке) .

Во втором

substringsSB s = substringsSB' Set.empty s
substringsSB' m str = substrings' m (init . B.tails $ str)
  where
    substrings' m [] = m
    substrings' m (s:ss) | Set.member s m = m
                         | otherwise      = substrings' insertTail ss
                           where
                             insertTail = insertInits m $ reverse $ (tail . B.inits $ s)

вы проверяете для каждого хвоста, был ли он замечен ранее, если это так, остановитесь. Это хорошо, но не дает большого выигрыша по сравнению с первым В первом случае, если хвост был замечен ранее, все остальные хвосты также были замечены ранее, поэтому вы пропускаете только самое большее O (n) тестов членства, O (n *) журнал п) операции. Для обычно нерегулярного ввода, только несколько самых коротких хвостов были замечены ранее, поэтому пропускаются только несколько тестов - очень небольшое усиление.

    insertInits m [] = m
    insertInits m (s:ss) | Set.member s m = m
                         | otherwise      = insertInits (doInsert s m) ss
    doInsert k m = {-# SCC "doInsert" #-}Set.insert k m

Если хвост еще не виден (нормальный), вы начинаете вставлять его отрывки - от самых длинных до самых коротких - разрыв, если таковые были замечены ранее (потому что тогда все более короткие отряды также были замечены ранее). Это замечательно, если много длинных инициатов встречается несколько раз, но если нет, все, что у вас есть, - это O (n ^ 2) дополнительных тестов членства.

Для обычного нерегулярного ввода длинные подстроки не встречаются несколько раз, но есть несколько коротких подстрок, и несколько сохраненных вставок не компенсируют дополнительные тесты членства, что делает второй метод более медленным с постоянным коэффициентом. (Членское тестирование дешевле, чем ввод, поэтому коэффициент должен быть меньше 2.)

Для периодического ввода первый метод также позволяет избежать ненужных вставок, второй сохраняет O (n) тесты во внешнем цикле, но добавляет O (p * n) тесты во внутреннем цикле, что делает его несколько хуже, чем в нерегулярном. случай.

Но для некоторых входов второй метод может быть значительно лучше. Попробуйте оба для

main = do
    let x = substringsSB $ B.pack $ replicate 9999 97 ++ [98]
    print (Set.size x)

Вы можете улучшить вторую версию, заменив дорогой member перед вставкой на дешевое size сравнение после вставки,

substringsSB str = go 0 Set.empty (init $ B.tails str)
  where
    go sz m (s:ss)
        | Set.member s m = m
        | otherwise      = go nsz nm ss
          where
            (nsz,nm) = insInits sz m (reverse . tail $ B.inits s)
    go _ m [] = m
    insInits sz m (s:ss)
        | sz1 == sz     = (sz,m)
        | otherwise     = insInits sz1 nm ss
          where
            nm = Set.insert s m
            sz1 = Set.size nm
    insInits sz m [] = (sz,m)

Это приближает его к первой версии в общем случае, делает его немного лучше (здесь), чем первая версия для concat $ replicate n "abcde" и намного лучше для приведенного выше примера зла.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...