10 ms·
For some reason, this raised my curiosity how fast different languages write individual characters to a pipe: PHP comes in at about 900KiB/s: php -r 'whil
by mg 4y ago
For some reason, this raised my curiosity how fast different languages write individual characters to a pipe:
PHP comes in at about 900KiB/s:
php -r 'while (1) echo 1;' | pv > /dev/null
Python is about 50% faster at about 1.5MiB/s:
python3 -c 'while (1): print (1, end="")' | pv > /dev/null
Javascript is slowest at around 200KiB/s:
node -e 'while (1) process.stdout.write("1");' | pv > /dev/null
What's also interesting is that node crashes after about a minute:
FATAL ERROR: Ineffective mark-compacts
near heap limit Allocation failed -
JavaScript heap out of memory
All results from within a Debian 10 docker container with the default repo versions of PHP, Python and Node.
Update:
Checking with strace shows that Python caches the output:
strace python3 -c 'while (1): print (1, end="")' | pv > /dev/null
Outputs a series of:
write(1, "11111111111111111111111111111111"..., 8193) = 8193
PHP and JS do not.
So the Python equivalent would be:
python3 -c 'while (1): print (1, end="", flush=True)' | pv > /dev/null
Which makes it compareable to the speed of JS.
Interesting, that PHP is over 4x faster than the Python and JS.
- megous 4y ago"Javascript" is slowest probably because node pushes the writes to a thread instead of printing directly from the main process like PHP. Python cheats, and it's still slow as heck even while cheating (buffers the output at 8192 chunks instead of issuing 1 byte writes). write(1, "1", 1) loop in C pushes 6.38MiB/s on my PC. :)
- cout 4y agoWhy is it cheating to use a buffer? This is the behavior you would get in C if you used the C standard library (putc/fputc) instead of a system call (write).
- megous 4y agoBecause it doesn't answer the question "how fast individual languages write individual characters to a pipe" if in fact some languages do not. It's not language "cheating" of course. It's just OP "measuring the wrong thing".
- chupasaurus 4y agoIf you want to compare apples to apples, you can switch Python to use unbuffered stdout/stderr (via `-u` or fcntl inside the script [0]) [0]: https://stackoverflow.com/a/881751/6001364 https://stackoverflow.com/a/881751/6001364
- capableweb 4y ago> Javascript is slowest at around 200KiB/s: I get around 1.56MiB/s with that code. PHP gets 4.04MiB/s. Python gets 4.35MiB/s. > What's also interesting is that node crashes after about a minute I believe this is because `while(1)` runs so fast that there is no "idle" time for V8 to actually run GC. V8 is a strange beast, and this is just a guess of mine. The following code shouldn't crash, give it a try: node -e 'function write() {process.stdout.write("1"); process.nextTick(write)} write()' | pv > /dev/null It's slower for me though, giving me 1.18MiB/s. More examples with Babashka and Clojure: bb -e "(while true (print \"1\"))" | pv > /dev/null 513KiB/s clj -e "(while true (print \"1\"))" | pv > /dev/null 3.02MiB/s clj -e "(require '[clojure.java.io :refer [copy]]) (while true (copy \"1\" *out*))" | pv > /dev/null 3.53MiB/s clj -e "(while true (.println System/out \"1\"))" | pv > /dev/null 5.06MiB/s Versions: PHP 8.1.6, Python 3.10.4, NodeJS v18.3.0, Babashka v0.8.1, Clojure 1.11.1.1105
- mg 4y agoYour node version indeed did not crash. Tried for 2 minutes. But using a longer string crashed after 23s here: node -e 'function write() {process.stdout.write("1111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000"); process.nextTick(write)} write()' | pv > /dev/null
- capableweb 4y agoHm, strange. With the same out of memory error as before or a different one? Tried running that one for 2 minutes, no errors here, and memory stays constant. Also, what NodeJS version are you on?
- mg 4y agoYes, same error as before. Memory usage stays the same for a while, then starts to skyrocket shortly before it crashes. node is v10.24.0. (Default from the Debian 10 repo)
- 4y ago
- MaxBarraclough 4y agoPerhaps different approaches to caching? I'm reminded of this StackOverflow question, Why is reading lines from stdin much slower in C++ than Python? https://stackoverflow.com/q/9371238/ https://stackoverflow.com/q/9371238/
- rascul 4y agoI did the same test, but added a rust and bash version. My results: Rust: 21.9MiB/s Bash: 282KiB/s PHP: 2.35MiB/s Python: 2.30MiB/s Node: 943KiB/s In my case, node did not crash after about two minutes. I find it interesting that PHP and Python are comparable for me but not you, but I'm sure there's a plethora of reasons to explain that. I'm not surprised rust is vastly faster and bash vastly slower, I just thought it interesting to compare since I use those languages a lot. Rust: fn main() { loop { print!("1"); } } Bash (no discernible difference between echo and printf): while :; do printf "1"; done | pv > /dev/null
- ur-whale 4y agofor the bash case, the cost of forking to write two chars is overwhelming compared to anything related to I/O.
- megous 4y agoThere's no forking and it's wrinting one character.
- mauvehaus 4y agoEcho and printf are shell built-ins in bash[0]. Does it have to fork to execute them? You could probably answer this by replacing printf with /bin/echo and comparing the results. I'm not in front of a Linux box, or I'd try. [0] https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html https://www.gnu.org/software/bash/manual/html_node/Bash-Buil...
- ur-whale 4y ago> Echo and printf are shell built-ins in bash Ah, yeah, good point, I am wrong.
- megous 4y agoRust also cheats. https://megous.com/dl/tmp/1046458b5b450018.png https://megous.com/dl/tmp/1046458b5b450018.png
- cout 4y agoWhat version of node are you using? It seems to run indefinitely on 14.19.3 that comes with Ubuntu 20.04.
- nequo 4y agoFor me: Python3: 3 MiB/s Node: 350 KiB/s Lua: 12 MiB/s lua -e 'while true do io.write("1") end' | pv > /dev/null Haskell: 5 MiB/s loop = do putStr "1" loop main = loop Awk: 4.2 MiB/s yes | awk '{printf("1")}' | pv > /dev/null
- VWWHFSfQ 4y agoLua is an interesting one. while true do io.write "1" end PUC-Rio 5.1: 25 MiB/s PUC-Rio 5.4: 25 MiB/s LuaJIT 2.1.0-beta3: 550 MiB/s <--- WOW They all go slightly faster if you localize the reference to `io.write` local write = io.write while true do write "1" end
- yakubin 4y ago> They all go slightly faster if you localize the reference to `io.write` No noticeable difference for LuaJIT, which makes sense, since JIT should figure it out without help.
- dllthomas 4y agoHaskell can be even simpler: main = putStr (repeat '1') [Edit: as pointed out below, this is no longer the case!] Strings are printed one character at a time in Haskell. This choice is justified by unpredictability of the interaction between laziness and buffering; I am uncertain it's the correct choice, but the proper response is to use Text where performance is relevant.
- cle 4y agoA major contributing factor is whether or not the language buffers output by default, and how big the buffer is. I don't think NodeJS buffers, whereas Python does. Here's some comparisons with Go (does not buffer by default): - Node (no buffering): 1.2 MiB/s - Go (no buffering): 2.4 MiB/s - Python (8 KiB buffer): 2.7 MiB/s - Go (8 KiB buffer): 218 MiB/s Go program: f := bufio.NewWriterSize(os.Stdout, 8192) for { f.WriteRune('1') }
- reincarnate0x14 4y agoNot specifically addressed at you, but it's a bit amusing watching a younger generation of programmers rediscovering things like this, which seemed hugely important in like 1990 but largely don't matter that much to modern workflows with dedicated APIs or various shared memory or network protocols, as not much that is really performance-critical is typically piped back and forth anymore. More than a few old backup or transfer scripts had extra dd or similar tools in the pipeline to create larger and semi-asynchronous buffers, or to re-size blocks on output to something handled better by the receiver, which was a big deal on high speed tape drives back in the day. I suspect most modern hardware devices have large enough static RAM and fast processors to make that mostly irrelevant.
- preseinger 4y agoIn addition to buffering within the process, Linux (usually) buffers process stdout with ~16KB, and does not buffer stderr.
- abuckenheimer 4y ago> python3 -c 'while (1): print (1, end="")' | pv > /dev/null python actually buffers its writes with print only flushing to stdout occasionally, you may want to try: python3 -c 'while (1): print (1, end="", flush=True)' | pv > /dev/null which I find goes much slower (550Kib/s)
- themulticaster 4y agoIf you ever need to write a random character to a pipe very fast, GNU coreutils has you covered with yes(1). It runs at about 6 GiB/s on my system: yes | pv > /dev/null There's an article floating around [1] about how yes(1) is extremely optimized considering its original purpose. In care you're wondering, yes(1) is meant for commands that (repeatedly) ask whether to proceed, expecting a y/n input or something like that. Instead of repeatedly typing "y", you just run "yes | the_command". Not sure about how yes(1) compares to the techniques presented in the linked post. Perhaps there's still room for improvement. [1] Previous HN discussion: https://news.ycombinator.com/item?id=14542938 https://news.ycombinator.com/item?id=14542938
- dpflug 4y agoFaster still is pv < /dev/zero > /dev/null
- BenjiWiebe 4y agoYes but you don't have control of which character is written (only NULLs). yes lets you specify which character to output. 'yes n' for example to output n.
- gitgud 4y ago> It runs at about 6 GiB/s on my system... Honest question: what are the practical use cases of this? Repeatedly typing the 'y' character into a Linux pipe is surely not that common, especially at that bit rate. Also seems like the bottleneck would always be the consuming program...
- rhyn00 4y agoAdding a few results: Using OP's code for following php 1.8mb/sec python 3.8 Mb/sec node 1.0 Mb/sec Java print 1.3 Mb/sec echo 'class Code {public static void main(String[] args) {while (true){System.out.print("1");}}}' >Code.java; javac Code.java ; java Code | pv>/dev/null Java with buffering 57.4 Mb/sec echo 'import java.io.*;class Code2 {public static void main(String[] args) throws IOException {BufferedWriter log = new BufferedWriter(new OutputStreamWriter(System.out));while(true){log.write("1");}}}' > Code2.java ; javac Code2.java ; java Code2 | pv >/dev/null
- kuschku 4y agoJava can get even much much faster: https://gist.github.com/justjanne/12306b797f4faa977436070ec0bd865f https://gist.github.com/justjanne/12306b797f4faa977436070ec0... That manages about 7 GiB/s reusing the same buffer, or about 300 MiB/s with clearing and refilling the buffer every time (the magic is in using java’s APIs for writing to files/sockets, which are designed for high performance, instead of using the APIs which are designed for writing to stdout)
- rhyn00 4y agoNice, that's pretty cool!
- dpflug 4y agoI was getting different results depending on when I run it. Took me a second to realize it was my processor frequency scaling.
- klohto 4y agoPython pushes 15MiB on my M1 Pro if you go down a level and use sys directly. python3 -c 'import sys while (1): sys.stdout.write("1")'| pv>/dev/null
- mg 4y agoThat caches though. You can see it when you strace it.
- klohto 4y agoGood point, but so does a standard print call. Calling flush() after each write does bring the perf to 1.5MiB
- rovr138 4y agopython3 -u -c 'import sys while (1): sys.stdout.write("1")'| pv>/dev/null 427KiB/s python3 -c 'import sys while (1): sys.stdout.write("1")'| pv>/dev/null 6.08MiB/s Using python 3.9.7 on macOS Monterey.
- GlitchMr 4y ago`process.stdout.write` is different to PHP's `echo` and Python's `print` in that it pushes a write to an event queue without waiting for the result which could result in filling event queue with writes. Instead, you can consider `await`-ing `write` so that it would write before pushing another `write` to an event queue. node -e ' const stdoutWrite = util.promisify(process.stdout.write).bind(process.stdout); (async () => { while (true) { await stdoutWrite("1"); } })(); ' | pv > /dev/null
- fasteo 4y agoLuajit using print and io.write LuaJIT 2.1.0-beta3 Using print is about 17 MiB/s luajit -e "while true do print('x') end" | pv > /dev/null Using io.write is about 111 MiB/s luajit -e "while true do io.write('x') end" | pv > /dev/null
- deleted 4y ago[deleted]
- orf 4y agoUsing `sys.stdout.write()` instead of `print()` gets ~8MiB/s on my machine.
- wazoox 4y agoOn my extremely old desktop PC (Phenom II 550) running an out-of-date OS (Slackware 14.2): Bash: while :; do printf "1"; done | ./pv > /dev/null [ 156KiB/s] Python3 3.7.2: python3 -c 'while (1): print (1, end="")' | ./pv > /dev/null [1,02MiB/s] Perl 5.22.2: perl -e 'while (true) {print 1}' | ./pv > /dev/null [3,03MiB/s] Node.js v12.22.1: node -e 'while (1) process.stdout.write("1");' | ./pv > /dev/null [ 482KiB/s]
- cestith 4y agoI'm on a 2015 MB Air with two browsers running, probably a dozen tabs between them, three tabs in iTerm2, Outlook, Word, and Teams running. Perl 5.18.0 gives me 3.5 MiB per second. Perl 5.28.3, 5.30.3, and 5.34.0 gives 4 MiB per second. perl5.34.0 -e 'while (){ print 1 }' | pv > /dev/null For Python 3.10.4, I get about 2.8 MiB/s as you have it written, but around 5 MiB/s (same for 3.9 but only 4 MiB/s for 3.8) with this. I also get 4.8 MiB/s with 2.7: python3 -c 'while (1): print (1)' | pv > /dev/null If I make Perl behave like yes and print a character and a newline, it has a jump of its own. The following gives me 37.3 MiB per second. perl5.34.0 -e 'while (){ print "1\n" }' | pv > /dev/null Interestingly, using Perl's say function (which is like a Println) slows it down significantly. This version is only 7.3 MiB/s. perl5.34.0 -E 'while (1) {say 1}' | pv > /dev/null Go 1.18 has 940 KiB/s with fmt.Print and 1.5 MiB/s with fmt.Println for some comparison. package main import "fmt" func main() { for ;; { fmt.Println("1") } } These are all macports builds.
- soheil 4y agoYou're testing a very specific operation, a loop, in each language to determine its speed, not sure if I'd generalize that. I wonder what it'd look like if you replaced the loop with static print statements that were 1000s of characters long with line breaks, the sort of things that compiler optimizations do.
- xthrowawayxx 4y agoI find that NodeJS runs eventually out of memory and crashes with applications that do a large amount of data processing over a long time with little breaks even if there are no memory leaks. Edit: I've found this consistently building multiple data processing applications over multiple years and multiple companies
- mscdex 4y agoPotential buffering issues aside, as others have pointed out the node.js example is performing asynchronous writes, unlike the other languages' examples (as far as I know). To do a proper synchronous write, you'd do something like: node -e 'const { writeSync } = require("fs"); while (1) writeSync(1, "1");' | pv > /dev/null That gets me ~1.1MB/s with node v18.1.0 and kernel 5.4.0.
- petercooper 4y agoI'll tell you what's fun. I get 5MB/sec with Python, 1.3MB/sec with Node and.... 12.6MB/sec with Ruby! :-) (Added: Same speed as Node if I use $stdout.sync = true though..)