From: Pádraig Brady
Date: Mon, 9 Mar 2015 19:27:32 +0000 (+0000)
Subject: yes: output data more efficiently
X-Git-Tag: v8.24~105
X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=35217221c211f3116f374f305654462195aa634a;p=thirdparty%2Fcoreutils.git
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
---
diff --git a/src/yes.c b/src/yes.c
index b35b13f754..d6dd4b1ef9 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)
diff --git a/tests/local.mk b/tests/local.mk
index 9a5208082e..56cba699a6 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -402,6 +402,7 @@ all_tests = \
tests/misc/uniq.pl \
tests/misc/uniq-perf.sh \
tests/misc/xattr.sh \
+ tests/misc/yes.sh \
tests/tail-2/wait.sh \
tests/tail-2/retry.sh \
tests/tail-2/symlink.sh \
diff --git a/tests/misc/yes.sh b/tests/misc/yes.sh
new file mode 100755
index 0000000000..4ed300a4fd
--- /dev/null
+++ b/tests/misc/yes.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+# Validate yes buffer handling
+
+# Copyright (C) 2015 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see