summaryrefslogtreecommitdiff
path: root/src/yes.c
diff options
context:
space:
mode:
authorPádraig Brady <P@draigBrady.com>2015-03-09 19:27:32 +0000
committerPádraig Brady <P@draigBrady.com>2015-03-10 00:12:30 +0000
commit35217221c211f3116f374f305654462195aa634a (patch)
tree233b01c55819c0d725c2f6ace6c736bd857a0b9f /src/yes.c
parent3c3b760c34834638edf6e875fbb722f4db7cc938 (diff)
downloadcoreutils-35217221c211f3116f374f305654462195aa634a.tar.xz
yes: output data more efficiently
yes(1) may be used to generate repeating patterns of text for test inputs etc., so adjust to be more efficient. Profiling the case where yes(1) is outputting small items through stdio (which was the default case), shows the overhead of continuously processing small items in main() and in stdio: $ yes >/dev/null & perf top -p $! 31.02% yes [.] main 27.36% libc-2.20.so [.] _IO_file_xsputn@@GLIBC_2.2.5 14.51% libc-2.20.so [.] fputs_unlocked 13.50% libc-2.20.so [.] strlen 10.66% libc-2.20.so [.] __GI___mempcpy 1.98% yes [.] fputs_unlocked@plta Sending more data per stdio call improves the situation, but still, there is significant stdio overhead due to memory copies, and the repeated string length checking: $ yes "`echo {1..1000}`" >/dev/null & perf top -p $! 42.26% libc-2.20.so [.] __GI___mempcpy 17.38% libc-2.20.so [.] strlen 5.21% [kernel] [k] __srcu_read_lock 4.58% [kernel] [k] __srcu_read_unlock 4.27% libc-2.20.so [.] _IO_file_xsputn@@GLIBC_2.2.5 2.50% libc-2.20.so [.] __GI___libc_write 2.45% [kernel] [k] system_call 2.40% [kernel] [k] system_call_after_swapgs 2.27% [kernel] [k] vfs_write 2.09% libc-2.20.so [.] _IO_do_write@@GLIBC_2.2.5 2.01% [kernel] [k] fsnotify 1.95% libc-2.20.so [.] _IO_file_write@@GLIBC_2.2.5 1.44% yes [.] main We can avoid all stdio overhead by building up the buffer _once_ and outputting that, and the profile below shows the bottleneck moved to the kernel: $ src/yes >/dev/null & perf top -p $! 15.42% [kernel] [k] __srcu_read_lock 12.98% [kernel] [k] __srcu_read_unlock 9.41% libc-2.20.so [.] __GI___libc_write 9.11% [kernel] [k] vfs_write 8.35% [kernel] [k] fsnotify 8.02% [kernel] [k] system_call 5.84% [kernel] [k] system_call_after_swapgs 4.54% [kernel] [k] __fget_light 3.98% [kernel] [k] sys_write 3.65% [kernel] [k] selinux_file_permission 3.44% [kernel] [k] rw_verify_area 2.94% [kernel] [k] __fsnotify_parent 2.76% [kernel] [k] security_file_permission 2.39% yes [.] main 2.17% [kernel] [k] __fdget_pos 2.13% [kernel] [k] sysret_check 0.81% [kernel] [k] write_null 0.36% yes [.] write@plt Note this change also ensures that yes(1) will only write complete lines for lines shorter than BUFSIZ. * src/yes.c (main): Build up a BUFSIZ buffer of lines, and output that, rather than having stdio process each item. * tests/misc/yes.sh: Add a new test for various buffer sizes. * tests/local.mk: Reference the new test. Fixes http://bugs.gnu.org/20029
Diffstat (limited to 'src/yes.c')
-rw-r--r--src/yes.c43
1 files changed, 41 insertions, 2 deletions
diff --git a/src/yes.c b/src/yes.c
index b35b13f75..d6dd4b1ef 100644
--- a/src/yes.c
+++ b/src/yes.c
@@ -58,6 +58,10 @@ Repeatedly output a line with all specified STRING(s), or 'y'.\n\
int
main (int argc, char **argv)
{
+ char buf[BUFSIZ];
+ char *pbuf = buf;
+ int i;
+
initialize_main (&argc, &argv);
set_program_name (argv[0]);
setlocale (LC_ALL, "");
@@ -77,9 +81,44 @@ main (int argc, char **argv)
argv[argc++] = bad_cast ("y");
}
- while (true)
+ /* Buffer data locally once, rather than having the
+ large overhead of stdio buffering each item. */
+ for (i = optind; i < argc; i++)
+ {
+ size_t len = strlen (argv[i]);
+ if (BUFSIZ < len || BUFSIZ - len <= pbuf - buf)
+ break;
+ memcpy (pbuf, argv[i], len);
+ pbuf += len;
+ *pbuf++ = i == argc - 1 ? '\n' : ' ';
+ }
+ if (i < argc)
+ pbuf = NULL;
+ else
+ {
+ size_t line_len = pbuf - buf;
+ size_t lines = BUFSIZ / line_len;
+ while (--lines)
+ {
+ memcpy (pbuf, pbuf - line_len, line_len);
+ pbuf += line_len;
+ }
+ }
+
+ /* The normal case is to continuously output the local buffer. */
+ while (pbuf)
+ {
+ if (write (STDOUT_FILENO, buf, pbuf - buf) == -1)
+ {
+ error (0, errno, _("standard output"));
+ return EXIT_FAILURE;
+ }
+ }
+
+ /* If the data doesn't fit in BUFSIZ then it's large
+ and not too inefficient to output through stdio. */
+ while (! pbuf)
{
- int i;
for (i = optind; i < argc; i++)
if (fputs (argv[i], stdout) == EOF
|| putchar (i == argc - 1 ? '\n' : ' ') == EOF)