How is GNU `yes` so fast?
reddit.com
reddit.com
This is the beautiful part of Unix: small tools that do only one thing well. Programs following this philosophy are very good abstractions. They do one very well defined thing so you can use them without having to understand how they work. I have used Unix for years and I've never felt the need to read the source code for `yes`. And because they do a very small thing, even if you need to read them, the overhead of optimization is not that much, for example, the optimized GNU yes is just under 100 LOC if you remove comments and help boilerplate. Yes, it's longer than the BSD version, but it's just a matter of minutes to understands what it does.
Not a huge bit of work to be sure but for a non-commercial project you might have trouble finding a volunteer who cares about that tedium.
cat /dev/zero|pv >/dev/null
gives me roughly 2/3 the speed of yes. (5 GB/s vs 7 GB/s) Plus yes gives you an arbitrary string, instead of only zeroes. pv > /dev/null < /dev/zero </dev/zero pv >/dev/nullpv </dev/zero >/dev/null
which is a common way of doing it (for any command with any inputs and outputs, not just the above ones), i.e.:
command < input_source > output_dest
All three pv command invocation variants, the one here and the two above, work. And it becomes more clear why they are the same, when you know that the redirections are done by the shell + kernel, and the command (such as pv) does not even know about it. So in all three cases, it does not see the redirections, because they are already done by the time the command starts running (with its stdin and stdout redirected to / from those respective sources). And that is precisely why the command works the same whether it is reading from the keyboard or a file or pipe, and whether it is writing to the screen or a file or pipe.
but here's todays Useless Use of Cat Award http://porkmail.org/era/unix/award.html
https://news.ycombinator.com/item?id=14414610
But in that case one could probably say that Gnu awk is just not very good at input handling (as Mawk doesn't appear to benefit from an extra "cat").
[edit] That's actually inaccurate (and badly expressed), see comments below.
When I was writing raspbian images to SD cards to use on a raspabery Cat and DD took within a few seconds of each other on an operation longer then a minute. Since then I have been using cat where I could, but I didn't think to right down the numbers though.
https://news.ycombinator.com/item?id=14414610
Note that cat+gnu awk was faster than just gnu awk - but mawk was faster still (reading a not entirely small file).
And in a similar vein of gp comparing Gnu and Openbsd, note that openbsd cat is a little more convoluted than the simplest possible implementation (at least to my eyes):
https://github.com/openbsd/src/blob/master/bin/cat/cat.c
https://github.com/coreutils/coreutils/blob/master/src/cat.c
(That is, Gnu "cat" and OpenBSD "cat" are less different than Gnu "yes" and OpenBSD "yes").
I'm glad it runs fast, and I hope that all OS utilities are optimised (and tested, of course!) instead of making their source code pretty. The fact is, most people want to use programs, not read them.
That really doesn't make any sense, /dev/zero should be at least as fast as yes.
I agree, all I remember is that when I tried it, /dev/zero sometimes sucked performance-wise. I can't recall the exact circumstances as it was some time ago, and could have been on any of Linux/FreeBSD/SunOS/HP-UX/IRIX - perhaps it was the fastest common way at the time?
On a recent x64 Linux, /dev/zero seems plenty fast enough now:
$ dd bs=8k count=819200 if=/dev/zero of=/dev/null
819200+0 records in
819200+0 records out
6710886400 bytes (6.7 GB, 6.2 GiB) copied, 0.331137 s, 20.3 GB/s
$ yes | dd bs=8k count=819200 of=/dev/null
819200+0 records in
819200+0 records out
6710886400 bytes (6.7 GB, 6.2 GiB) copied, 0.959551 s, 7.0 GB/s$ pv < /dev/zero > /dev/null [ 16GiB/s]
But the version of yes using vmsplice() is even faster than that on my machine.
pv < /usr/bin/yes > /dev/null
doesn't seem to work properly. FWIW I get 330MiB/s vs 8.4GiB/s for /dev/zero.[Incidentally first I've heard of pv but I've known about dd for a decade or two].
yes | pv > /dev/nullcat /dev/zero | pv > /dev/null
uses a pipe like the 'yes' line above, but runs substantially slower on my computer than the redirect-only version.
yes | pv > /dev/nullFrom pv's man page:
> Its standard input will be passed through to its standard output and progress will be shown on standard error.
> Maybe I just don't understand how pipes and redirection works since I rarely use Linux :(
The Windows/DOS command line has the same concepts[0], though it's probably used less often: by default a process has 3 FDs for STDIN (0), STDOUT (1) and STDERR (2).
At a shell
* you can feed a file to STDIN via <$FILE (so `</dev/zero pv` will feed the data of /dev/zero to pv), the 0 is optional
* you can pipe an output to a command (other | command) or the output of a command to an other (command | other)
* you can redirect the STDOUT or STDERR to files via 1> and 2> (the "1" is optional so 1> and > are the same thing) (you can redirect both independently)
* you can "merge" either output stream to the other by redirecting them to &FD so 1>&2 will redirect STDOUT (1) to STDERR (2) and 2>&1 will redirect STDERR (2) to STDOUT (1), you can combine that with a regular redirection so e.g. `command >foo 2>&1 ` or with a pipe (`command 2>&1 | other`)
And you can actually create more FDs to use in your pipelines[1] though I don't remember ever seeing
[0] https://support.microsoft.com/en-us/help/110930/redirecting-...
I want to use safe programs, and programs with readable code are more likely to be properly audited.
yes(){ while :; do echo "${1:-y}"; done; }
Python: import sys
while True:
if len(sys.argv) > 1:
print(sys.argv[1])
else:
print("y")
And if you don't need the features of `yes` and only need a loop that prints 'y', then there really is hard to beat the simplicity of: while:; do echo "y"; donehttps://github.com/openbsd/src/blob/master/usr.bin/yes/yes.c
It's essentially line for line identical to your python code...
For readability purpose it is easier to go through each lines of the python program than the OpenBSD C code. Its not massively different, but its distinguishable enough that I would choose the python version if I wanted to maximize readability, minimize syntax requirement and did not want to use shell script.
The Shell function is in my view the superior choice if the audience is a programmer than know the shell script syntax. It is just a single loop and is written in the environment that the program is intended to be used in. The only drawback there is the speed.
... and python. Don't forget to count python.
Just because you say so?
I actually checked just now, and it looks like you'd be making twice as many system calls with /dev/zero compared to generating the data locally:
strace dd count=1 bs=512 if=/dev/zero of=test
open("/dev/zero", O_RDONLY) = 3
dup2(3, 0) = 0
close(3) = 0
lseek(0, 0, SEEK_CUR) = 0
open("test2", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
dup2(3, 1) = 1
close(3) = 0
read(0,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 512) = 512
write(1, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 512) = 512
close(0) = 0
close(1)
While 'strace yes > test2' is just a constant stream of write() calls.The difference matters if you're benchmarking e.g. some new SSD compared to a tmpfs on a machine with 100+ GB of RAM. It's always better if the tools have less overhead, because the comparison is more meaningful.
Also consider that it can be faster to write to a local network than to disk. I've never done it, but I imagine that the kernel's not going to want to deal with your /dev/zero calls if it's spending all of its time writing to a 10GB switch. I can imagine some very specialized storage servers that could spend most of their time writing from memory buffers to a network switch, or if you're troubleshooting a slowdown in the networking itself.
$ strace yes > /tmp/test2
[various initialization steps]
write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 8192) = 8192
write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 8192) = 8192
write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 8192) = 8192
write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 8192) = 8192
[...]
To measure everything, I started up a whole new shell, expecting to see a 'write(1, ...)', a 'read(1)', and a 'write(f, ...)': $ strace -f sh -c 'yes > /tmp/test2'
[various initialization steps]
[pid 15839] write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 8192) = 8192
[pid 15839] write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 8192) = 8192
[pid 15839] write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 8192) = 8192
[pid 15839] write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 8192) = 8192
[...]
How does this possibly work? File descriptor 1 is supposed to be the terminal, not a file! Of course, the magic of file redirection: open("/tmp/test2", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3 # Open the file, get FD 3
fcntl(1, F_DUPFD, 10) = 10 # Copy FD 1 (the terminal, STDOUT) to FD 10 temporarily
close(1) = 0 # Close original FD 1
fcntl(10, F_SETFD, FD_CLOEXEC) = 0 # Close FD 10 (the terminal, copy of STDOUT) when exec() is called
dup2(3, 1) = 1 # Copy FD 3 (the file) to FD 1 (STDOUT)
close(3) = 0 # Close FD 3 (the file's original descrptor)When I saw this item here I reached for the terminal and wrote a simple (and non-compliant) alternative that simply initialises 8150 bytes of "y\n" and then loops a write forever. I understand that it is not a fully standard-compliant yes, and that maybe GNU yes is indeed fast, but that awfully simple program that takes all of 10 lines (everything included) and took me all of a minute to write performs just as well as far as pv is concerned.
(I eventually completed a feature complete yes but I still think that simply not using `puts` is hardly optimising to the limit.)
The long-standing advice when writing GNU utilities used to be that if the program you were replacing was optimized for minimizing CPU use, write yours to minimize memory use, or vice-versa. Or in this case, if the program was optimized for simplicity, optimize for throughput.
It would have been very easy for the nascent GNU project to unintentionally produce a line-by-line equivalent of BSD yes.c, which would have potentially landed them in the 80/90s equivalent of the Google v.s. Oracle case.
https://github.com/coreutils/coreutils/blob/ccbd1d7dc5189f46...
https://github.com/coreutils/coreutils/commit/35217221c211f3...
But see the post of belorn where he argues that the error handling seems to be good.
https://www.gnu.org/prep/standards/standards.html#Reading-No...
(Lately I've been really tempted to pull a Cato and start terminating my every HN post with "C delenda est." https://en.wikipedia.org/wiki/Carthago_delenda_est)
It's just a trade off. For utilities whose behaviour doesn't change we're happy to improve the speed.
In the old days I think we would have left yes in c because the compiler will build it on any platform.
My first experience with Linux is porting land.c to one of the commercial Unix. Now I first met TCP/IP on a 3B2 and later met systems sold by SMI and DEC and SCO and we all mostly constructed packets the same way and a guy called Stevens had written some nice books about this that everyone had. I think I recall the commercial and free BSD also did things the usual way. But whoever figured out this interesting phenomenon land.c demonstrated happened to be a Linux user and this platform had some different ideas about it. I saw rewriting the relevant parts as an annoying, menial task.
Why would it be slower to do a single, say, 8190 bytes write instead of 2730 3-byte writes?
Small write calls themselves incur a considerable syscall overhead.
atexit (close_stdout);
Aren't all streams closed at exit? And why closing stdout, anyway?
https://github.com/lattera/glibc/blob/master/string/strlen.c
https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/x86...
The pcmpeqb instruction is from SSE 4, it compares 16 bytes per op
I think unix v6 was mostly as clear. Linux suffered the real world penalty. But maybe BSD managed to keep it's source poetic.
Naturally it could be that the C function puts used by most people in OpenBSD is implemented with built in EINTR catching loop, or that OpenBSD do not interrupts writes.
As you say, the GNU versions definitely handle EINTR - the linux man page for puts() just says it returns a non-negative value on success, it's not even specified whether it returns the number of bytes written or not.
But the point stands; there's no error handling in the OpenBSD version. But that could be considered a design decision; the OpenBSD version never gives up on writing data until you kill it; the GNU version bails at the first error.
Someone does a Go version and gets the same speed as GNU yes. Someone else tries several languages. This person got the same speed in luajit, and faster in m4 and php. Ruby and perl about 10% slower, python2 about 10% slower still, and python3 about half that. The code is given for all of these, and subsequent comments improved python3 about 50% from his results, but still not up to python2.
[1] https://www.reddit.com/r/programming/comments/6gxf02/how_is_...
If you write the Python script using bytes (not Unicode), then Python 3 is faster than Python 2, at least for me. Python 3 is 20 % slower than GNU yes and Python 2 35 % slower than GNU yes.
e: I think in your last sentence you are comparing results from different computers.
(haven't run the tests myself though)
edit: haven't noticed the comment by dom0.
[1] https://www.reddit.com/r/programming/comments/6gxf02/comment...
what is going on with the m4 code?
Remember this is without any printing: C is fast, if I remember correctly it was something like 0.015 or 0.03s while at the other end of the spectrum I had to wait 0.1s for JavaScript. C results varied from simple to double because of the OS I guess because it was so quick.
It isn't a really significant benchmark though, for any language. It was one of those nights. Ah, and I remember a large difference between go run and go install.
Sure, but it's reassuring to know that the universe is still sane, C is still faster than Javascript.
Thanks for expanding on your comment!
c 0.062s
f90 0.080s
go 0.108s
awk 0.116s
lua 0.117s
rs 0.140s
sh 0.434s
php 0.886s
scm 1.323s
py 2.965s
rb 3.738s
js 5.411s
My memory was bad, I also added fortran for the occasion.[1]: https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris...
(use std::borrow::Cow;) ?!
(I don't know rust, its on my list.. Especially with cow borrowing! They clearly are my people).
I noticed they added a comparison of Rust to gnu yes on the thread. They only got: 2.17GiB/s but on a slower machine.. (2.04 for the GNU)
In that Rust program, Cow is being used here so if the user provides an argument it ends up with an owned vector that contains that argument plus a newline, otherwise it ends up with a borrowed byte slice that contains "y\n". That said, there's not really a whole lot of point to using Cow here since allocation is a one-time cost and nobody cares if yes starts up a few microseconds slower, but the usage of Cow is limited to just a couple of lines so it's not really complicating anything (the actual write() call just ends up taking a &[u8] byte slice, so the usage of Cow is strictly limited to main()).
Also, I didn't realize it was Clone-on-write. Interesting, and the documentation does confirm this. I say "interesting" because the actual operation involved is `to_owned()`, not `clone()`, seeing as how `clone()` on a `&str` just gives you the same `&str` back.
> $ python2.7 yes.py | pv > /dev/null
> ... [7.22GiB/s] ...
> $ python3.4 yes.py | pv >/dev/null
> ... [8.76GiB/s] ..darwin with BSD + GNU yes, vs some scripting langs:
$ /usr/bin/yes | pv > /dev/null
^C00MiB 0:00:04 [23.9MiB/s] [ <=>
$ /opt/local/libexec/gnubin/yes | pv > /dev/null
^C77GiB 0:00:05 [ 644MiB/s] [ <=>
$ bash -c 'while true; do echo "y"; done' | pv > /dev/null
^C38KiB 0:00:03 [ 233KiB/s] [
$ node -e 'while (true) { console.log("y"); }' | pv > /dev/null
^C57MiB 0:00:03 [ 582KiB/s] [
$ perl6 -e 'loop { print "y\n" x (1024*8) }' | pv > /dev/null
^C295MiB 0:00:02 [ 139MiB/s] [ <=>
$ ruby -e 'while true do puts("y"*1024*8) end' | pv > /dev/null
^C14GiB 0:00:09 [ 827MiB/s] [
$ python2.7 -c 'while True: print "y\n" * (1024*8),' | pv > /dev/null
^C.3GiB 0:00:10 [1.73GiB/s] [ <=>
$ python3.6 -c 'while True: print("y\n" * (1024*8)),' | pv > /dev/null
^C73GiB 0:00:05 [1.79GiB/s] [ <=>
$ perl -C0 -E 'print "y\n" x (1024*8) while 1' | pv > /dev/null
^C.6GiB 0:00:08 [1.84GiB/s] [ <=> yes | rm -r large_directory
yes | fsck /dev/foo yes "" | cat -n
to generate a sequence number. JdeBP ~ $seq
ksh: seq: not found
JdeBP ~ $
Of course, on the one mentioned there is jot. $ echo {1..5}
1 2 3 4 5
If you have Bash 4.0 or above, you can include a step size: $ echo {1..10..2}
1 3 5 7 9 yes | rm -r <>
and not rm -rf <> > rm -rf foo/bar
rm: foo/bar: Permission denied> [...] Do not write diagnostic messages or modify the exit status in the case of nonexistent operands. [...]
OK, may be I misunderstood, who ran yes, the customer or the genius?
> In 2006, the yes command received publicity for being a means to test whether or not a user's MacBook is affected by the Intermittent Shutdown Syndrome. By running the yes command twice via Terminal under Mac OS X, users were able to max out their computer's CPU, and thus see if the failure was heat related.
I always assumed it was busywork to make the customer feel better.
At the time I had 10.11 on it, but that OS had itself been upgraded from 10.10.
When 10.12 came out, I decided to install fresh - so I backed everything up and installed onto a new SSD drive. The issue has now gone away completely....
I don't understand this reasoning. Why is it being limited to main memory speed? Surely the yes program, the fragments of the OS being used, and the program reading the data, all fit within the L2 cache?
There's no reason that the L2 cache needed to be flushed at any point - the caches are all dealing with physical memory, rather than virtualised address space, so the fact that there are two processes here shouldn't stop the caching from working.
It's completely possible for the data to not (all) leave the CPU. If the caches are large enough, then the pages full of "y\n" will be still resident in the cache when the next iteration of the program overwrites the same pages again. Then the CPU has no need to send the original page out to main memory.
For each byte of data passing through pv:
1. the byte is read from yes memory to CPU registers by the write syscall handler in the kernel
2. the byte is written to kernel's internal buffer associated with the pipe
3. the byte is read back in the read syscall called by pv
4. the byte is written to a buffer in pv memory
5. and thas's the end because write syscall executed by pv on /dev/null very likely doesn't actually bother reading the submitted buffer at all
edit: Actually it might only be 20GB/s because on Linux pv seems to use the splice syscall to transfer data from stdin to stdout without copying to userspace.
This is also the reason why further "optimization" of this program in assembly was a fool's errand: the bulk of CPU load is in the kernel.
The actual throughput then, once you include OS copying, is either 2 or 4 times the quoted speed (depending on splice usage), so we're either at main memory theoretical speeds, or double main memory speeds. Intuitively, I'd still have expected that it should be a larger multiple.
(A quick search can't find me any reliable Intel L1/L2 cache speeds/multiples to quote, so I admit this comment is more speculation than it should be!)
$ yes |pv > /dev/null
46.6GiB 0:00:05 [9.33GiB/s]
$ taskset 1 yes |taskset 1 pv > /dev/null
32.9GiB 0:00:05 [6.58GiB/s]
$ taskset 1 yes |taskset 2 pv > /dev/null
45.7GiB 0:00:05 [9.13GiB/s]
$ taskset 1 yes |taskset 4 pv > /dev/null
45.7GiB 0:00:05 [9.18GiB/s]
Very rough numbers - the 9.13/9.33 difference flip-flopped when I ran the commands again. Binding both processes to the same core is definitely a performance hit though. There might be some gain through a shared cache, but it's lost more through lack of parallelism.I tried 2/4 as not sure how 'real' cores vs 'hyperthread' cores are numbered. These numbers are from a i7-7700k.
Assuming pv uses splice(), there is one only copy in the workload: copy_from_user() from fixed source buffer to some kernel allocated page, then those pages are spliced to /dev/null.
If the pages are not "recycled" (through LRU scheme for allocation), the destination changes every time and the L2 cache is constantly trashed.
See eg. https://stackoverflow.com/questions/23599074/system-calls-ov... who benchmarked it at ~638ns per "read" call.
(Many, many years ago I was working on the Zeus web server, and we went to surprising lengths to avoid syscalls for performance.)
A read() syscall takes longer than a getpid() syscall because read() has more work to do, it actually does a data copy of len bytes, which takes some time (and will be faster/slower if data is cache hot)
What we call the "syscall overhead" is what happens before and after the actual data copy, switching between user and kernel mode.
You make that overhead negligible by calling read() with a large size.
Snap!
IIRC, ZWS used shared memory to mirror the results of the time() syscall across processes, to save a few nanoseconds on some operating systems :) That was before Linux and other OSs used techniques like the vsyscall/VDSO mentioned in the stackoverflow discussion...
https://www.reddit.com/r/unix/comments/6gxduc/how_is_gnu_yes...
I've realised straight tee() is actually wrong - it works fine for piping to something but "zcyes > /dev/null" won't work, it needs vmsplice() like mrb said.
core2duo e8400, 4.8 kernel, coreutils 8.25
> yes | dd bs=1M count=10000 of=/dev/null iflag=fullblock
10485760000 bytes (10 GB, 9.8 GiB) copied, 4.1901 s, 2.5 GB/s
> ./zcyes | dd bs=1M count=10000 of=/dev/null iflag=fullblock
10485760000 bytes (10 GB, 9.8 GiB) copied, 1.8542 s, 5.7 GB/sAnd pv on the other side of the pipe should use splice().
Now that would be a complete zero-copy I/O path, purely limited by the CPU, not by memory bandwidth. It would benchmark at hundreds of GB/s :)
Good catch, I just noticed that it does.
https://www.reddit.com/r/unix/comments/6gxduc/how_is_gnu_yes...
Edit: I optimized further and now get 123 GB/s See https://www.reddit.com/r/unix/comments/6gxduc/how_is_gnu_yes...
https://github.com/freebsd/freebsd/commit/1d61762ca37c20ab6f...
It's about twice as fast as GNU yes now on my FreeBSD system here.
Update: They fixed that issue with this follow up https://github.com/freebsd/freebsd/commit/2592fbb8
# /usr/local/bin/yes | pv > /dev/null
11.5MiB 0:00:09 [1.02MiB/s] [ <=>]
# /usr/bin/yes | pv > /dev/null
1.07GiB 0:00:09 [ 142MiB/s] [ <=>]
JavaScript wins again!!The top comment is:
"It's a shame they didn't finish their kernel, but at least they got yes working at 10GiB/s."
which as an OS guy, someone who has been working on Unix for 30+ years, as a guy who was friends with one the QNX kernel guys (they had perhaps the only widely used microkernel that actually delivered), that's hugely amusing and spot on. The GNU guys never really stepped up to being kernel people. Bitch at me all you want, they didn't get there. It's a funny comment, especially coming from reddit.
We must not be reading the same Hacker News...
Anyway, the comment you're quoting is just a shallow jab that belittles the GNU developers' work without contributing anything new or meaningful. It's telling that you had to spend two paragraphs to justify cross-posting it here.
Jokes have their place, but bringing up the failure of Hurd in every GNU-related post is banal. And saying they "never really stepped up" to your level as a mighty kernel developer, as if the people who brought us glibc and coreutils lack an understanding of OS internals, just seems rude and curiously out of touch.
What I was trying to get at is this, if you care about your upvotes, hacker news promotes a sort of hive mind. Which is somewhat like "say only nice things unless you are clearly swatting down something that is obviously wrong".
Which is mostly fine, fantastic in fact. I'm fine with it, hacker news is really pleasant because of the (more or less ) quality of the posts and especially the quality of the comments. I'd much rather have it be this way than a free for all, those go bad pretty fast.
So I'm for the hive mind, I was just pointing out that you can't make jokes and be upvoted. The joke wasn't banal at all IMO. GNU has done a lot of good, I've been there since the beginning and paid attention along the way. They have also been pretty self serving with their choices, every project has to sign away their rights and then GNU takes full credit for the project even though they had nothing to do with it other than it being GNU troff for example. Given their tendency to take credit for stuff that they didn't do, and their claim that they can do an OS but clearly can't, that joke is funny as heck. If you don't get that, sorry, you haven't been paying attention.
() The quality of the posts in the area of programming, especially systems programming, is spotty. Some stuff is great, stuff I didn't know (there is a lot of that here and I'm very grateful for it, it's why I stick around), some stuff is meh, and then there is stuff like "wow, look at $OBVIOUS, isn't that cool?" that gets upvoted. That last one I just don't get, but whatever, the good stuff is good. The signal/noise ratio here is better than any other programmer oriented site I've found.
However, as avip found out below, it does still render OS X useless within less than a minute (at least on my 2015 MBP).
But before that, parent shell has to buffer until EOF. With "yes" output being unbound, this means unbound buffering / memory growth. That is, until OOM killer take notice and shut it down.
Whole thing will probably take few seconds to a minute (depending on how much free RAM there is vs. how fast it is), peg a single CPU core in the process, and will recover cleanly (except for shell in question being terminated).
...unless system in question has single CPU and large+slow swap. Then yes, bring-it-to-its-knees.
RAM can be tweaked by preventing Linux heuristic overcommit via sysctl vm.overcommit_memory=2. (0 is only recommended if you do not run broken applications. It so happens that many JS and Java VMs are broken on memory pressure.)
bash: xrealloc: cannot allocate 18446744071562067968 bytesNot complaining, I like this kind of analysis
But it seems you won't be limited, in a shell script, by the speed you can push y's
Somebody wanted to optimize 'yes', so they did. There doesn't need to be a good reason, just like there doesn't need to be a good reason for a person to read a certain book or watch a certain movie, other than they want to do it.
And it does make sense for Open Source code because the resources are limited hence other features and/or bug fixes might be more important than pushing data at full speed
That's not how open source works, though. There's not a group of people obligated to work on the GNU utilities. There's not a central project manager ordering people around telling them what changes to make.
Declaring that 'yes' is generally fast enough already doesn't imply that it was fast enough for the person who spent their time optimizing it. Somebody needed (or just wanted) 'yes' to be really fast, so they did the work and submitted the changes back.
No, but there is review and approval of patches (also the most frequent contributors know where the project is going and there is bug tracking)
If one just makes it faster without major benefits that patch is likely to be rejected
IF the project were overwhelmed with incoming patches, then I would agree that there's probably higher priority changes than optimizing 'yes'.
But I'm almost certain the GNU project is not being overwhelmed with changes to the core utilities. Everything else being equal (code quality, readability, test coverage, etc.) there's really no reason to reject a patch that demonstrably improves the project, even in some silly way like making 'yes' really fast.
This is software engineering. Thinking about the tradeoffs and whether they are appropriate or warranted is bread and butter. It very much makes sense to ask whether all of this is merely optimizing for a benchmark at the expense of other factors that might turn out to be more important, such as long term maintainability, portability, behaviour in some common cases which are significantly unlike the benchmark, and so forth.
Several people have made some of these points here and in other discussions; and not only do they make sense, they are an important part of the discipline.
Is the optimized implementation documented well enough that other people coming to it anew can understand it in another 10 years' time? How many of the magic constants have to be tweaked as the operating system evolves? Is it clear to maintenance programmers how they need to be tweaked? Does optimizing for the benchmark skew the implementation too far away from one common case where one is only wanting a few "y"s (because, say, the program being told "y" only asks 17 questions) resulting in every invocation of the yes program rapidly generating KiBs or even MiBs of output, that lives in pipe buffers in kernel space unused and only to be thrown away? Does it make more sense to put buffer size optimizations in the C library where all can benefit? What is the benefit of optimizing for Linux-only system calls on GNU Hurd? Will we optimize yes for GNU Hurd, too, in the same codebase? How much conditional compilation will we end up with? If the C library is improved to do better in the future, is it a problem that the optimized for benchmark program no longer automatically gains from it? How much of a problem is it that a GNU program is now not portable to systems other than Linux, and is locked into one operating system?
And what about other benchmarks? Many have noted that this benchmark pretty much relies on the fact that the output of the yes program is simply thrown away, and no real work is being done by anything else on the system. What about a benchmark where it is? What about a benchmark where it is important that yes issues a system call per line, yielding the CPU to other processes so that they can process that line before yes bothers to print the next? What about a benchmark that measures kernel memory usage and treats lower as better? What about benchmarks that measure how small the yes program is; not just in terms of code but in terms of memory, I/O, and CPU usages; because the memory, I/O, and CPU should actually be given to more important programs than yes on the system that are actually the system's main function? What about low impact?
Yes, it's fun to focus narrowly and optimize yes so that it generates as much output as it can for one specific toy usage. But in engineering one has to ask and think about so much more.
As a result you can process very large volumes of data and consume (not waste, consume) significant system resources to perform your processing.
Make the static array BUFSIZ * 1024 to trim the syscalls by a factor of 1000.
This is amazing. Maybe we can turn this into a verb? "I Volkswagened the common cases with precalced buffers".
Either way, it’d be expensive for you.
Realistically, not mentioning anyone or anything is the only complete defense. The truth still comes close.
1. Someone uses "volkswagen" as a verb meaning "cheat in benchmarks".
2. Volkswagen takes them to court, on the basis that it is slanderous or libellous to associate Volkswagen with cheating in benchmarks.
3. Counsel for the defence reminds the court that Volkswagen were in the news for a protracted period for a benchmark-rigging that saw them hit with a multi-billion-dollar fine, slashed a third off their stock price, saw their CEO resign, led to there being a "Volkswagen emissions scandal" page on Wikipedia, etc., etc., etc.
4. [THIS IS THE BIT I'D LIKE YOU TO FILL IN FOR ME.]
5. Volkswagen wins the case.
How exactly does step 4 go?
And they’d have a pretty good case with that, considering the original report all this media campaign was based on called out 6 carmakers, but the media (and the poster) only call out Volkswagen.
Incidentally, that word works even better in German, where every verb must end in "-en".
So, what's the distribution of #bytes read for runs of 'yes'? If we know that, is GNU 'yes' really faster than the simpler BSD versions?
Also, assuming this exercise still is somewhat worhtwhile, could startup time be decreased by creating a static buffer with a few thousand copies of "y\n"? What effect does that have on the size of the binary? I suspect it wouldn't get up much given that you can lose dynamic linking information (that may mean having to make a direct syscall, too).
Wouldn't that be entirely negligible compared to starting the program in the first place?
Why did the GNU developers go to such lengths to optimize the yes program? It's a tiny, simple shell utility that is mostly used for allowing developers to lazily "y" there way through confirm prompts thrown out by other shell scripts.
is this a case of optimization "horniness" (for lack of a better word) taken to its most absurd extreme, or is there some use case where making the yes program very fast is actually important?
Source: https://github.com/coreutils/coreutils/commit/35217221c211f3...
I've personally used it for generating repeating text and filling disks in system testing, so I appreciate it being faster at those tasks. I also sometimes use it as a signal generator for a hacky load generator, like so:
yes | xargs -L 1 -P NUM_PROCESSES -I {} curl SOME_TARGET_URL > /dev/null
This doesn't benefit from being faster per se, but I appreciate it using less CPU since I want to give curl as much system resources as possible.I would write() the buffer each time it gets enlarged, in order to improve startup speed.
Also: The reddit program has a bug if the size of the buffer is not a multiple of the input text size.
And it's increasing the buffer by incrementing one at a time, instead of copying the buffer to itself, reducing the number of loops needed (at cost of slightly more complicated math).
yes | ssh server "cat > /dev/null"
But yes take arguments so there might be more use cases: $ man yes
NAME
yes - output a string repeatedly until killed
SYNOPSIS
yes [STRING]...
yes OPTION
DESCRIPTION
Repeatedly output a line with all specified STRING(s), or 'y'.If "only few yes's are needed" then the slowdown to produce them will be inconsequential, whether they still fill a buffer in this case or not.
One could put a write in the memcpy loop, so that first it writes one copy of the string, then two, then 4, 8 (etc.), meaning time to first byte is short, but it quickly gets up to the eventual asymptotic speed with a full buffer.
( https://www.reddit.com/r/rust/comments/4wde08/optimising_yes... )
The author appears to be aware of that:
> Even with the function overheads and additional bounds checks of GNU's yes
How about a /proc/bin/yes for this? Like most /proc files, it would appear to be empty. Executing it would involve a fs/binfmt_proc.c file in the kernel source, which would be a handler for this sort of executable. That would get the job done entirely in the kernel.
`yes` will help me on the "see what happens when something uses all the CPU and memory" test case. Thanks Reddit/HN!
I also had to do the same test on windows, so python won
After reading here so many unfair critics and pedantic dislike over PHP[1][2][3][4][5][6], I just want to say: STFU.
[1] https://news.ycombinator.com/item?id=12706136
[2] https://news.ycombinator.com/item?id=3825227
[3] https://news.ycombinator.com/item?id=3824881
[4] https://news.ycombinator.com/item?id=1823022
[5] https://news.ycombinator.com/item?id=1819517
[6] https://news.ycombinator.com/item?id=1819413
... Just to name a few.
[quote]
It's hard to find a worse example than php, because when many "bad" languages fail in one or the other category, php seems to fail in most categories.
* Java has a really repetitive syntax, but it's libraries might make up for that.
* C++ with all it's features has bazillion ways to shoot yourself, but it's arguably fast and the syntax is bearable. ...
[/quote]
I am not sure what was the last time I had to choose a language by a single dimension. If we are talking about performance, scripting languages rarely shine. On the top of that, performance critical things in scripting languages are often implemented in a compiled language (like Pandas for example).
Except that, in the yes example, PHP shines by its performance.
Of course, you don't choose a language based on that on a single dimension, but speed is an important factor otherwise benchmarks would not exist.
FWIW, when I run the tests there's no substantial difference between Perl, LUA, or PHP.
>OS X just uses an old NetBSD version similar to OpenBSD's >NetBSD's is 139MiB/s, FreeBSD, OpenBSD, DragonFlyBSD have very similar code as NetBSD and are probably identical,
It really depends on how fast your memory and processor are. Everything there was benchmarked with an i7-4790 with DDR3-1600, and very very little running in the background.
Can we have a new flag for posts by people who don't know what they're doing so I can skip them? I am serious.