tech-userlevel archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Possible slight speedup to non-interactive /bin/sh startup



Inspired by something joerg@ said a while ago (about not wanting
more delays in sh startup by linking against an extra library)
I have been wondering whether we need the cost of linking libedit
and libtermlib into non-interactive shells.

So, I made the patch appended below (assuming I remember it!) to
remove those libraries from build time linking, and instead
dynamically load them in sh when the shell first becomes interactive.

This should make essentially no diffeence to an interactive sh
(nothing you'll ever notice) but might just speed up non-interactive
shells a little (as they will never waste time linking those
libraries that they will never use.)

And it seems to - in the most absurd of benchmarks, running the
simplest possible sh invocation ("sh -c :") it seems to cut about
15% from the total (user+system) cpu time (and as that does not
normally block for anything, from the elapsed time as well).

Before you get all excited, on my system that was about 170 us
(yes, us, not ms) per invocation (measured over 10000 runs.)

To be slightly more realistic, for the ATF sh tests (which run
about 4K shells) - which are about the simplest actually useful
sh invocations we're ever likely to see, the cpu+sys time for
the full set of tests was reduced about 4% (elapsed time just 1.3%).

And for a full ATF test run (all the tests we have) the cpu+sys
reduction was just about 2.7% ... this is probably a more representative
measure of what might be achieved for smallish shell scripts, like
those encountered in Makefiles (etc).   The difference for longer
scripts (autoconf etc) is likely to be negligible.

Is this something that is worth pursuing?

If so, I think I need some assistance with the required Makefile
magic ... what is there now (see the patch) builds OK (builds
everything OK, and it all runs correctly from what I can tell) but
has two (that I know of) obvious deficiencies:

1. it needs a way to find out what the correct name for the libedit
shared library to use is, from the build system, other Makefiles,
or something (it currently just has "libedit.so.3" embedded in it,
with no path, and simply knowing what major version is current.)

2. it needs a way to turn itself off when building /rescue/sh - that
builds OK as it is, and /rescue/sh runs, but (kind of obviously) is
unable to dynamically link the library into the static binary.   That
one needs to build the old way (this is just an #ifdef in the code).

Help with those )if this is worth anything) would be appreciated.

Any other suggestions about the method I used gratefully received,
dynamic linking is a new experience for me.   You don't need to
know anything about the shell (well, just maybe except that out2str(s)
is more or less fprintf(stderr, "%s", s)) to follow what this patch
does ... I simply hunted down all the libedit calls in the shell,
added the names of those functions to a table, and at init time
dlopen() the library, lookup the symbols we need, and then call
via the addresses returned (via #define's of the function names, so
the code using them does not need to change.)

kre

ps: the code size grows about 50 bytes (just under) for this (amd64), plus
about 100+ bytes of data (almost all that table, and the function names
as strings) - and none of this affects SMALL shells which don't do
history or command line editing at all (so don't use libedit or libtermlib).

Index: Makefile
===================================================================
RCS file: /cvsroot/src/bin/sh/Makefile,v
retrieving revision 1.115
diff -u -r1.115 Makefile
--- Makefile	28 Oct 2018 18:13:47 -0000	1.115
+++ Makefile	11 Feb 2019 01:39:30 -0000
@@ -14,8 +14,9 @@
 
 DPSRCS+=${GENHDRS}
 
-LDADD+=	-ledit -lterminfo
-DPADD+=	${LIBEDIT} ${LIBTERMINFO}
+#LDADD+=	-ledit -lterminfo
+#DPADD+=	${LIBEDIT} ${LIBTERMINFO}
+CPPFLAGS+=	-DDEFERRED_LIBS='"libedit.so.3"'
 
 # Environment for scripts executed during build.
 SCRIPT_ENV= \
Index: histedit.c
===================================================================
RCS file: /cvsroot/src/bin/sh/histedit.c,v
retrieving revision 1.55
diff -u -r1.55 histedit.c
--- histedit.c	10 Feb 2019 19:21:52 -0000	1.55
+++ histedit.c	11 Feb 2019 01:39:31 -0000
@@ -41,6 +41,8 @@
 #endif
 #endif /* not lint */
 
+#define	DEFERRED_FUNCS_HERE	/* for myhistedit.h if DEFERRED_LIBS defined */
+
 #include <sys/param.h>
 #include <paths.h>
 #include <stdio.h>
@@ -60,6 +62,7 @@
 #include "myhistedit.h"
 #include "error.h"
 #include "alias.h"
+
 #ifndef SMALL
 #include "eval.h"
 #include "memalloc.h"
@@ -74,6 +77,10 @@
 
 STATIC const char *fc_replace(const char *, char *, char *);
 
+#ifdef DEFERRED_LIBS
+static int he_lib_init(void);
+#endif
+
 #ifdef DEBUG
 extern FILE *tracefile;
 #endif
@@ -86,10 +93,25 @@
 histedit(void)
 {
 	FILE *el_err;
+#ifdef DEFERRED_LIBS
+	static int dl_done = 0;
+#endif
 
 #define editing (Eflag || Vflag)
 
 	if (iflag == 1) {
+#ifdef DEFERRED_LIBS
+		if (dl_done < 0)
+			return;
+		INTOFF;
+		if (dl_done == 0)
+			dl_done = he_lib_init();
+		INTON;
+		if (dl_done < 0) {
+		    out2str("sh: cannot load library for history/editing\n");
+		    return;
+		}
+#endif
 		if (!hist) {
 			/*
 			 * turn history on
@@ -573,7 +595,28 @@
 	}
 	return (he.num);
 }
-#else
+
+#ifdef DEFERRED_LIBS
+static int
+he_lib_init(void)
+{
+	struct he_funcs *hef;
+	void *handle;
+
+	handle = dlopen(DEFERRED_LIBS, RTLD_NOW|RTLD_GLOBAL);
+	if (handle == NULL)
+		return -1;
+
+	for (hef = he_funcs; hef->hef_name != NULL; hef++) {
+		hef->hef_addr = dlsym(handle, hef->hef_name);
+		if (hef->hef_addr == NULL)
+			return -1;
+	}
+	return 1;
+}
+#endif
+
+#else /* ie: SMALL */
 int
 histcmd(int argc, char **argv)
 {
@@ -586,4 +629,4 @@
 	error("not compiled with history support");
 	/* NOTREACHED */
 }
-#endif
+#endif /* SMALL */
Index: myhistedit.h
===================================================================
RCS file: /cvsroot/src/bin/sh/myhistedit.h,v
retrieving revision 1.13
diff -u -r1.13 myhistedit.h
--- myhistedit.h	28 Jun 2017 13:46:06 -0000	1.13
+++ myhistedit.h	11 Feb 2019 01:39:32 -0000
@@ -46,3 +46,47 @@
 int not_fcnumber(char *);
 int str_to_event(const char *, int);
 
+
+#if defined(DEFERRED_LIBS) && !defined(SMALL)
+#include <dlfcn.h>
+
+extern struct he_funcs {
+	const char * const hef_name;
+	void		*  hef_addr;
+} he_funcs[];
+#define	HISTORY_INIT	0
+#define	HISTORY_END	(HISTORY_INIT + 1)
+#define	HISTORY		(HISTORY_END + 1)
+#define	EL_INIT		(HISTORY + 1)
+#define	EL_END		(EL_INIT + 1)
+#define	EL_SET		(EL_END + 1)
+#define	EL_SOURCE	(EL_SET + 1)
+#define	EL_GETS		(EL_SOURCE + 1)
+#define	EL_FN_COMPLETE	(EL_GETS + 1)
+
+#ifdef	DEFERRED_FUNCS_HERE
+struct he_funcs he_funcs[] = {
+		{ "history_init", NULL },
+		{ "history_end", NULL },
+		{ "history", NULL },
+		{ "el_init", NULL },
+		{ "el_end", NULL },
+		{ "el_set", NULL },
+		{ "el_source", NULL },
+		{ "el_gets", NULL },
+		{ "_el_fn_complete", NULL },
+		{ NULL, NULL }
+};
+#endif
+
+#define	history_init	(*(History *(*)(void))(he_funcs[HISTORY_INIT].hef_addr))
+#define	history_end	(*(void (*)(History *))(he_funcs[HISTORY_END].hef_addr))
+#define	history		(*(int (*)(History *, HistEvent *, int, ...))(he_funcs[HISTORY].hef_addr))
+#define	el_init		(*(EditLine * (*)(const char *, FILE *, FILE*, FILE *))(he_funcs[EL_INIT].hef_addr))
+#define	el_end		(*(void (*)(EditLine *))(he_funcs[EL_END].hef_addr))
+#define	el_set		(*(int (*)(EditLine *, int, ...))(he_funcs[EL_SET].hef_addr))
+#define	el_source	(*(int (*)(EditLine *, const char *))(he_funcs[EL_SOURCE].hef_addr))
+#define	el_gets		(*(const char * (*)(EditLine *, int *))(he_funcs[EL_GETS].hef_addr))
+#define	_el_fn_complete	(*(unsigned char (*)(EditLine *, int) )(he_funcs[EL_FN_COMPLETE].hef_addr))
+
+#endif	/* DEFERRED_LIBS && ! SMALL */




Home | Main Index | Thread Index | Old Index