Re: [dev] [PATCH][RFC] Add a basic version of tr

From: Silvan Jegen <s.jegen_AT_gmail.com>
Date: Wed, 15 Jan 2014 20:43:54 +0100

On Wed, Jan 15, 2014 at 11:27:23AM +0000, sin wrote:
> On Tue, Jan 14, 2014 at 09:35:11AM +0100, Silvan Jegen wrote:
> > On Tue, Jan 14, 2014 at 12:22 AM, <q_AT_c9x.me> wrote:
> > > On Mon, Jan 13, 2014 at 11:19:49AM -0800, Silvan Jegen wrote:
> > >> I have rewritten "tr" to use mmap and the wchar.h functions. It seems
> > >> to be quite slow but as far as I can tell it works reasonably well (at
> > >> least when using a UTF-8 locale). Comments/review and testing welcome
> > >> (I am relatively new to C so beware)!
> > >>
> > >> + void (*mapfunc) (const wchar_t*, char*);
> > >> +
> > >> + setlocale(LC_ALL, "");
> > >> +
> > >> + mappings = (wchar_t *) mmap(NULL, 0x110000 * sizeof(wchar_t), PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
> > >> +
> > >> + if(argc >= 2) {
> > >> + parse_mapping(argv[0], argv[1], mappings);
> > >> + mapfunc = map_to_set;
> > >> + } else {
> > >> + parse_mapping(argv[0], NULL, mappings);
> > >> + mapfunc = map_to_null;
> > >> + }
> > >> +
> > >> + while(afgets(&buf, &size, stdin))
> > >> + mapfunc(mappings, buf);
> > >
> > > Hi,
> > >
> > > if you want it to be faster I would suggest dropping the function
> > > pointer so the compiler can inline instead of doing an indirect
> > > call.
>
> > I wasn't aware of the performance implications when using a function pointer.
>
> How slow is it currently? Can you provide your benchmark measurements?

I only did some superficial testing running 'time tr di DI' on a text
file 152MB in size (on my desktop while playing music and running
transmission-daemon).

Using the function pointer:

real 0m6.816s
user 0m5.567s
sys 0m0.170s

real 0m8.070s
user 0m5.783s
sys 0m0.167s

real 0m7.921s
user 0m5.697s
sys 0m0.183s

real 0m8.295s
user 0m5.713s
sys 0m0.187s

real 0m8.533s
user 0m5.920s
sys 0m0.207s

real 0m7.840s
user 0m5.683s
sys 0m0.167s

real 0m7.830s
user 0m5.563s
sys 0m0.193s

real 0m7.862s
user 0m5.590s
sys 0m0.167s

real 0m7.145s
user 0m5.743s
sys 0m0.163s

real 0m7.973s
user 0m5.827s
sys 0m0.183s

Average real: 7.828500000000


No function pointer:

real 0m6.859s
user 0m5.690s
sys 0m0.183s

real 0m7.895s
user 0m5.647s
sys 0m0.207s

real 0m8.170s
user 0m5.667s
sys 0m0.183s

real 0m7.872s
user 0m5.660s
sys 0m0.180s

real 0m8.135s
user 0m5.713s
sys 0m0.173s

real 0m8.166s
user 0m5.697s
sys 0m0.160s

real 0m8.073s
user 0m5.737s
sys 0m0.130s

real 0m8.007s
user 0m5.693s
sys 0m0.217s

real 0m7.801s
user 0m5.553s
sys 0m0.150s

real 0m7.858s
user 0m5.673s
sys 0m0.177s

Average real: 7.883600000000


GNU coreutils' tr for comparison:

real 0m1.411s
user 0m0.123s
sys 0m0.167s

real 0m2.744s
user 0m0.107s
sys 0m0.183s

real 0m2.618s
user 0m0.127s
sys 0m0.180s

real 0m2.248s
user 0m0.093s
sys 0m0.167s

real 0m2.510s
user 0m0.090s
sys 0m0.183s

real 0m2.234s
user 0m0.120s
sys 0m0.180s

real 0m2.512s
user 0m0.127s
sys 0m0.157s

real 0m2.602s
user 0m0.097s
sys 0m0.173s

real 0m2.517s
user 0m0.127s
sys 0m0.157s

real 0m2.480s
user 0m0.103s
sys 0m0.157s

Note, though, that GNU's tr does not seem to handle Unicode at all[1]
while this version of tr, according to "perf record/report", seems to
spend most of its running time in the Unicode handling functions of glibc.


> Most tools in sbase are really not what I'd call blazingly fast and
> that is not the point. I like to keep things simple and reliable
> as much as possible. If we can optimize it without sacrificing simplicity
> then go for it.
>
> I do not think inlining makes sense here at all.

By no means was this any serious benchmarking but eliminating the function
pointer did not seem to make an obvious difference.

I would still go for the function-pointer-less version of the
code since it actually is one line shorter, I think. The second,
function-pointer-less version of the code can be found below.

I will start writing a man page (possibly based on the GNU one) as soon
as I find the time (hopefully in the next few days).

[1] http://en.wikipedia.org/wiki/Tr_(Unix)


--->8---

Subject: [PATCH] Add the tr program

---
 Makefile |   1 +
 tr.c     | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 141 insertions(+)
 create mode 100644 tr.c
diff --git a/Makefile b/Makefile
index 81dfaf6..ee84221 100644
--- a/Makefile
+++ b/Makefile
_AT_@ -81,6 +81,7 @@ SRC = \
 	tee.c      \
 	test.c     \
 	touch.c    \
+	tr.c       \
 	true.c     \
 	tty.c      \
 	uname.c    \
diff --git a/tr.c b/tr.c
new file mode 100644
index 0000000..b163f56
--- /dev/null
+++ b/tr.c
_AT_@ -0,0 +1,140 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <locale.h>
+#include <wchar.h>
+#include "text.h"
+#include "util.h"
+
+static void
+usage(void)
+{
+	eprintf("usage: %s set1 [set2]\n", argv0);
+}
+
+void
+handleescapes(char *s)
+{
+	switch(*s) {
+	case 'n':
+		*s = '\x0A';
+		break;
+	case 't':
+		*s = '\x09';
+		break;
+	case '\\':
+		*s = '\x5c';
+		break;
+	}
+}
+
+void
+parsemapping(const char *set1, const char *set2, wchar_t *mappings)
+{
+	char *s;
+	wchar_t runeleft;
+	wchar_t runeright;
+	int leftbytes;
+	int rightbytes;
+	size_t n = 0;
+	size_t lset2;
+
+	if(set2) {
+		lset2 = strnlen(set2, 255 * sizeof(wchar_t));
+	} else {
+		set2 = &set1[0];
+		lset2 = 0;
+	}
+
+	s = (char *) set1;
+	while(*s) {
+		if(*s == '\\') {
+			handleescapes(++s);
+		}
+
+		leftbytes = mbtowc(&runeleft, s, 4);
+		if(set2[n] != '\0')
+			rightbytes = mbtowc(&runeright, set2 + n, 4);
+		mappings[runeleft] = runeright;
+
+		s += leftbytes;
+		if(n < lset2)
+			n += rightbytes;
+	}
+}
+
+void
+maptonull(const wchar_t *mappings, char *in)
+{
+	const char *s;
+	wchar_t runeleft;
+	int leftbytes = 0;
+
+	s = in;
+	while(*s) {
+		leftbytes = mbtowc(&runeleft, s, 4);
+		if(!mappings[runeleft])
+			putwchar(runeleft);
+		s += leftbytes;
+	}
+}
+
+void
+maptoset(const wchar_t *mappings, char *in)
+{
+	const char *s;
+	wchar_t runeleft;
+	int leftbytes = 0;
+
+	s = in;
+	while(*s) {
+		leftbytes = mbtowc(&runeleft, s, 4);
+		if(!mappings[runeleft]) {
+			putwchar(runeleft);
+		} else {
+			putwchar(mappings[runeleft]);
+		}
+		s += leftbytes;
+	}
+}
+
+int
+main(int argc, char *argv[])
+{
+	wchar_t *mappings;
+	char *buf = NULL;
+	size_t size = 0;
+
+	setlocale(LC_ALL, "");
+
+	mappings = (wchar_t *) mmap(NULL, 0x110000 * sizeof(wchar_t),
+					PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
+
+	ARGBEGIN {
+	default:
+		usage();
+	} ARGEND;
+
+	if(argc == 0)
+		usage();
+
+	if(argc >= 2) {
+		parsemapping(argv[0], argv[1], mappings);
+		while(afgets(&buf, &size, stdin))
+			maptoset(mappings, buf);
+	} else {
+		parsemapping(argv[0], NULL, mappings);
+		while(afgets(&buf, &size, stdin))
+			maptonull(mappings, buf);
+	}
+
+	free(buf);
+
+	if(ferror(stdin))
+		eprintf("<stdin>: read error:");
+
+	return EXIT_SUCCESS;
+}
-- 
1.8.5.2
Received on Wed Jan 15 2014 - 20:43:54 CET

This archive was generated by hypermail 2.3.0 : Wed Jan 15 2014 - 20:48:07 CET