[Xmonad] darcs patch: ShellPrompt.hs: a quick optimization of nub
Andrea Rossato
mailing_list at istitutocolli.org
Tue Oct 16 04:35:17 EDT 2007
On Mon, Oct 15, 2007 at 07:54:28PM -0400, gwern0 at gmail.com wrote:
> Mon Oct 15 19:48:50 EDT 2007 gwern0 at gmail.com
> * ShellPrompt.hs: a quick optimization of nub
> I saw some complaints about ShellPrompt being slow - and noticed
> it myself - and it seems ShellPrompt uses 'nub' in an awkward
> place to uniquefy input. Nub doesn't perform well on long lists,
> but I once ran into a similar problem and the suggested solution
> was something clever: convert to a Set and then back to a List.
> Sets can't have duplicate entries, and they uniquefy faster than
> nub. The price is that the output is not sorted the same as nub's
> output would be, but this is OK because the output of (toList .
> fromList) is immediately passed to 'sort' - which should then
> produce the same output for both versions. I haven't really tested
> this but on long directories this should help.
Indeed the benchmarks I tried show that the problem was nub. Quite
amazingly changing nub with toList . fromList means reducing cpu time
of about 75%.
With numb:
time promptReadline /usr/bin/
2878
real 0m8.504s
user 0m7.559s
sys 0m0.019s
time promptGetDirCont /usr/bin/
2878
real 0m8.429s
user 0m7.554s
sys 0m0.039s
With toList . fromList:
time promptReadlineSet /usr/bin/
2878
real 0m0.110s
user 0m0.082s
sys 0m0.004s
time promptGetDirContSet /usr/bin/
2878
real 0m0.227s
user 0m0.185s
sys 0m0.022s
It is true that ReadLine is twice quicker that getDirectoryContent but
I would prefer not to rely on an external library for such an
improvement. What do you think?
Andrea
-------------- next part --------------
import Control.Monad
import Data.List
import System.Console.Readline
import System.Environment
getShellCompl :: String -> IO [String]
getShellCompl s
| s /= "" && last s /= ' ' = do
fl <- filenameCompletionFunction s
c <- commandCompletionFunction s
return $ sort . nub $ fl ++ c
| otherwise = return []
commandCompletionFunction :: String -> IO [String]
commandCompletionFunction str
| '/' `elem` str = return []
| otherwise = do
p <- getEnv "PATH"
cl p
where
cl = liftM (nub . rmPath . concat) . mapM fCF . map addToPath . split ':'
addToPath = flip (++) ("/" ++ str)
fCF = filenameCompletionFunction
rmPath :: [String] -> [String]
rmPath s =
map (reverse . fst . break (=='/') . reverse) s
split :: Eq a => a -> [a] -> [[a]]
split _ [] = []
split e l =
f : split e (rest ls)
where
(f,ls) = span (/=e) l
rest s | s == [] = []
| otherwise = tail s
main = do
a <- getArgs
putStrLn =<< fmap (show . length) (getShellCompl $ a !! 0)
-------------- next part --------------
import Control.Monad
import Data.List
import System.Console.Readline
import System.Environment
import qualified Data.Set as S
getShellCompl :: String -> IO [String]
getShellCompl s
| s /= "" && last s /= ' ' = do
fl <- filenameCompletionFunction s
c <- commandCompletionFunction s
return $ sort . (S.toList . S.fromList) $ fl ++ c
| otherwise = return []
commandCompletionFunction :: String -> IO [String]
commandCompletionFunction str
| '/' `elem` str = return []
| otherwise = do
p <- getEnv "PATH"
cl p
where
cl = liftM (nub . rmPath . concat) . mapM fCF . map addToPath . split ':'
addToPath = flip (++) ("/" ++ str)
fCF = filenameCompletionFunction
rmPath :: [String] -> [String]
rmPath s =
map (reverse . fst . break (=='/') . reverse) s
split :: Eq a => a -> [a] -> [[a]]
split _ [] = []
split e l =
f : split e (rest ls)
where
(f,ls) = span (/=e) l
rest s | s == [] = []
| otherwise = tail s
main = do
a <- getArgs
putStrLn =<< fmap (show . length) (getShellCompl $ a !! 0)
-------------- next part --------------
import Control.Monad
import Data.List
import System.Directory
import System.IO
import System.Environment
import System.Process
runProcessWithInput :: FilePath -> [String] -> String -> IO String
runProcessWithInput cmd args input = do
(pin, pout, perr, ph) <- runInteractiveProcess cmd args Nothing Nothing
hPutStr pin input
hClose pin
output <- hGetContents pout
when (output==output) $ return ()
hClose pout
hClose perr
waitForProcess ph
return output
getShellCompl :: String -> IO [String]
getShellCompl s
| s /= "" && last s /= ' ' = do
f <- fmap lines $ runProcessWithInput "/bin/bash" [] ("compgen -A file " ++ s ++ "\n")
c <- commandCompletionFunction s
return . map escape . sort . nub $ f ++ c
| otherwise = return []
commandCompletionFunction :: String -> IO [String]
commandCompletionFunction str
| '/' `elem` str = return []
| otherwise = do
p <- getEnv "PATH" `catch` const (return [])
let ds = split ':' p
fp d f = d ++ "/" ++ f
es <- forM ds $ \d -> do
exists <- doesDirectoryExist d
if exists
then getDirectoryContents d >>= filterM (isExecutable . fp d)
else return []
return . filter (isPrefixOf str) . concat $ es
isExecutable :: FilePath ->IO Bool
isExecutable f = do
fe <- doesFileExist f
if fe
then fmap executable $ getPermissions f
else return False
split :: Eq a => a -> [a] -> [[a]]
split _ [] = []
split e l =
f : split e (rest ls)
where
(f,ls) = span (/=e) l
rest s | s == [] = []
| otherwise = tail s
escape :: String -> String
escape [] = ""
escape (' ':xs) = "\\ " ++ escape xs
escape (x:xs)
| isSpecialChar x = '\\' : x : escape xs
| otherwise = x : escape xs
isSpecialChar :: Char -> Bool
isSpecialChar = flip elem "\\@\"'#?$*()[]{};"
main = do
a <- getArgs
putStrLn =<< fmap (show . length) (getShellCompl $ a !! 0)
-------------- next part --------------
import Control.Monad
import Data.List
import qualified Data.Set as S
import System.Directory
import System.IO
import System.Environment
import System.Process
runProcessWithInput :: FilePath -> [String] -> String -> IO String
runProcessWithInput cmd args input = do
(pin, pout, perr, ph) <- runInteractiveProcess cmd args Nothing Nothing
hPutStr pin input
hClose pin
output <- hGetContents pout
when (output==output) $ return ()
hClose pout
hClose perr
waitForProcess ph
return output
getShellCompl :: String -> IO [String]
getShellCompl s
| s /= "" && last s /= ' ' = do
f <- fmap lines $ runProcessWithInput "/bin/bash" [] ("compgen -A file " ++ s ++ "\n")
c <- commandCompletionFunction s
return . map escape . sort . (S.toList . S.fromList) $ f ++ c
| otherwise = return []
commandCompletionFunction :: String -> IO [String]
commandCompletionFunction str
| '/' `elem` str = return []
| otherwise = do
p <- getEnv "PATH" `catch` const (return [])
let ds = split ':' p
fp d f = d ++ "/" ++ f
es <- forM ds $ \d -> do
exists <- doesDirectoryExist d
if exists
then getDirectoryContents d >>= filterM (isExecutable . fp d)
else return []
return . filter (isPrefixOf str) . concat $ es
isExecutable :: FilePath ->IO Bool
isExecutable f = do
fe <- doesFileExist f
if fe
then fmap executable $ getPermissions f
else return False
split :: Eq a => a -> [a] -> [[a]]
split _ [] = []
split e l =
f : split e (rest ls)
where
(f,ls) = span (/=e) l
rest s | s == [] = []
| otherwise = tail s
escape :: String -> String
escape [] = ""
escape (' ':xs) = "\\ " ++ escape xs
escape (x:xs)
| isSpecialChar x = '\\' : x : escape xs
| otherwise = x : escape xs
isSpecialChar :: Char -> Bool
isSpecialChar = flip elem "\\@\"'#?$*()[]{};"
main = do
a <- getArgs
putStrLn =<< fmap (show . length) (getShellCompl $ a !! 0)
More information about the Xmonad
mailing list