Я пытался решить головоломку "Слово Nubmers" ITA Software, используя подход грубой силы. Похоже, моя версия Haskell более чем в 10 раз медленнее, чем версия С#/С++.
Ответ
Благодаря Брайан О'Салливан ответ, я смог "исправить" свою программу до приемлемой производительности. Вы можете прочитать его код, который намного чище, чем мой. Здесь я расскажу о ключевых моментах.
-
Int
-Int64
в Linux GHC x64. Если вы неunsafeCoerce
, вы должны просто использоватьInt
. Это избавит вас от необходимостиfromIntegral
. ВыполнениеInt64
в Windows 32-бит GHC - это просто darn медленно, избегайте этого. (На самом деле это не ошибка GHC. Как уже упоминалось в моем сообщении в блоге ниже, 64-битные целые числа в 32-разрядных программах в целом медленны (по крайней мере, в Windows)) -
-fllvm
или-fvia-C
для производительности. - Предпочитает
quotRem
доdivMod
,quotRem
уже достаточно. Это дало мне ускорение на 20%. - В общем, предпочитайте
Data.Vector
Data.Array
как "массив" - Используйте шаблон обертки-работника либерально.
Вышеупомянутые моменты были достаточными, чтобы дать мне примерно 100% повышение по сравнению с моей оригинальной версией.
В своем сообщении в блоге я подробно описал пошаговый иллюстрированный пример того, как я превратил исходную программу в соответствие с программой Брайана, Здесь также упоминаются другие пункты.
Оригинальный вопрос
(Это может звучать как сообщение "могли бы вы сделать для меня", но я утверждаю, что такой конкретный пример был бы очень поучительным, так как профилирование производительности Haskell часто рассматривается как миф).
(Как отмечается в комментариях, я думаю, что я неправильно истолковал проблему, но кто заботится, мы можем сосредоточиться на производительности в другой проблеме)
Вот моя версия быстрого описания проблемы:
A wordNumber is defined as
wordNumber 1 = "one"
wordNumber 2 = "onetwo"
wordNumber 3 = "onethree"
wordNumber 15 = "onetwothreefourfivesixseveneightnineteneleventwelvethirteenfourteenfifteen"
...
Problem: Find the 51-billion-th letter of (wordNumber Infinity); assume that letter is found at 'wordNumber x', also find 'sum [1..x]'
С императивной точки зрения наивным алгоритмом было бы иметь 2 счетчика, один для суммы чисел и один для суммы длин. Продолжайте считать длину каждого словаNumber и "break", чтобы вернуть результат.
Настоящий подход грубой силы реализован на С# здесь: http://ideone.com/JjCb3. Ответ на мой компьютер занимает около 1,5 минут. Существует также реализация С++, которая выполняется через 45 секунд на моем компьютере.
Затем я применил версию Haskell с грубой силой: http://ideone.com/ngfFq. Он не может завершить расчет за 5 минут на моей машине. (Ирония: у нее больше строк, чем у версии С#)
Вот профиль -p
программы Haskell: http://hpaste.org/49934
Вопрос: Как сделать это в сравнении с версией С#? Существуют ли очевидные ошибки, которые я делаю?
(Примечание: я полностью осознаю, что грубое принуждение это не правильное решение этой проблемы. В основном я заинтересован в том, чтобы версия Haskell выполнялась сравнительно с версией С#. Сейчас она по меньшей мере на 5 раз медленнее, поэтому очевидно Мне не хватает чего-то очевидного)
(Примечание 2: Это не похоже на утечку пространства. Программа работает с постоянной памятью (около 2 МБ) на моем компьютере)
(Примечание 3: Я компилирую с помощью `ghc -O2 WordNumber.hs)
Чтобы сделать вопрос более дружественным к читателю, я включаю "суть" двух версий.
// C#
long sumNum = 0;
long sumLen = 0;
long target = 51000000000;
long i = 1;
for (; i < 999999999; i++)
{
// WordiLength(1) = 3 "one"
// WordiLength(101) = 13 "onehundredone"
long newLength = sumLen + WordiLength(i);
if (newLength >= target)
break;
sumNum += i;
sumLen = newLength;
}
Console.WriteLine(Wordify(i)[Convert.ToInt32(target - sumLen - 1)]);
-
-- Haskell
-- This has become totally ugly during my squeeze for
-- performance
-- Tail recursive
-- n-th number (51000000000 in our problem) -> accumulated result -> list of 'zipped' left to try
-- accumulated has the format (sum of numbers, current lengths of the whole chain, the current number)
solve :: Int64 -> (Int64, Int64, Int64) -> [(Int64, Int64)] -> (Int64, Int64, Int64)
solve !n [email protected](!sumNum, !sumLen, !curr) ((!num, !len):xs)
| sumLen' >= n = (sumNum', sumLen, num)
| otherwise = solve n (sumNum', sumLen', num) xs
where
sumNum' = sumNum + num
sumLen' = sumLen + len
-- wordLength 1 = 3 "one"
-- wordLength 101 = 13 "onehundredone"
wordLength :: Int64 -> Int64
-- wordLength = ...
solution :: Int64 -> (Int64, Char)
solution !x =
let (sumNum, sumLen, n) = solve x (0,0,1) (map (\n -> (n, wordLength n)) [1..])
in (sumNum, (wordify n) !! (fromIntegral $ x - sumLen - 1))