Hallo Habr.
Kürzlich Siemargllud mich ein, mit Hilfe von Haskell einen merkwürdigen Artikel über den Sieg über Unix zu übersetzen wc
. Natürlich werde ich es nicht übersetzen, und das aus mehreren Gründen:
- Der Autor drückte sich keineswegs alles aus der Single-Threaded-Version heraus, und die Single-Threaded-Version war viel langsamer
wc
. - In diesem Artikel war es notwendig, Multithreading für den Sieg zu verwenden (was an sich ein bisschen Betrug und Sieg ist, eher über den gesunden Menschenverstand als über
wc
). - Dafür musste sich der Autor vertiefen
TrichoMonaden und Monoide - nicht, dies ist ein hervorragendes Beispiel für die Reize des monoidalen Denkens, aber meiner Meinung nach ein wenig zu viel für eine solche Aufgabe, zumal aus diesem Grund - der Code erwies sich als zu umfangreich,
- und in der Tat, zu konkurrieren
wc
, das eine Reihe von Optionen und Funktionen hat, zu erkennen, dass es ein sehr Spielzeug-Analogon ist, im Allgemeinen ist es irgendwie seltsam und sogar ein wenig dumm.
Trotzdem ist es gut , seltsame Dinge zu tun. Deshalb werden wir heute versuchen, den ersten der oben genannten Punkte zu korrigieren und das Ergebnis von Chris (dem Namen des Autors des Originalartikels) zu verbessern.
Wie wir letztes Mal herausgefunden haben, kann ich keinen C-Code schreiben, also werde ich ihn auch nicht schreiben, und als Konkurrent der Haskell-Implementierung habe ich (wie Chris) einen synergistischen wc
von GNU Coreutils. Diese Typen wissen sicherlich, wie man in C schreibt, dieser Code ist kein Jahrzehnt alt, und sie haben sich um die Leistung gekümmert, gemessen an solchen Stücken:
Spoiler: Wir werden das System wc
ohne Probleme um eine Größenordnung überholen , einen vollständig idiomatischen Code erhalten und weniger als eine halbe Stunde für Änderungen und Optimierungen des ursprünglichen Codes aufwenden.
Versuchsbedingungen
Also diskutieren wir zuerst den Versuchsaufbau.
Daten
Ich habe diese Datei heruntergeladen und mit mir zusammengeklebt, sodass die Gesamtgröße ungefähr 1,8 Gigabyte beträgt:
% for i in `seq 1 10`; cat part.txt >> test.txt
% du -sh test.txt
1.8G test.txt
, test.txt
tmpfs
-, , IO.
Gentoo Linux Core i7 4770 32 .
- ghc 8.8.2.
wc
coreutils 8.31, gcc 9.2 -O2 -march=native
:
% wc --version
wc (GNU coreutils) 8.31
Packaged by Gentoo (8.31-r1 (p0))
, -march=native
— C, - - , : x86_64-, wc
coreutils ( SIMD-, ).
, -O3
-O2
— , -O2
.
, time
. time
, , ,
- 0.3 ,
- , , .
, ( ) , .
? wc
!
Unicode ( ), C-, wc
LANG=C LC_ALL=C wc /path/to/test.txt
. ? 10.4 . .
, (ru_RU.UTF-8
) wc
(7.20 ), , , , C- — , .
, ( ), 0.2 — , IO-bound-.
Haskell
, ?
, cs
bs
( , ):
import qualified Data.ByteString.Lazy.Char8 as BS
import Data.Char
wc :: BS.ByteString -> (Int, Int, Int)
wc s =
let (bs, ws, ls, _) = BS.foldl' go (0, 0, 0, False) s
in (bs, ws, ls)
where
go :: (Int, Int, Int, Bool) -> Char -> (Int, Int, Int, Bool)
go (!bs, !ws, !ls, !wasSpace) c =
let addLine | c == '\n' = 1
| otherwise = 0
addWord | wasSpace = 0
| isSpace c = 1
| otherwise = 0
in (bs + 1, ws + addWord, ls + addLine, isSpace c)
, , ( ).
, , 9 wc
. : 31.2 , 306% wc
. 9 , , .
, ( , ), , , , .
?
-, , — . :
{-# LANGUAGE Strict #-}
{-# LANGUAGE RecordWildCards #-}
import qualified Data.ByteString.Lazy.Char8 as BS
import Data.Char
data State = State
{ bs :: Int
, ws :: Int
, ls :: Int
, wasSpace :: Bool
}
wc :: BS.ByteString -> (Int, Int, Int)
wc s = (bs, ws, ls)
where
State { .. } = BS.foldl' go (State 0 0 0 False) s
go State { .. } c = State (bs + 1) (ws + addWord) (ls + addLine) (isSpace c)
where
addLine | c == '\n' = 1
| otherwise = 0
addWord | wasSpace = 0
| isSpace c = 1
| otherwise = 0
bang-, - {-# LANGUAGE Strict #-}
. , ?
, , — 4 ! 7.56 , 75% wc
, ! ?
, : , , . -
data State = State
{ bs :: {-# UNPACK #-} !Int
, ws :: {-# UNPACK #-} !Int
, ls :: {-# UNPACK #-} !Int
, wasSpace :: !Bool
}
Int
- . , nursery area, , , — , .
CSE
, isSpace c
, — . , , go
:
go State { .. } c = State (bs + 1) (ws + addWord) (ls + addLine) isSp
where
isSp = isSpace c
addLine | c == '\n' = 1
| otherwise = 0
addWord | wasSpace = 0
| isSp = 1
| otherwise = 0
? — 2.93 , 28% wc
.
? ghc , , ? — isSpace
( ) , common subexpression elimination.
— ( ). , wc
, . - , :
- LLVM- (
-fllvm
) — , . - (
-O2
) — -O
, -O2
, ? - (
{-# INLINE wc #-}
). , - , , , - . , , , .
, , , .
:
. -, . , -O2
, , LLVM- , ( ).
, . , C (19%!). , .
.
, State
, .
, Bool
, . , Int
, 1
True
, 0
— False
? , , !
data State = State
{ bs :: Int
, ws :: Int
, ls :: Int
, wasSpace :: Int
}
wc :: BS.ByteString -> (Int, Int, Int)
wc s = (bs, ws, ls)
where
State { .. } = BS.foldl' go (State 0 0 0 0) s
go State { .. } c = State (bs + 1) (ws + addWord) (ls + addLine) isSp
where
isSp | isSpace c = 1
| otherwise = 0
addLine | c == '\n' = 1
| otherwise = 0
addWord | wasSpace == 1 = 0
| isSp == 1 = 1
| otherwise = 0
(, Bool
), . , , addWord
guard' ( if
) :
addWord = (1 - wasSpace) * isSp
.
? , , 1.91 , 18% wc
. , .
, ?
ASCII, isSpace
. , . , , , .
, Data.ByteString.Lazy
Data.ByteString.Lazy.Char8
, Char
.
:
{-# LANGUAGE Strict #-}
{-# LANGUAGE RecordWildCards #-}
module Data.WordCount where
import qualified Data.ByteString.Lazy as BS
data State = State
{ bs :: Int
, ws :: Int
, ls :: Int
, wasSpace :: Int
}
wc :: BS.ByteString -> (Int, Int, Int)
wc s = (bs, ws, ls)
where
State { .. } = BS.foldl' go (State 0 0 0 0) s
go State { .. } c = State (bs + 1) (ws + addWord) (ls + addLine) isSp
where
isSp | c == 32 || c - 9 <= 4 = 1
| otherwise = 0
addLine | c == 10 = 1
| otherwise = 0
addWord = (1 - wasSpace) * isSp
{-# INLINE wc #-}
, 1.45 , 14% wc
.
, — , , .
, , . , : wasSpace
, , :
wc s = (bs, ws + 1 - wasSpace, ls)
where
State { .. } = BS.foldl' go (State 0 0 0 1) s
...
, .
:
0.35 , ? - - ?
, , wc
. :
main :: IO ()
main = do
[path] <- getArgs
contents <- BS.readFile path
print $ wc contents
readFile
. mmap
. bytestring-mmap, ByteString
' mmap
- , main
main :: IO ()
main = do
[path] <- getArgs
contents <- unsafeMMapFile path
print $ wc contents
, unsafeMMapFile
ByteString
, wc
, . , unsafeMMapFile
, , mmap
- , .
— , 0.04-0.06 0.35 . , .
mmap
, : , , . , : , .
?
, , Unix-, . , , ST
.
, , : , wc
, , « » wc
, , . , .
Um nicht zu quälen, werden wir Pläne für die Zukunft diskutieren: Im nächsten Artikel werden wir uns mit etwas wesentlich Interessanterem befassen, bei dem der Haskell wirklich glänzen wird. Insbesondere modularisieren wir das, was wir heute haben, sehen, wie wir unser Spielzeug ein haskell wc
wenig weniger Spielzeug machen können, und bewerten auch, wie sich dies auf die Leistung auswirkt.