Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

"echo" is typically part of the shell, it doesn't run another executable, so I don't think that would save anything.


Shh! Don’t spoil things! Picowatt-hours, I say. Picowatt-hours!

But seriously, although echo is typically a shell built-in and distinctly faster than /usr/bin/echo, it’s still much slower than <<<, presumably because it still has to set up a pipe and an extra… shall we say pseudoprocess.

Comparing behaviours for feeding text into `true` (typically a shell built-in, so that process spawn times doesn’t drown the signal):

  try() {
      echo -e "\e[32;1m$1\e[m"
      for (( run = 0; run < 5; run++ )); do
          time (for (( i = 0; i < 1000; i++ )); do
              $2
          done)
      done
  }

  a() { /usr/bin/echo .dump | true; }; try /usr/bin/echo a
  b() {          echo .dump | true; }; try echo          b
  c() {            <<<.dump   true; }; try '<<<'         c
  d() {                       true; }; try "no piping"   d
My best times of the five runs, under bash/zsh, expressed in time per iteration:

• /usr/bin/echo: 750μs/845μs

• echo: 469μs/371μs

• <<<: 11μs/31μs

• No piping: 3μs/10μs

So… yeah, on a very slightly older or slower machine than mine, using <<< may save you more than half a millisecond. That’s a much bigger difference than I expected—I was expecting it to be well under 200μs, maybe under 100μs, though the more I think about it the more I realise my expectation may have been unreasonable.


Improved benchmarking script (I just wasn’t thinking carefully at the time, just getting something out quick; but eval is obviously better than requiring a function):

  try() {
      echo -e "\e[32;1m$1\e[m"
      for (( run = 0; run < 5; run++ )); do
          time (for (( i = 0; i < 1000; i++ )); do
              eval $1
          done)
      done
  }

  try "/usr/bin/echo .dump | true"
  try          "echo .dump | true"
  try            "<<<.dump   true"
  try                       "true"


I also want to note that this is thoroughly into the nanowatt-hour scale (even at the unrealistically low figure of 1W power consumption, 300μs makes it 83⅓ nWh). Tens or hundreds of thousands of picowatt-hours!


There is another difference that users may care about here. zsh will create a temp file for each here-string in the here-string version, and bash may do too¹. Whether those files hit a disk is a matter of system configuration, and whether such a disk is magically quick or swirling rust is a different issue too.

[Just noting that the implementation here isn't equivalent for nerdsnipe-ery, not arguing for real attempts at optimisation of simple pipelines.]

¹ Always with older versions, only for large strings with newer versions.


Interesting, didn’t know about that. Hadn’t thought about it too deeply.

  python <<<'import subprocess; subprocess.run(["ls", "-la", "/proc/self/fd"])'
Running under zsh, link 0 -> '/tmp/zshqqjTS0 (deleted)'; under bash, link 0 -> 'pipe:[1509353]'.

(I know <(…) is substituted in zsh with /proc/self/fd/… for a fd corresponding to a pipe:[…], and regular | piping makes 0 be a pipe:[…].)

I know some common configurations use concrete /tmp. Mine is tmpfs.

Thanks for the info! I like sharing these kinds of things because I have found them interesting and expect a few others will too, and people often add to them details I hadn’t known and like to know!


Fellow shell trivia enthusiast, hi!

    $ bash -c 'realpath /dev/stdin <<< small'
    /proc/79631/fd/pipe:[5282632]
    $ bash -c 'realpath /dev/stdin <<< $(printf "%65536s")'
    /tmp/sh-thd.yZ2L6p (deleted)
You can see the cutover on buffer size from my install of bash 5.2.15. Anything under 64k of here-string will still use a pipe.

In case you weren't aware, you can also force a "real" file with zsh process substitution by using =(…)¹. It can be useful when the tool you're using doesn't behave correctly with <(…), if it wishes to seek across it for example. Sadly, =(…) isn't supported in bash [yet?].

¹ In your case it still wouldn't hit a disk as it is in /tmp, but it at least becomes seekable.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: