Make a Lisp in Nim
hookrace.net
hookrace.net
perf3 perf3 /
macros chars chars * 100
-------------------------------------------------
scala 15963 24885 64,15
nim 11121 20263 54,88
java 17969 53223 33,76
ocaml 7063 24371 28,98
coffee 2326 15653 14,86
racket 2461 17229 14,28
cs 5414 45039 12,02
clojure 1174 10099 11,62
ruby 1255 13247 9,47
go 3048 36321 8,39
vb 4523 58099 7,78
rust 4084 56516 7,23
js 1726 26437 6,53
c 3649 73047 5,00
haskell 1163 30115 3,86
python 304 19632 1,55
forth 563 44715 1,26
php 331 27332 1,21Also, which specific interpreters/AOT compilers/JIT compilers are being used for each language being measured?
λ> import qualified Data.Text.IO as T
λ> import qualified Data.Text as T
λ> data Entry = Entry {name :: T.Text, macroPerf :: Int, charLength :: Int} deriving Show
λ> -- parse into list of lists
λ> let table = (fmap T.words) . T.lines <$> T.readFile "langstats.txt"
λ> -- I need to parse these things... safely.. using the Safe package!
λ> import Safe
λ> -- parse table into list of `Maybe Entry` taking advantage of applicative instance
λ> -- the list of lists are just name, perf3 macros, chars, and a field I'm ignoring for now
λ> -- we can just use list destructuring (\(name : perf3macros : chars : _))
λ> entries <- fmap (\(n:p3:chars:_) -> liftA3 Entry (Just n) (toInt p3) (toInt chars)) <$> table
λ> :t entries
entries :: [Maybe Entry]
λ> -- for easy sorting, we want just [Entry]
λ> -- safely turn our [Maybe Entry] into [Entry]
λ> import Data.Maybe (isJust, fromJust)
λ> let entries' = map fromJust . filter isJust $ entries -- safe because we filter out the justs first!
λ> -- speaking of sorting... we need a couple imports
λ> import Data.List (sortBy)
λ> import Data.Function (on)
λ> let results = sortBy (compare `on` macroPerf) entries'
[Entry {name = "python", macroPerf = 304, charLength = 19632},Entry {name = "php", macroPerf = 331, charLength = 27332},Entry {name = "forth", macroPerf = 563, charLength = 44715},Entry {name = "haskell", macroPerf = 1163, charLength = 30115},Entry {name = "clojure", macroPerf = 1174, charLength = 10099},Entry {name = "ruby", macroPerf = 1255, charLength = 13247},Entry {name = "js", macroPerf = 1726, charLength = 26437},Entry {name = "coffee", macroPerf = 2326, charLength = 15653},Entry {name = "racket", macroPerf = 2461, charLength = 17229},Entry {name = "go", macroPerf = 3048, charLength = 36321},Entry {name = "c", macroPerf = 3649, charLength = 73047},Entry {name = "rust", macroPerf = 4084, charLength = 56516},Entry {name = "vb", macroPerf = 4523, charLength = 58099},Entry {name = "cs", macroPerf = 5414, charLength = 45039},Entry {name = "ocaml", macroPerf = 7063, charLength = 24371},Entry {name = "nim", macroPerf = 11121, charLength = 20263},Entry {name = "scala", macroPerf = 15963, charLength = 24885},Entry {name = "java", macroPerf = 17969, charLength = 53223}]
λ> -- how about some pretty printing?
λ> mapM_ (\e -> putStrLn $ (T.unpack (name e)) ++ "\t" ++ show (macroPerf e) ++ "\t" ++ show (charLength e)) results> Rust: build with --release. 10X performance boost!
That might mean that Rust would top the charts instead of Nim.
[1]: https://github.com/kanaka/mal/commit/434516e0d172904e06b05f6...
if *strn == "&".to_string() { ... }
is a very slow (and verbose) way to write if &strn[..] == "&" { ... }
and rr_string("'".to_string() + k.to_string() + "' not found".to_string())
is a very slow (and verbose) way to write rr_string(format!("'{}' not found", k))`
Etc. etc.[1]: https://github.com/kanaka/mal/blob/master/rust/src/env.rs
EDIT: further, looks like it's on a really old Rust: https://github.com/kanaka/rust-pcre wasn't updated since October...
EDIT 2: I tried to update the code, but it's really, really out of date, and will be a ton of work. So I've just submitted https://github.com/kanaka/mal/pull/23 instead. :(
The reason it still uses the alternate pcre is because this still hasn't been fixed: https://github.com/rust-lang/regex/issues/28 I would love to get rid of that nastiness.
Have I mentioned how much I can't wait until 1.0.0 actually ships?
For that matter: the constructive part was in the other post where he suggested some improvements. I can understand if the pull request is seen as aggressive, though.
UPDATE: I will point out that the README is pretty clear that this is rust 0.13. Doesn't mean it's a good representation of rust 0.13 either of course, but it clearly isn't based on a recent version of Rust.
That did seem rather inefficient at the time but I wasn't able to discern the more efficient method at the time. I've kind of been waiting for Rust 1 to cycle back around. But I'll see if I might be able to address some of those and bump to Rust 1.0 alpha in the next few days.
Note the conversation about performance is kind of unfortunate. Those numbers should be considered VERY rough (they were just a personal notes file of mine). Also, with --release, the numbers place rust in the same range as other compiled languages.
But if you want to take another pass through the code now that it's not based on an ancient Rust version, I'm always happy to take any advice from the master. :-)
[ed: Also interesting to note that the clojure version is much slower than scala/java. If nothing else, I guess it's an indication of performance gains that can be had by implementing parts of a clojure program in java (unless there's something off with the clojure implementation, of course.]
Lua does seem to be an odd one in that the short tests run quickly but this does not translate into iterations for the longer 10 second test. Perhaps the mal implementation is triggering bad GC behavior or something and that drags down the longer running tests. That's just speculation though. Again, please take the numbers with a mountain size grain of salt.
There is this misunderstanding between implementations and languages where people equate whatever they have installed on their computer with all implementations of the said language.
Also not knowing about profilers and optimization flags.
So yeah, it is sad.
$ rustc --release prog.rs
error: Unrecognized option: 'release'.
There is a -O for optimization, it is equivalent to -C opt-level=2.EDIT: Oh, cargo build does have a --release which seems to be equivalent to -C opt-level=3, which I guess is even better.
Alternatively, don't have a default and let people opt-in to whatever default they like. That forces people to actually make a choice, instead of being lazy and publishing poor benchmarks without having even looked up what optimization knobs there are to turn on or off.
Can someone explain the huge performance difference between C and assembly? I though C compiled to assembly, therefore I would think that good handwritten assembly would more or less be the upper performance bound for C.
"Haskell compiles to C therefore good, handwritten C ought to upper bound performance for Haskell"
main :: IO ()
main = do
x <- return ([1..100000001] !! 100000000)
putStrLn "done"
As Haskell is lazy and pure it "automatically" eliminates the `x` and returns nearly immediately avoiding all work. The correspondingly written C code would do much more work despite its waste.Of course, the human writing the C version might automatically perform this optimization, but similar things can arise in far less obvious locations in a more genuine compilation.
Any C compiler worth using will optimize out 'x' in that case. The details of why (laziness vs code analysis saying it's unused) is pretty much irrelevant.
Saying "the human writing the C compiler" is an odd way to put it. It's "the human writing the Haskell compiler" deciding it shouldn't be evaluated in Haskell, too. Totally meaningless distinction.
It looks like the C version is using GLib for hash tables and data structures, and it's possible that Nim's library is simpler and faster.
There could be a lot of reasons.