কেন এই হাস্কেল কোডটি ও-এর সাথে ধীরে চলবে?


87

হাস্কেল কোডের এই টুকরাটি আরও ধীর গতিতে চলে -Oতবে -Oএটি অ-বিপজ্জনক হওয়া উচিত । কেউ কি বলতে পারেন কি হয়েছে? যদি এটি গুরুত্বপূর্ণ হয় তবে এটি এই সমস্যাটি সমাধান করার চেষ্টা এবং এটি বাইনারি অনুসন্ধান এবং অবিচ্ছিন্ন সেগমেন্ট ট্রি ব্যবহার করে:

import Control.Monad
import Data.Array

data Node =
      Leaf   Int           -- value
    | Branch Int Node Node -- sum, left child, right child
type NodeArray = Array Int Node

-- create an empty node with range [l, r)
create :: Int -> Int -> Node
create l r
    | l + 1 == r = Leaf 0
    | otherwise  = Branch 0 (create l m) (create m r)
    where m = (l + r) `div` 2

-- Get the sum in range [0, r). The range of the node is [nl, nr)
sumof :: Node -> Int -> Int -> Int -> Int
sumof (Leaf val) r nl nr
    | nr <= r   = val
    | otherwise = 0
sumof (Branch sum lc rc) r nl nr
    | nr <= r   = sum
    | r  > nl   = (sumof lc r nl m) + (sumof rc r m nr)
    | otherwise = 0
    where m = (nl + nr) `div` 2

-- Increase the value at x by 1. The range of the node is [nl, nr)
increase :: Node -> Int -> Int -> Int -> Node
increase (Leaf val) x nl nr = Leaf (val + 1)
increase (Branch sum lc rc) x nl nr
    | x < m     = Branch (sum + 1) (increase lc x nl m) rc
    | otherwise = Branch (sum + 1) lc (increase rc x m nr)
    where m = (nl + nr) `div` 2

-- signature said it all
tonodes :: Int -> [Int] -> [Node]
tonodes n = reverse . tonodes' . reverse
    where
        tonodes' :: [Int] -> [Node]
        tonodes' (h:t) = increase h' h 0 n : s' where s'@(h':_) = tonodes' t
        tonodes' _ = [create 0 n]

-- find the minimum m in [l, r] such that (predicate m) is True
binarysearch :: (Int -> Bool) -> Int -> Int -> Int
binarysearch predicate l r
    | l == r      = r
    | predicate m = binarysearch predicate l m
    | otherwise   = binarysearch predicate (m+1) r
    where m = (l + r) `div` 2

-- main, literally
main :: IO ()
main = do
    [n, m] <- fmap (map read . words) getLine
    nodes <- fmap (listArray (0, n) . tonodes n . map (subtract 1) . map read . words) getLine
    replicateM_ m $ query n nodes
    where
        query :: Int -> NodeArray -> IO ()
        query n nodes = do
            [p, k] <- fmap (map read . words) getLine
            print $ binarysearch (ok nodes n p k) 0 n
            where
                ok :: NodeArray -> Int -> Int -> Int -> Int -> Bool
                ok nodes n p k s = (sumof (nodes ! min (p + s + 1) n) s 0 n) - (sumof (nodes ! max (p - s) 0) s 0 n) >= k

( কোড পর্যালোচনা সহ এটি ঠিক একই কোড তবে এই প্রশ্নটি অন্য একটি সমস্যার সমাধান করে))

এটি সি ++ তে আমার ইনপুট জেনারেটর:

#include <cstdio>
#include <cstdlib>
using namespace std;
int main (int argc, char * argv[]) {
    srand(1827);
    int n = 100000;
    if(argc > 1)
        sscanf(argv[1], "%d", &n);
    printf("%d %d\n", n, n);
    for(int i = 0; i < n; i++)
        printf("%d%c", rand() % n + 1, i == n - 1 ? '\n' : ' ');
    for(int i = 0; i < n; i++) {
        int p = rand() % n;
        int k = rand() % n + 1;
        printf("%d %d\n", p, k);
    }
}

যদি আপনার কাছে সি ++ সংকলক উপলব্ধ না থাকে তবে এটি ফলাফল./gen.exe 1000

এটি আমার কম্পিউটারে মৃত্যুদন্ড কার্যকর:

$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 7.8.3
$ ghc -fforce-recomp 1827.hs
[1 of 1] Compiling Main             ( 1827.hs, 1827.o )
Linking 1827.exe ...
$ time ./gen.exe 1000 | ./1827.exe > /dev/null
real    0m0.088s
user    0m0.015s
sys     0m0.015s
$ ghc -fforce-recomp -O 1827.hs
[1 of 1] Compiling Main             ( 1827.hs, 1827.o )
Linking 1827.exe ...
$ time ./gen.exe 1000 | ./1827.exe > /dev/null
real    0m2.969s
user    0m0.000s
sys     0m0.045s

এবং এটি হিপ প্রোফাইলের সারাংশ:

$ ghc -fforce-recomp -rtsopts ./1827.hs
[1 of 1] Compiling Main             ( 1827.hs, 1827.o )
Linking 1827.exe ...
$ ./gen.exe 1000 | ./1827.exe +RTS -s > /dev/null
      70,207,096 bytes allocated in the heap
       2,112,416 bytes copied during GC
         613,368 bytes maximum residency (3 sample(s))
          28,816 bytes maximum slop
               3 MB total memory in use (0 MB lost due to fragmentation)
                                    Tot time (elapsed)  Avg pause  Max pause
  Gen  0       132 colls,     0 par    0.00s    0.00s     0.0000s    0.0004s
  Gen  1         3 colls,     0 par    0.00s    0.00s     0.0006s    0.0010s
  INIT    time    0.00s  (  0.00s elapsed)
  MUT     time    0.03s  (  0.03s elapsed)
  GC      time    0.00s  (  0.01s elapsed)
  EXIT    time    0.00s  (  0.00s elapsed)
  Total   time    0.03s  (  0.04s elapsed)
  %GC     time       0.0%  (14.7% elapsed)
  Alloc rate    2,250,213,011 bytes per MUT second
  Productivity 100.0% of total user, 83.1% of total elapsed
$ ghc -fforce-recomp -O -rtsopts ./1827.hs
[1 of 1] Compiling Main             ( 1827.hs, 1827.o )
Linking 1827.exe ...
$ ./gen.exe 1000 | ./1827.exe +RTS -s > /dev/null
   6,009,233,608 bytes allocated in the heap
     622,682,200 bytes copied during GC
         443,240 bytes maximum residency (505 sample(s))
          48,256 bytes maximum slop
               3 MB total memory in use (0 MB lost due to fragmentation)
                                    Tot time (elapsed)  Avg pause  Max pause
  Gen  0     10945 colls,     0 par    0.72s    0.63s     0.0001s    0.0004s
  Gen  1       505 colls,     0 par    0.16s    0.13s     0.0003s    0.0005s
  INIT    time    0.00s  (  0.00s elapsed)
  MUT     time    2.00s  (  2.13s elapsed)
  GC      time    0.87s  (  0.76s elapsed)
  EXIT    time    0.00s  (  0.00s elapsed)
  Total   time    2.89s  (  2.90s elapsed)
  %GC     time      30.3%  (26.4% elapsed)
  Alloc rate    3,009,412,603 bytes per MUT second
  Productivity  69.7% of total user, 69.4% of total elapsed

4
জিএইচসি সংস্করণ অন্তর্ভুক্ত করার জন্য আপনাকে ধন্যবাদ!
dfeuer

4
@dfeuer ফলাফলটি এখন আমার প্রশ্নের মধ্যে অন্তর্ভুক্ত।
johnchen902

13
আরও একটি বিকল্প চেষ্টা: -fno-state-hack। তারপরে আমাকে আসলে বিশদ অনুসন্ধানের চেষ্টা করতে হবে।
dfeuer

17
আমি খুব বেশি বিশদ জানি না, তবে মূলত এটি অনুমান করার জন্য একটি হিউরিস্টিক যে আপনার প্রোগ্রামটি যে নির্দিষ্ট ফাংশনগুলি তৈরি করে (যথা নামগুলিতে IOবা STপ্রকারের মধ্যে লুকানো থাকে ) কেবল একবার কল হয়ে যায়। এটি সাধারণত একটি ভাল অনুমান, তবে এটি যখন খারাপ অনুমান হয় তখন জিএইচসি খুব খারাপ কোড তৈরি করতে পারে। বিকাশকারীরা দীর্ঘদিন ধরে খারাপ ছাড়া ভাল করার জন্য কোনও উপায় খোঁজার চেষ্টা করছেন। আমি মনে করি জোচিম ব্রেটনার আজকাল এটিতে কাজ করছেন।
dfeuer

4
এটি দেখতে অনেকটা ghc.haskell.org/trac/ghc/ticket/10102 এর মতো লাগে । নোট করুন যে উভয় প্রোগ্রামই ব্যবহার করে replicateM_এবং সেখানে জিএইচসি ভুলভাবে গণনাটি বাইরে replicateM_থেকে এর ভিতরে নিয়ে যায়, তাই এটি পুনরাবৃত্তি করে।
জোয়াচিম ব্রেটনার

উত্তর:


42

আমার ধারণা এই সময়টি এই প্রশ্নের উপযুক্ত উত্তর পেয়েছে।

আপনার কোডটি দিয়ে কী ঘটেছে -O

আমাকে আপনার মূল ফাংশনটি জুম করতে দিন এবং এটি আবার সামান্য লিখুন:

main :: IO ()
main = do
    [n, m] <- fmap (map read . words) getLine
    line <- getLine
    let nodes = listArray (0, n) . tonodes n . map (subtract 1) . map read . words $ line
    replicateM_ m $ query n nodes

স্পষ্টতই, এখানে উদ্দেশ্যটি হ'ল NodeArrayএকবার তৈরি করা হয়েছে, এবং তারপরে প্রতিটি mআমন্ত্রণে ব্যবহৃত হয় query

দুর্ভাগ্যক্রমে, জিএইচসি এই কোডটি কার্যকরভাবে,

main = do
    [n, m] <- fmap (map read . words) getLine
    line <- getLine
    replicateM_ m $ do
        let nodes = listArray (0, n) . tonodes n . map (subtract 1) . map read . words $ line
        query n nodes

এবং আপনি এখানে তাত্ক্ষণিকভাবে সমস্যাটি দেখতে পারেন।

রাষ্ট্র হ্যাক কী এবং কেন এটি আমার প্রোগ্রামগুলির কর্মক্ষমতা নষ্ট করে

কারণ হ'ল রাষ্ট্র হ্যাক, যা বলে ("মোটামুটি):" যখন কোনও কিছু টাইপ হয় তখন IO aধরে নিন যে এটি কেবল একবার বলা হয়েছে ”" সরকারী ডকুমেন্টেশন আরো অনেক কিছু সম্প্রসারিত নয়:

-fno-state-hack

"স্টেট হ্যাক" বন্ধ করুন যার মাধ্যমে কোনও রাজ্যের # টোকেনযুক্ত যে কোনও ল্যাম্বদা যুক্তি হিসাবে একক-প্রবেশ হিসাবে বিবেচিত হয়, তাই এর ভিতরে থাকা জিনিসগুলি ইনলাইন করা ঠিক আছে বলে মনে করা হয়। এটি আইও এবং এসটি মোনাড কোডের কার্যকারিতা উন্নত করতে পারে, তবে এটি ভাগ করে নেওয়ার ঝুঁকি চালায় runs

মোটামুটিভাবে, ধারণাটি নিম্নরূপ: আপনি যদি কোনও IOধরণের এবং কোনও ধারা সহ কোনও ফাংশন সংজ্ঞায়িত করেন , যেমন

foo x = do
    putStrLn y
    putStrLn y
  where y = ...x...

কিছু ধরণের কিছু টাইপ IO aহিসাবে দেখা যেতে পারে RealWord -> (a, RealWorld)। এই দৃষ্টিতে উপরেরগুলি (মোটামুটিভাবে) হয়ে যায়

foo x = 
   let y = ...x... in 
   \world1 ->
     let (world2, ()) = putStrLn y world1
     let (world3, ()) = putStrLn y world2
     in  (world3, ())

একটি কল foo(সাধারণত) এর মতো দেখতে হবে foo argument world। তবে সংজ্ঞাটি fooকেবল একটি যুক্তি নেয়, এবং অন্যটি কেবল পরে স্থানীয় ল্যাম্বডা এক্সপ্রেশন দ্বারা গ্রাস করা হয়! এটি খুব ধীর কল হতে চলেছে foo। কোডটি দেখতে দেখতে এটি আরও দ্রুত হবে:

foo x world1 = 
   let y = ...x... in 
   let (world2, ()) = putStrLn y world1
   let (world3, ()) = putStrLn y world2
   in  (world3, ())

এটাকে এক্সটেনশন বলা হয় এবং বিভিন্ন ভিত্তিতে সম্পন্ন করা হয় (উদাহরণস্বরূপ ফাংশনটির সংজ্ঞা বিশ্লেষণ করে , এটি কীভাবে ডাকা হচ্ছে তা যাচাই করে , এবং - এই ক্ষেত্রে - নির্দেশিত হিউরিস্টিক টাইপ করুন)।

দুর্ভাগ্যক্রমে, যদি কলটি কলটি fooপ্রকৃতরূপে হয় let fooArgument = foo argument, অর্থাত্ একটি যুক্তি সহ, তবে worldপাস হয়নি (এখনও)। মূল কোডে, যদি fooArgumentএরপরে বেশ কয়েকবার ব্যবহার করা হয় তবে yএখনও একবারেই গণনা করা হবে এবং ভাগ করা হবে। পরিবর্তিত কোডে, yপ্রতিবার পুনরায় গণনা করা হবে - আপনার কী হয়েছে তা অবিকল nodes

জিনিস কি স্থির করা যায়?

সম্ভবত। এটি করার চেষ্টা করার জন্য # 9388 দেখুন । এটি সংশোধন করার ক্ষেত্রে সমস্যাটি হ'ল এটির অনেকগুলি ক্ষেত্রে পারফরম্যান্সের জন্য ব্যয় হবে যেখানে রূপান্তরটি ঠিক হয়ে যায়, যদিও সংকলক সম্ভবত এটি নিশ্চিতভাবে জানতে পারে না। এবং সম্ভবত এমন কেস রয়েছে যেখানে প্রযুক্তিগতভাবে এটি ঠিক নয়, অর্থাত্ ভাগ করে নেওয়ার বিষয়টি হারিয়ে যায়, তবে এটি এখনও উপকারী কারণ দ্রুত কলিংয়ের স্পিডআপগুলি পুনর্বিবেচনার অতিরিক্ত ব্যয়কে ছাড়িয়ে যায়। সুতরাং এখান থেকে কোথায় যাবেন তা পরিষ্কার নয়।


4
খুব আকর্ষণীয়! তবে আমি এতটা ভাল করে বুঝতে পারি নি কেন: "অন্যটি কেবল পরে স্থানীয় ল্যাম্বডা এক্সপ্রেশন দ্বারা গ্রাস করা হয়! এটি খুব ধীর কল হতে চলেছে foo"?
ইম্জ - ইভান জাখারিয়াচেভ

কোনও স্থানীয় স্থানীয় মামলার জন্য কি কোনও সমাধান নেই? -f-no-state-hackসংকলন যখন বেশ ভারী ওজন মনে হয়। {-# NOINLINE #-}সুস্পষ্ট জিনিসটির মতো মনে হচ্ছে তবে আমি কীভাবে এটি এখানে প্রয়োগ করব তা ভাবতে পারি না। সম্ভবত এটি কেবল nodesএকটি আইও অ্যাকশন করা এবং এর ক্রম উপর নির্ভর করা যথেষ্ট হবে >>=?
বেরেন্ড ভেন্টার

আমি আরও দেখেছি যে সাহায্যের replicateM_ n fooসাথে প্রতিস্থাপন করা forM_ (\_ -> foo) [1..n]
জোচিম ব্রেটনার
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.