
A Little Story About the `yes` Unix Command - omn1
https://matthias-endler.de/2017/yes/
======
crehn
Just for fun:

GNU true.c:
[https://github.com/coreutils/coreutils/blob/master/src/true....](https://github.com/coreutils/coreutils/blob/master/src/true.c)

OpenBSD true.c: [http://cvsweb.openbsd.org/cgi-
bin/cvsweb/src/usr.bin/true/tr...](http://cvsweb.openbsd.org/cgi-
bin/cvsweb/src/usr.bin/true/true.c?rev=1.1&content-type=text/x-cvsweb-markup)

~~~
wahern
I'm surprised that neither GNU yes nor GNU cat uses splice(2). Here's what I
get. Note that ./rust is the final Rust program from the article modified to
print stats and exit after a set byte count[1], ./splice is a simple C program
that uses splice to copy the input string from a generated anonymous temporary
file to stdout, and ./consume uses splice to move data from stdin to
/dev/null. In all tests the default string of "y\n" is used.

    
    
      $ ./rust | cat >/dev/null
      5.220 GB/s (8589934592 bytes in 1.645 seconds)
    
      $ ./splice | cat >/dev/null
      14.514 GB/s (8589934592 bytes in 0.551 seconds)
    
      $ ./rust | ./consume 
      9.312 GB/s (8589934592 bytes in 0.922 seconds)
    
      $ ./splice | ./consume 
      25.118 GB/s (8589934592 bytes in 0.318 seconds)
    

As you can see, cat(1) is the real bottleneck.

splice is Linux-only. Most systems have sendfile(2), including Linux, but I
didn't test it. The implementation and semantics of sendfile vary across
platforms.

[1] I did this before I wrote ./consume (which takes an optional byte count
limit), having assumed that GNU cat was using splice. As cat doesn't have a
way to limit the number of bytes read/written, and other tools like head or
tail definitely don't use splice, the simplest way to limit the benchmark
without introducing a bottleneck was to have the producers themselves print
stats and exit. The relevant diff is

    
    
        + let mut count = 0u64 as usize;
        + let bytes = (1u64 << 33) as usize;
        ...
        -while locked.write_all(filled).is_ok() {}
        +while locked.write_all(filled).is_ok() {
        +  count += filled.len();
        +  if count >= bytes {
        +      break;
        +  }
        +}

~~~
pwg
> I'm surprised that neither GNU yes nor GNU cat uses splice(2). ... splice is
> Linux-only.

The fact that splice is Linux only is most likely why. GNU programs are
portable to many different OSes, many of which no longer exist in any real
form. They try to not use any 'single OS' specific feature if at all possible.

~~~
wahern
splice(2) and tee(2) were basically tailor made for cat(1) and tee(1), and for
some reason I was under the impression that GNU tee was using splice(2) and/or
tee(2). Using these could be trivial--just a few extra lines of code that
could fall-through to the existing methods, for a huge speed-up in
performance. (Performance matters because the consumers could be CPU bound,
and an inefficient cat or tee might be taking away resources that could be
used by the consumer.)

Regarding portability, GNU tail uses the Linux-specific inotify(2) to respond
faster to writes. Like alot of OSS, coreutils uses the BSD .d_type member
extension[1] of struct dirent to avoid unnecessary stat() calls. There are
many other more intrusive OS-specific details baked into coreutils, but often
it's the nature of the problem--in many situations you're dependent on
platform-specific details or features. For the most part, these nitty-gritty
platform-specific details are far more intrusive in terms of code complexity
than the performance optimizations.

[1] Missing from Solaris, and probably most other SysV derivatives.

------
aleden
This topic comes up every now and then. I thought this post was particularly
insightful,

"One thing to keep in mind when looking at GNU programs is that they're often
intentionally written in an odd style to remove all questions of Unix
copyright infringement at the time that they were written.

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://news.ycombinator.com/item?id=14543640](https://news.ycombinator.com/item?id=14543640)

------
dmitrygr
That last rust example is less readable than modern template-metaprogramming
variants of C++.

There is something elegant about being able to beat it out on speed with about
30 lines of C

~~~
a_t48
Simply putting a comment above each function would make it bearable.

~~~
dmitrygr
not as readable as this faster variant

    
    
      #include <sys/uio.h>
    
      //2048 "y\n" s
      static char __attribute__((aligned(4096))) str[] = "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n";
      static struct iovec v[] = {
      	{str, 4096},			//adjust number of these to taste
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      };
      
      
      int main(int argc, char** argv) {
      
      	while(1) {
      		//scatter-gather write (on linux there is basically no way for write to write an odd number of characters, except EOF, so we need not check for partial writes
      		(void)writev(1, v, sizeof(v) / sizeof(*v));
      	}
      	return 0;
      }

~~~
kibwen
Bah, that'll never pass code review. How about this:

    
    
      #include <sys/uio.h>
      
      #define str(x) #x
      #define expand(x) str(x)
      #define y1 y\n
      #define y2 expand(y1) expand(y1)
      #define y4 y2 y2
      #define y8 y4 y4
      #define y16 y8 y8
      #define y32 y16 y16
      #define y64 y32 y32
      #define y128 y64 y64
      #define y256 y128 y128
      #define y512 y256 y256
      #define y1024 y512 y512
      #define y2048 y1024 y1024
    
      //2048 "y\n" s
      static char __attribute__((aligned(4096))) str[] = y2048;
      static struct iovec v[] = {
      	{str, 4096},			//adjust number of these to taste
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      	{str, 4096},
      };
      
      
      int main(int argc, char** argv) {
      
      	while(1) {
      		//scatter-gather write (on linux there is basically no way for write to write an odd number of characters, except EOF, so we need not check for partial writes
      		(void)writev(1, v, sizeof(v) / sizeof(*v));
      	}
      	return 0;
      }

~~~
flukus
Both suffer from way to much binary bloat, for small performance hit we can
shave off a whole 4Kb.

Is anyone going to take a stab at an enterprise edition?

~~~
taneq
"Enterprise edition" would be the same thing but written in Java and require
an Oracle backend.

~~~
_jal
Don't forget LDAP, SAML and SNMP support. What's the OID for 'y' again?

~~~
viraptor
Could reuse type from 2.16.840.1.113883.18.347. It's an extended yes/no, to
account for "flavours of null". (Not even joking...)

~~~
_jal
I just died a little inside.

------
pixelbeat
The GNU variant was discussed recently at:
[https://news.ycombinator.com/item?id=14542938](https://news.ycombinator.com/item?id=14542938)

The commit that sped up GNU yes has a summary of the perf measurements:
[https://github.com/coreutils/coreutils/commit/3521722](https://github.com/coreutils/coreutils/commit/3521722)

yes can be used to generate arbitrary repeated data for testing or whatever,
so it is useful to be fast

------
dnel
Interesting, but is there actually a practical benefit of having GigaBytes of
the word yes being generated or is this all just optimisation porn?

~~~
dcosson
Seems like it might even be better if yes was rate limited to a couple of
lines per second or less. It would be more than enough for its intended use
and when users inevitably run it in a shell to see what it does it wouldn't
generate tons of output.

It seems like a violation of "do one thing well" to use it for generating data
for testing, isn't dd and /dev/zero a better way to do that?

------
kccqzy
Really? You didn’t even mention reducing system calls? That’s basically what
full_write does: try to output a whole buffer with one system call.

In your regular program, even with just a normal setvbuf call to set up block
buffering would make a huge difference.

------
xurukefi

      import sys
    
      while True:
          sys.stdout.write("y\n" * 2**16)
    

gives me over 4GiB/s

~~~
omlettehead
Sure, but you're building strings in memory. It might not be a lot of memory,
especially if you're able to run Python, but the native `yes` command can run
on the smallest of embedded systems, which is why its speed is impressive.

------
Myrmornis
The author says "no magic here" for the C version:

    
    
      for (;;)
        printf("%s\n", argc>1? argv[1]: "y");
    

but it's not totally obvious to me whether the argument to printf would be
evaluated on every iteration of the for loop or not. Does the compiler know
that those don't change, and is the answer to that question fairly basic C
knowledge or not?

~~~
pkaye
The compiler can assume that argc will not change within the loop so it can
optimize it. I just looked up the assembly output from gcc and it pulls the
argc>1 outside of the loop and replaced the printf with puts. So something
like:

    
    
      if argc>1
          for (;;) puts(argv[1])
      else
          for (;;) puts("y")
    

The replacing of printf with puts is based on gcc having specific knowledge
about the printf library function.

~~~
ben_bai
This is exactly the OpenBSD implementation.

------
billsmithaustin
Those performance improvements make the code more complicated too. How fast
does the yes command actually need to be?

~~~
VintageCool
I would desire the yes command to be as slow as possible while still
performing its basic function of automatically confirming questions that come
up during an installation.

This is because I clusterssh into 40 machines and hey I haven't formally
accepted them into my known_hosts file yet so I type "yes" to acknowledge
them, but whoops I had already accepted two of them so now they are spitting
out the letter 'y' as fast as they possibly can and now I have to wait for all
of that output to transfer over the wire onto my machine despite pressing
ctrl+c a minute ago.

~~~
Blackthorn
Then just use yes | head -50? There's no need to artificially slow it down
when there are more reasonable means of capping output than relying on SIGINT.

~~~
kqr
yes | head -50 will not accept new signatures into known_hosts.

------
dreta
Can anybody explain to me what's this syntax? This is the first time i see
anything like it, and i've been programming in C since i was a teenager.

    
    
        main(argc, argv)
        char **argv;
        {
        }

~~~
ecma
That's a K&R (Kernighan and Ritchie) style function declaration. Compilers
still support it but the version you'd be more familiar with (ANSI C style)
has been standard since at least the late 80s IIRC. ANSI C was standardised in
1989 but that process had been in progress for something like 5 years
beforehand.

~~~
colejohnson66
Does it have to do with how, in BCPL (one of C’s ancestors), everything was
basically an integer? So everything is an integer unless told otherwise?

~~~
boomlinde
Specifically, everything is an integer with the auto lifetime (gets discarded
with the stack frame) unless otherwise specified.

I think most modern compilers will warn on encountering such short hand due to
the common error of accidentally declaring integers when you meant to assign a
value to a variable you forgot to declare.

------
blibble
all the reddit users from the linked article missed the SIGPIPE trick!

you don't need to check the return value from write() as your process will be
terminated with SIGPIPE if it tries writing to a closed pipe.

saying that, none of them check the return code correctly: if the consumer
only reads a byte at a time you could eventually get 'yyyyyyyyyyyyyyyyyy'
(without any newlines)

quite impressive that so many implementations of "yes" have the same bug :)

~~~
dmitrygr
please demonstrate that consumer.

on linux there is no way to do this without your consumer being a kernel
driver that actually [purposefully accepts one byte at a time. all file io and
pipes have basically no way to accept one byte only (except EOF)

~~~
blibble
you wouldn't see it on Linux as pipes are implemented using complete pages
(always powers of 2), but there's probably some OS out there with a different
implementation where you can set the buffer size to be an odd number, and then
you'd see the bug with plain simple cat

I didn't say you'd likely see it in practice :)

------
rurban
Not impressive at all. Basically he had to write a lot of manual buffering
code to reach GNU yes throughput. I would suggest to use an infrastructure
which already provides proper IO.

e.g. perl or clisp.

    
    
        $ perl -C0 -e'print "y\n" x (1024*8) while 1' | pv > /dev/null
        ^C.4GiB 0:00:11 [6.17GiB/s] [              <=>                                                     ]
    
        $ yes | pv > /dev/null
        ^C.3GiB 0:00:07 [6.64GiB/s] [         <=>
    

And with linux-only vmsplice the record stands at 123GB/s
[https://www.reddit.com/r/unix/comments/6gxduc/how_is_gnu_yes...](https://www.reddit.com/r/unix/comments/6gxduc/how_is_gnu_yes_so_fast/diua761/)

------
13of40
I guess a stretch goal would be to make a "shouldi" command that can consume
more y's per second than yes can produce. Of course at that point the shell
itself would probably become the bottleneck.

~~~
glandium
The shell can't be a bottleneck. If you run `yes | shouldi`, all the shell
does is setup the pipe, give one of its ends as stdin to shouldi, and the
other end as stdout to yes.

------
nurettin
Rust strike force is back, rewriting Unix tools in a sane language

------
nodesocket

        main(argc, argv)
        char **argv;
        {
          for (;;)
             printf("%s\n", argc>1? argv[1]: "y");
        }
    

Is beautiful, readable, and minimal. The "optimized" Rust version is
complicated and over 50 lines of code. At what point does performance
optimization go too far?

~~~
1_2__4
As a non-Rust developer my first thought upon seeing the optimized version was
“On the other hand let’s not visit Rust, ‘tis a silly place.”

~~~
LEGOlord208
There was a lot of things about Rust that made think it was weird before I
actually gave in and tried it. Now I adore Rust. You should try it.

------
cannikin
Funny, I just learned about this command a couple of days ago as a simple way
to max out your CPU. I was trying to drain the battery on my Macbook Pro and
running 4 of these at the same time did the trick nicely. Redirected to
/dev/null and run in the background: "yes > /dev/null &"

------
jwilk
Curiously, yes(1) is not standardized by POSIX.

Are there any UNIX systems that don't have it?

------
jlebrech
maybe it's supposed to be slow, wouldn't a faster 'yes' spam stdin much
faster? you only need to hit yes occasionally and faster than a second

------
feelin_googley
k3

    
    
       while[1;`0:"yes\n"]

------
naranha
any idea why it is so much faster on fedora 26?

$ yes | pv -r > /dev/null

[10.3GiB/s]

~~~
totony
benchmarking is useless when comparing different conditions

ie your computer is probably faster than op's

~~~
naranha
Of course. Though 37MiB/s compared to 10Gi/s probably means that the GNU
version used by Fedora has a faster implementation than Apple's version.

~~~
totony
You're right, my mistake, I didn't check the speed of the OP's

------
snikch
I use the yes command to defrost my lunch. I open up a couple of tabs running

yes > /dev/null

Then place my frozen lunch on the back of my macbook. Give it an hour or so
and boom, defrosted.

~~~
TFortunato
Hah, nice! Somewhat related, I once worked on a project that used a high
reliability PC meant for extended use in "extreme" outdoor environments. One
of the issues they (manufacturer) worried about was the pcb and solder joints
experiencing thermal fatigue fron lots of seasonal and night/day temperature
cycles.

Their ingenious solution was to always run the system towards the warmer end
of its spec, and so it included a program that would monitor the temperature
inside the case, and would spawn/kill a bunch of threads doing compute
intensive math in order to keep the temperature constant when the users
workload wasn't enough!

~~~
Asooka
Couldn't they slow down the cooling fans? Or was it a passively cooled PC?

~~~
TFortunato
Passively cooled. The thing was originally meant for extended use in "extreme"
industrial environments (eg at a power substation, inside a wind turbine,
etc..), so it had no vents or moving parts at all. Everything was heatpiped to
the metal case, which looked like a heatsink.

Similar looking model from same company:
[https://selinc.com/products/3355/](https://selinc.com/products/3355/)

------
sabujp
tldr; watched pootie tang

------
Annatar

      env::args().nth(1).unwrap_or("y".into());
    

this ridiculously complicated syntax to perform such a simple thing is why I
will never accept Rust. What a clumsy, ugly language. I’ll just stick with
learning ANSI common LISP, that pays immediate dividends.

~~~
simias
It's fairly idiomatic so you get used to it but if you prefer something a
little more C-ish you can write it like that (as a side note the code in TFA
forgets to import std::env and BufWriter):

    
    
        use std::env::args;
    
        fn main() {
        
           let expletive =
                match args().nth(1) {
                    Some(arg) => arg,
                    None => "y".into(),
                };
        
            loop {
                println!("{}", expletive);
            }
        }
    

This is the equivalent to the unoptimized C version. I would argue that it's a
little more readable too, substituting "loop" instead of the idiomatic
"for(;;)" and the more verbose match syntax instead of the ternary (the terser
rust equivalent being the `unwrap_or` you seemed to dislike).

Personally I think I'd use the "unwrap_or" version, when you're familiar with
the language it's completely transparent, easier to parse and expresses intent
better I think. For an outsider I can see why it would look like a strange
incantation though (but the same could be said about ternaries or CL's "do"
construct for instance).

The only thing I'd deem inelegant here is the `"y".into()`. It's probably not
obvious what it does and why it's necessary to somebody not familiar with
Rust.

~~~
Annatar
That example is indeed more readable, but it still reads like methods on
objects which is a fundamental issue for me. Any non trivial program written
in an object-oriented paradigm eventually becomes incomprehensible, whereas
the functional approach doesn’t rely on the state machine model. Two different
approaches. I didn’t care for objects back in 1990 and I certainly care even
less for them now. I upvoted your example in appreciation for the effort you
went through though.

~~~
simias
I agree that for these types of constructs a more functional style could look
better. That being said the OOP approach as the merit of reading in the proper
order: you get your args, pick the 1st and then use that or the default "y"
value.

If you rewrite this to use function calls instead of methods you end up with
something like:

    
    
        (or (cadr (args)) "y")
    

Which is fine if you're used to lisp syntax but it's really a matter of taste
at this point.

