diff --git a/doc/content-for-why.html b/doc/content-for-why.html new file mode 100644 index 000000000..cc879f5d3 --- /dev/null +++ b/doc/content-for-why.html @@ -0,0 +1,169 @@ +POKI_PUT_TOC_HERE + +

Someone asked me the other day about design, tradeoffs, thought process, +why I felt it necessary to build Miller, etc. Here are some answers. + +

Who is Miller for?

+ +

For background, I’m a software engineer, with a heavy devops bent +and a non-trivial amount of data-engineering in my career. +Initially I wrote Miller mainly for myself: I’m +coder-friendly (being a coder); I’m Github-friendly; most of my data are +well-structured or easily structurable (TSV-formatted SQL-query output, CSV +files, log files, JSON data structures); I care about interoperability between +all the various formats Miller supports (I’ve encountered them all); I do +all my work on Linux or OSX. + +

But now there’s this neat little tool which seems to be +useful for people in various disciplines. I don’t even know +entirely who. I can click through Github starrers and read a bit about +what they seem to do, but not everyone’s on Github (or stars +things). I’ve gotten a lot of feature requests through Github — but +only from people who are Github users. For sure, not everyone’s on Linux +or OSX (I have a Windows port underway). Not everyone’s a coder (it seems +like a lot of Miller’s Github starrers are devops folks like myself, or +data-science-ish people, or biology/genomics folks.) A lot of people care 100% +about CSV. And so on. + +

So I wonder (please drop a note at https://github.com/johnkerl/miller/issues) +does Miller do what you need? Do you use it for all sorts of things, or just +one or two nice things? Are there things you wish it did but it doesn’t? +Is it almost there, or just nowhere near what you want? Are there not enough +features or way too many? Are the docs too complicated; do you have a hard time +finding out how to do what you want? Should I think differently about what this +tool even is in the first place? Should I think differently about who +it’s for? + +

What was Miller created to do?

+ +

First: there are tools like xsv which handles CSV marvelously and +jq which handles JSON marvelously, and so on — but I over the +years of my career in the software industry I’ve found myself, and +others, doing a lot of ad-hoc things which really were fundamentally the same +except for format. So the number one thing about Miller is doing common +things while supporting multiple formats: (a) ingest a +list of records where a record is a list of key-value pairs (however +represented in the input files); (b) transform that stream of records; (c) emit +the transformed stream — either in the same format as input, or in a +different format. + +

Second thing, a lot like the first: just as I didn’t want to build +something only for a single file format, I didn’t want to build something +only for one problem domain. In my work doing software engineering, devops, +data engineering, etc. I saw a lot of commonalities and I wanted to +solve as many problems simultaneously as possible. + +

Third: it had to be streaming. As time goes by +and we (some of us, sometimes) have machines with tens or hundreds of GB of +RAM, it’s maybe less important, but I’m unhappy with tools which +ingest all data, then do stuff, then emit all data. One reason is to be able to +handle files bigger than available RAM. Another reason is to be able to handle +input which trickles in, e.g. you have some process emitting data now and then +and you can pipe it to Miller and it will emit transformed records one at a +time. + +

Fourth: it had to be fast. This precludes all +sorts of very nice things written in Ruby, for example. I love Ruby as a very +expressive language, and I have several very useful little utility scripts +written in Ruby. But a few years ago I ported over some of my old +tried-and-true C programs and the lines-of-code count was a lot lower +— it was great! Until I ran them on multi-GB files and realized they took +60x as long to complete. So I couldn’t write Miller in Ruby, or in +languages like it. I was going to have to do something in a low-level language +in order to make it performant. I did simple experiments in several languages +— Ruby, Python, Lua, Rust, Go, D. In one I just read lines and printed +them back out — a line-oriented cat. In another I consumed input +lines like x=1,y=2,z=3 one at a time, split them on commas and equals +signs to populate hash maps, transformed them (e.g. remove the y +field, and emitted them. Basically mlr cut -x -f y with DKVP format. +I didn’t do anything fancy — just using each language’s +getline, string-split, hashmap-put, etc. And nothing was as fast as +C, so I used C. (See also here.) + +

Fifth thing: I wanted Miller to be pipe-friendly and +interoperate with other command-line tools. Since the basic +paradigm is ingest records, transform records, emit records — where the +input and output formats can be the same or different, and the transform can be +complex, or just pass-through — this means you can use it to transform +data, or re-format it, or both. So if you just want to do +data-cleaning/prep/formatting and do all the "real" work in R, you can. If you +just want a little glue script between other tools you can get that. And if you +want to do non-trivial data-reduction in Miller you can. + +

Sixth thing: Must have comprehensive documentation and +unit-test. Since Miller handles a lot of formats and solves a lot +of problems, there’s a lot to test and a lot to keep working correctly as +I add features or optimize. And I wanted it to be able to explain itself +— not only through web docs like the one you’re reading but also +through man mlr and mlr --help, mlr sort --help, +etc. + +

Seventh thing: Must have a domain-specific +language (DSL) but also must let you do common things +without it. All those little verbs Miller has to help you +avoid having to write for-loops are great. I use them for +keystroke-saving: mlr stats1 -a mean,stddev,min,max -f quantity, for +example, without you having to write for-loops or define accumulator variables. +But you also have to be able to break out of that and write arbitrary code when +you want to: mlr put '$distance = $rate * $time' or anything else you +can think up. In Perl/AWK/etc. it’s all DSL. In xsv et al. it’s +all verbs. In Miller I like having the combination. + +

Eighth thing: It’s an awful lot of fun to +write. In my experience I didn’t find any tools which do +multi-format, streaming, efficient, multi-purpose, with DSL and non-DSL, so I +wrote one. But I don’t guarantee it’s unique in the world. It fills +a niche in the world (people use it) but it also fills a niche in my life. + +

Tradeoffs

+ +

Miller is command-line-only by design. People who want a graphical user +interface won’t find it here. This is in part (a) accommodating my +personal preferences, and in part (b) guided by my experience/belief that the +command line is very expressive. Steep learning curve, yes. I consider that +price worth paying. + +

Another tradeoff: supporting lists of records — each with only one +depth — keeps me supporting only what can be expressed in all of +those formats. E.g. in JSON you can have lists of lists of lists which Miller +just doesn’t handle. So Miller can’t (and won’t) handle +arbitrary JSON because it only handles tabular data which can be expressed in a +variety of formats. + +

A third tradeoff is doing build-from-scratch in a low-level language. +It’d be quicker to write (but slower to run) if written in a high-level +language. If Miller were written in Python, it would be implemented in +significantly fewer lines of code than its current C implementation. The DSL +would just be an eval of Python code. And it would run slower, but +maybe not enough slower to be a problem for most folks. + +

A fourth tradeoff is in the DSL (more visibly so in 5.0.0 but already in +pre-5.0.0): how much to make it dynamically typed — so you can just say +y=x+1 with a minimum number of keystrokes — vs. having it do a good job +of telling you when you’ve made a typo. This is a common paradigm across +all languages. Some like Ruby you don’t declare anything and +they’re quick to code little stuff in but programs of even a few thousand +lines (which isn’t large in the software world) become insanely +unmanageable. Then Java at the other extreme which is very typesafe but you +have to type in a lot of punctuation, angle brackets, datatypes, repetition, +etc. just to be able to get anything done. And some in the middle like Go which +are typesafe but with type inference which aim to do the best of both. In the +Miller (5.0.0) DSL you get y=x+1 by default but you can have things like int y += x+1 etc. so the typesafety is opt-in. See also here for more information on +type-checking. + +

Moving forward

+ +

I originally aimed Miller at people who already know what +sed/awk/cut/sort/join are and +wanted some options. But as time goes by I realize that tools like this can be +useful to folks who don’t know what those things are; people who +aren’t primarily coders; people who are scientists, or data scientists. +These days some journalists do data analysis. So moving forward in terms of +docs, I am working on having more cookbook, follow-by-example stuff in addition +to the existing language-reference kinds of stuff. And prioritizing a Windows +port — which is way overdue. And continuing to seek out input from people +who use Miller on where to go next. diff --git a/doc/content-for-whyc.html b/doc/content-for-whyc.html index 1b934c2b5..38793df53 100644 --- a/doc/content-for-whyc.html +++ b/doc/content-for-whyc.html @@ -29,7 +29,28 @@ often. The D language (http://dolang.org) is an exciting and elegant successor to C++ (more about which below) — D has many of Go’s strengths, with a tighter stylistic similarity to C. And initial experiments with Rust are intriguing. Yet with none of them could I obtain the -throughput I get in C: see the POKI_PUT_LINK_FOR_PAGE(performance.html)HERE page. +throughput I get in C. + +

Specifically, I did simple experiments in several languages — Ruby, +Python, Lua, Rust, Go, D. In one I just read lines and printed them back out +— a line-oriented cat. In another I consumed input lines like +x=1,y=2,z=3 one at a time, split them on commas and equals signs to +populate hash maps, transformed them (e.g. remove the y field), and +emitted them. Basically mlr cut -x -f y with DKVP format. I +didn’t do anything fancy — just using each language’s +getline, string-split, hashmap-put, etc. And nothing was as fast as +C, so I used C. Here are the experiments I kept (I failed to keep the +Lua code, for example): +C cat, +another C cat, +D cat, +Go cat, +another Go cat, +Rust cat, +Nim cat, +D cut, +Go cut, +Nim cut.

One of Go’s most powerful features is the ease with which it allows quick-to-code, error-free concurrency. Yet Miller, like most high-volume diff --git a/doc/cookbook.html b/doc/cookbook.html index 80cad36c9..a4a48ccc8 100644 --- a/doc/cookbook.html +++ b/doc/cookbook.html @@ -608,33 +608,33 @@ $ mlr --ofmt '%.9lf' --opprint seqgen --start 1 --stop 28 then put ' ' then put '$seconds=systime()' then step -a delta -f seconds then cut -x -f seconds i o fcount seconds_delta 1 1 1 0 -2 2 3 0.000033140 -3 3 5 0.000011921 +2 2 3 0.000031948 +3 3 5 0.000013113 4 5 9 0.000015974 5 8 15 0.000020027 -6 13 25 0.000029087 -7 21 41 0.000042915 -8 34 67 0.000066996 -9 55 109 0.000104904 -10 89 177 0.000169039 -11 144 287 0.000321150 -12 233 465 0.000429869 -13 377 753 0.000709057 -14 610 1219 0.001135111 -15 987 1973 0.001828909 -16 1597 3193 0.002882004 -17 2584 5167 0.004646063 -18 4181 8361 0.010217905 -19 6765 13529 0.014159918 -20 10946 21891 0.019574165 -21 17711 35421 0.031366825 -22 28657 57313 0.050403118 -23 46368 92735 0.089492083 -24 75025 150049 0.139133930 -25 121393 242785 0.208625078 -26 196418 392835 0.348189831 -27 317811 635621 0.540225029 -28 514229 1028457 0.867680073 +6 13 25 0.000029802 +7 21 41 0.000044107 +8 34 67 0.000068903 +9 55 109 0.000108004 +10 89 177 0.000172138 +11 144 287 0.000277996 +12 233 465 0.000442028 +13 377 753 0.000705004 +14 610 1219 0.001137972 +15 987 1973 0.001832008 +16 1597 3193 0.003048897 +17 2584 5167 0.004843950 +18 4181 8361 0.009150982 +19 6765 13529 0.015188217 +20 10946 21891 0.022678852 +21 17711 35421 0.036461115 +22 28657 57313 0.052274942 +23 46368 92735 0.091028929 +24 75025 150049 0.138079166 +25 121393 242785 0.235754967 +26 196418 392835 0.368572950 +27 317811 635621 0.592602968 +28 514229 1028457 0.926891088

@@ -666,32 +666,32 @@ $ mlr --ofmt '%.9lf' --opprint seqgen --start 1 --stop 28 then put ' ' then put '$seconds=systime()' then step -a delta -f seconds then cut -x -f seconds i o fcount seconds_delta 1 1 1 0 -2 2 3 0.000043869 -3 3 3 0.000014067 -4 5 3 0.000011921 +2 2 3 0.000035048 +3 3 3 0.000012875 +4 5 3 0.000010967 5 8 3 0.000011206 6 13 3 0.000010967 7 21 3 0.000010014 -8 34 3 0.000010967 +8 34 3 0.000010014 9 55 3 0.000011921 -10 89 3 0.000011921 -11 144 3 0.000011206 -12 233 3 0.000014782 -13 377 3 0.000012159 +10 89 3 0.000010014 +11 144 3 0.000010014 +12 233 3 0.000014067 +13 377 3 0.000010967 14 610 3 0.000010014 -15 987 3 0.000010967 -16 1597 3 0.000010967 -17 2584 3 0.000010014 -18 4181 3 0.000010967 -19 6765 3 0.000010967 +15 987 3 0.000010014 +16 1597 3 0.000010014 +17 2584 3 0.000008821 +18 4181 3 0.000010014 +19 6765 3 0.000010014 20 10946 3 0.000010014 -21 17711 3 0.000010967 +21 17711 3 0.000010014 22 28657 3 0.000010014 -23 46368 3 0.000013113 +23 46368 3 0.000011921 24 75025 3 0.000010967 -25 121393 3 0.000010967 +25 121393 3 0.000010014 26 196418 3 0.000010014 -27 317811 3 0.000010967 +27 317811 3 0.000010014 28 514229 3 0.000010014 diff --git a/doc/why.html b/doc/why.html new file mode 100644 index 000000000..f4ba64ee0 --- /dev/null +++ b/doc/why.html @@ -0,0 +1,326 @@ + + + + + + + + + + + + Why? + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+
Why?
+

+ + +

+

+ +

Someone asked me the other day about design, tradeoffs, thought process, +why I felt it necessary to build Miller, etc. Here are some answers. + +

Who is Miller for?

+ +

For background, I’m a software engineer, with a heavy devops bent +and a non-trivial amount of data-engineering in my career. +Initially I wrote Miller mainly for myself: I’m +coder-friendly (being a coder); I’m Github-friendly; most of my data are +well-structured or easily structurable (TSV-formatted SQL-query output, CSV +files, log files, JSON data structures); I care about interoperability between +all the various formats Miller supports (I’ve encountered them all); I do +all my work on Linux or OSX. + +

But now there’s this neat little tool which seems to be +useful for people in various disciplines. I don’t even know +entirely who. I can click through Github starrers and read a bit about +what they seem to do, but not everyone’s on Github (or stars +things). I’ve gotten a lot of feature requests through Github — but +only from people who are Github users. For sure, not everyone’s on Linux +or OSX (I have a Windows port underway). Not everyone’s a coder (it seems +like a lot of Miller’s Github starrers are devops folks like myself, or +data-science-ish people, or biology/genomics folks.) A lot of people care 100% +about CSV. And so on. + +

So I wonder (please drop a note at https://github.com/johnkerl/miller/issues) +does Miller do what you need? Do you use it for all sorts of things, or just +one or two nice things? Are there things you wish it did but it doesn’t? +Is it almost there, or just nowhere near what you want? Are there not enough +features or way too many? Are the docs too complicated; do you have a hard time +finding out how to do what you want? Should I think differently about what this +tool even is in the first place? Should I think differently about who +it’s for? + +

What was Miller created to do?

+ +

First: there are tools like xsv which handles CSV marvelously and +jq which handles JSON marvelously, and so on — but I over the +years of my career in the software industry I’ve found myself, and +others, doing a lot of ad-hoc things which really were fundamentally the same +except for format. So the number one thing about Miller is doing common +things while supporting multiple formats: (a) ingest a +list of records where a record is a list of key-value pairs (however +represented in the input files); (b) transform that stream of records; (c) emit +the transformed stream — either in the same format as input, or in a +different format. + +

Second thing, a lot like the first: just as I didn’t want to build +something only for a single file format, I didn’t want to build something +only for one problem domain. In my work doing software engineering, devops, +data engineering, etc. I saw a lot of commonalities and I wanted to +solve as many problems simultaneously as possible. + +

Third: it had to be streaming. As time goes by +and we (some of us, sometimes) have machines with tens or hundreds of GB of +RAM, it’s maybe less important, but I’m unhappy with tools which +ingest all data, then do stuff, then emit all data. One reason is to be able to +handle files bigger than available RAM. Another reason is to be able to handle +input which trickles in, e.g. you have some process emitting data now and then +and you can pipe it to Miller and it will emit transformed records one at a +time. + +

Fourth: it had to be fast. This precludes all +sorts of very nice things written in Ruby, for example. I love Ruby as a very +expressive language, and I have several very useful little utility scripts +written in Ruby. But a few years ago I ported over some of my old +tried-and-true C programs and the lines-of-code count was a lot lower +— it was great! Until I ran them on multi-GB files and realized they took +60x as long to complete. So I couldn’t write Miller in Ruby, or in +languages like it. I was going to have to do something in a low-level language +in order to make it performant. I did simple experiments in several languages +— Ruby, Python, Lua, Rust, Go, D. In one I just read lines and printed +them back out — a line-oriented cat. In another I consumed input +lines like x=1,y=2,z=3 one at a time, split them on commas and equals +signs to populate hash maps, transformed them (e.g. remove the y +field, and emitted them. Basically mlr cut -x -f y with DKVP format. +I didn’t do anything fancy — just using each language’s +getline, string-split, hashmap-put, etc. And nothing was as fast as +C, so I used C. (See also here.) + +

Fifth thing: I wanted Miller to be pipe-friendly and +interoperate with other command-line tools. Since the basic +paradigm is ingest records, transform records, emit records — where the +input and output formats can be the same or different, and the transform can be +complex, or just pass-through — this means you can use it to transform +data, or re-format it, or both. So if you just want to do +data-cleaning/prep/formatting and do all the "real" work in R, you can. If you +just want a little glue script between other tools you can get that. And if you +want to do non-trivial data-reduction in Miller you can. + +

Sixth thing: Must have comprehensive documentation and +unit-test. Since Miller handles a lot of formats and solves a lot +of problems, there’s a lot to test and a lot to keep working correctly as +I add features or optimize. And I wanted it to be able to explain itself +— not only through web docs like the one you’re reading but also +through man mlr and mlr --help, mlr sort --help, +etc. + +

Seventh thing: Must have a domain-specific +language (DSL) but also must let you do common things +without it. All those little verbs Miller has to help you +avoid having to write for-loops are great. I use them for +keystroke-saving: mlr stats1 -a mean,stddev,min,max -f quantity, for +example, without you having to write for-loops or define accumulator variables. +But you also have to be able to break out of that and write arbitrary code when +you want to: mlr put '$distance = $rate * $time' or anything else you +can think up. In Perl/AWK/etc. it’s all DSL. In xsv et al. it’s +all verbs. In Miller I like having the combination. + +

Eighth thing: It’s an awful lot of fun to +write. In my experience I didn’t find any tools which do +multi-format, streaming, efficient, multi-purpose, with DSL and non-DSL, so I +wrote one. But I don’t guarantee it’s unique in the world. It fills +a niche in the world (people use it) but it also fills a niche in my life. + +

Tradeoffs

+ +

Miller is command-line-only by design. People who want a graphical user +interface won’t find it here. This is in part (a) accommodating my +personal preferences, and in part (b) guided by my experience/belief that the +command line is very expressive. Steep learning curve, yes. I consider that +price worth paying. + +

Another tradeoff: supporting lists of records — each with only one +depth — keeps me supporting only what can be expressed in all of +those formats. E.g. in JSON you can have lists of lists of lists which Miller +just doesn’t handle. So Miller can’t (and won’t) handle +arbitrary JSON because it only handles tabular data which can be expressed in a +variety of formats. + +

A third tradeoff is doing build-from-scratch in a low-level language. +It’d be quicker to write (but slower to run) if written in a high-level +language. If Miller were written in Python, it would be implemented in +significantly fewer lines of code than its current C implementation. The DSL +would just be an eval of Python code. And it would run slower, but +maybe not enough slower to be a problem for most folks. + +

A fourth tradeoff is in the DSL (more visibly so in 5.0.0 but already in +pre-5.0.0): how much to make it dynamically typed — so you can just say +y=x+1 with a minimum number of keystrokes — vs. having it do a good job +of telling you when you’ve made a typo. This is a common paradigm across +all languages. Some like Ruby you don’t declare anything and +they’re quick to code little stuff in but programs of even a few thousand +lines (which isn’t large in the software world) become insanely +unmanageable. Then Java at the other extreme which is very typesafe but you +have to type in a lot of punctuation, angle brackets, datatypes, repetition, +etc. just to be able to get anything done. And some in the middle like Go which +are typesafe but with type inference which aim to do the best of both. In the +Miller (5.0.0) DSL you get y=x+1 by default but you can have things like int y += x+1 etc. so the typesafety is opt-in. See also here for more information on +type-checking. + +

Moving forward

+ +

I originally aimed Miller at people who already know what +sed/awk/cut/sort/join are and +wanted some options. But as time goes by I realize that tools like this can be +useful to folks who don’t know what those things are; people who +aren’t primarily coders; people who are scientists, or data scientists. +These days some journalists do data analysis. So moving forward in terms of +docs, I am working on having more cookbook, follow-by-example stuff in addition +to the existing language-reference kinds of stuff. And prioritizing a Windows +port — which is way overdue. And continuing to seek out input from people +who use Miller on where to go next. +

+
+ + diff --git a/doc/whyc.html b/doc/whyc.html index 040102e72..98a26ef06 100644 --- a/doc/whyc.html +++ b/doc/whyc.html @@ -183,7 +183,28 @@ often. The D language (http://dolang.org) is an exciting and elegant successor to C++ (more about which below) — D has many of Go’s strengths, with a tighter stylistic similarity to C. And initial experiments with Rust are intriguing. Yet with none of them could I obtain the -throughput I get in C: see the Performance page. +throughput I get in C. + +

Specifically, I did simple experiments in several languages — Ruby, +Python, Lua, Rust, Go, D. In one I just read lines and printed them back out +— a line-oriented cat. In another I consumed input lines like +x=1,y=2,z=3 one at a time, split them on commas and equals signs to +populate hash maps, transformed them (e.g. remove the y field), and +emitted them. Basically mlr cut -x -f y with DKVP format. I +didn’t do anything fancy — just using each language’s +getline, string-split, hashmap-put, etc. And nothing was as fast as +C, so I used C. Here are the experiments I kept (I failed to keep the +Lua code, for example): +C cat, +another C cat, +D cat, +Go cat, +another Go cat, +Rust cat, +Nim cat, +D cut, +Go cut, +Nim cut.

One of Go’s most powerful features is the ease with which it allows quick-to-code, error-free concurrency. Yet Miller, like most high-volume diff --git a/perf/catc.c.txt b/perf/catc.c.txt new file mode 100644 index 000000000..6401222a3 --- /dev/null +++ b/perf/catc.c.txt @@ -0,0 +1,45 @@ +#include +#include +#include + +// ---------------------------------------------------------------- +static int do_stream(char* file_name) { + FILE* input_stream = stdin; + FILE* output_stream = stdout; + + if (strcmp(file_name, "-")) { + input_stream = fopen(file_name, "r"); + if (input_stream == NULL) { + perror(file_name); + return 0; + } + } + + while (1) { + char* line = NULL; + size_t linecap = 0; + ssize_t linelen = getdelim(&line, &linecap, '\n', input_stream); + if (linelen <= 0) { + break; + } + fputs(line, output_stream); + free(line); + } + if (input_stream != stdin) + fclose(input_stream); + + return 1; +} + +// ================================================================ +int main(int argc, char** argv) { + int ok = 1; + if (argc == 1) { + ok = ok && do_stream("-"); + } else { + for (int argi = 1; argi < argc; argi++) { + ok = do_stream(argv[argi]); + } + } + return ok ? 0 : 1; +} diff --git a/perf/catc0.c.txt b/perf/catc0.c.txt new file mode 100644 index 000000000..af3faa8f6 --- /dev/null +++ b/perf/catc0.c.txt @@ -0,0 +1,44 @@ +#include +#include +#include + +#define MYBUFSIZ 8192 +static char iobuf[MYBUFSIZ]; + +// ---------------------------------------------------------------- +static int do_stream(char* file_name) { + FILE* input_stream = stdin; + FILE* output_stream = stdout; + + if (strcmp(file_name, "-")) { + input_stream = fopen(file_name, "r"); + if (input_stream == NULL) { + perror(file_name); + return 0; + } + } + + while (1) { + char* line = fgets(iobuf, BUFSIZ, input_stream); + if (line == NULL) + break; + fputs(line, output_stream); + } + if (input_stream != stdin) + fclose(input_stream); + + return 1; +} + +// ================================================================ +int main(int argc, char** argv) { + int ok = 1; + if (argc == 1) { + ok = ok && do_stream("-"); + } else { + for (int argi = 1; argi < argc; argi++) { + ok = do_stream(argv[argi]); + } + } + return ok ? 0 : 1; +} diff --git a/perf/catd.d.txt b/perf/catd.d.txt new file mode 100644 index 000000000..f80a01a9f --- /dev/null +++ b/perf/catd.d.txt @@ -0,0 +1,9 @@ +// Reads $(D stdin) and writes it to $(D stdout). +import std.stdio; + +void main() +{ + string line; + while ((line = stdin.readln()) !is null) + write(line); +} diff --git a/perf/catgo.go.txt b/perf/catgo.go.txt new file mode 100644 index 000000000..7b01385d1 --- /dev/null +++ b/perf/catgo.go.txt @@ -0,0 +1,66 @@ +package main + +import ( + "bufio" + "io" + "log" + "os" +) + +// ---------------------------------------------------------------- +func main() { + args := os.Args[1:] + includeFields := []string {"a", "x"}; + + ok := true + if len(args) == 0 { + ok = handle("-", includeFields) && ok + } else { + for _, arg := range args { + ok = handle(arg, includeFields) && ok + } + } + if ok { + os.Exit(0) + } else { + os.Exit(1) + } +} + +// ---------------------------------------------------------------- +func handle(fileName string, includeFields []string) (ok bool) { + inputStream := os.Stdin + if fileName != "-" { + var err error + if inputStream, err = os.Open(fileName); err != nil { + log.Println(err) + return false + } + } + + reader := bufio.NewReader(inputStream) + writer := bufio.NewWriter(os.Stdout) + eof := false + + for !eof { + line, err := reader.ReadString('\n') + if err == io.EOF { + err = nil + eof = true + } else if err != nil { + log.Println(err) + if fileName != "-" { + inputStream.Close() + } + return false + } else { + writer.WriteString(line) + } + } + if fileName != "-" { + inputStream.Close() + } + writer.Flush() + + return true +} diff --git a/perf/catgo2.go.txt b/perf/catgo2.go.txt new file mode 100644 index 000000000..5c4b3668d --- /dev/null +++ b/perf/catgo2.go.txt @@ -0,0 +1,55 @@ +package main + +import ( + "bufio" + "fmt" + "log" + "os" +) + +// ---------------------------------------------------------------- +func main() { + args := os.Args[1:] + includeFields := []string {"a", "x"}; + + ok := true + if len(args) == 0 { + ok = handle("-", includeFields) && ok + } else { + for _, arg := range args { + ok = handle(arg, includeFields) && ok + } + } + if ok { + os.Exit(0) + } else { + os.Exit(1) + } +} + +// ---------------------------------------------------------------- +func handle(fileName string, includeFields []string) (ok bool) { + inputStream := os.Stdin + if fileName != "-" { + var err error + if inputStream, err = os.Open(fileName); err != nil { + log.Println(err) + return false + } + } + + scanner := bufio.NewScanner(inputStream) + for scanner.Scan() { + line := scanner.Text() + fmt.Println(line) + } + if err := scanner.Err(); err != nil { + fmt.Fprintln(os.Stderr, "reading standard input:", err) + } + + if fileName != "-" { + inputStream.Close() + } + + return true +} diff --git a/perf/catrust.rs.txt b/perf/catrust.rs.txt new file mode 100644 index 000000000..6436db2bd --- /dev/null +++ b/perf/catrust.rs.txt @@ -0,0 +1,16 @@ +use std::io; + +fn main() { + for line in io::stdin().lock().lines() { + print!("{}", line.unwrap()); + } +} + +//fn main() { +// let mut reader = io::stdin(); +// let mut line; +// loop { +// line = reader.read_line(); +// print!("{}\n", line); +// } +//} diff --git a/perf/cutd.d.txt b/perf/cutd.d.txt new file mode 100644 index 000000000..5dc07035f --- /dev/null +++ b/perf/cutd.d.txt @@ -0,0 +1,40 @@ +// Reads $(D stdin) and writes it to $(D stdout). +// http://dlang.org/hash-map.html +import std.stdio; +import std.string; +import std.array; + +void main() { + string[] includeFields = ["a", "x"]; + string line; + while ((line = stdin.readln()) !is null) { + // Input string to hashmap. + string[string] oldmap; + string[] fields = split(line, ','); + foreach (field; fields) { + string[] kvps = split(field, '='); // really want splitN with max #parts = 2 + oldmap[kvps[0]] = kvps[1]; + } + + // Hashmap-to-hashmap transform. + // Note: unordered hashmap here. + string[string] newmap; + foreach (includeField; includeFields) { + if (includeField in oldmap) { + newmap[includeField] = oldmap[includeField]; + } + } + + // Hashmap to output strings. + int i = 0; + foreach (key; newmap.keys) { + if (i > 0) + write(','); + write(key); + write('='); + write(newmap[key]); + i++; + } + write('\n'); + } +} diff --git a/perf/cutgo.go b/perf/cutgo.go index 3bd2c0acd..56ec3be75 100644 --- a/perf/cutgo.go +++ b/perf/cutgo.go @@ -100,7 +100,6 @@ func handle(fileName string, includeFields []string) (ok bool) { // Write to output stream //fmt.Println("") writer.WriteString(out) - // xxx now == ?!? 0.720s // delta 0.390s // 56% diff --git a/perf/cutgo.go.txt b/perf/cutgo.go.txt new file mode 100644 index 000000000..56ec3be75 --- /dev/null +++ b/perf/cutgo.go.txt @@ -0,0 +1,114 @@ +package main + +import ( + "bufio" + "io" + "log" + "os" + "strings" +) + +// ---------------------------------------------------------------- +func main() { + args := os.Args[1:] + includeFields := []string {"a", "x"}; + + ok := true + if len(args) == 0 { + ok = handle("-", includeFields) && ok + } else { + for _, arg := range args { + ok = handle(arg, includeFields) && ok + } + } + if ok { + os.Exit(0) + } else { + os.Exit(1) + } +} + +// ---------------------------------------------------------------- +func handle(fileName string, includeFields []string) (ok bool) { + inputStream := os.Stdin + if fileName != "-" { + var err error + if inputStream, err = os.Open(fileName); err != nil { + log.Println(err) + return false + } + } + + reader := bufio.NewReader(inputStream) + writer := bufio.NewWriter(os.Stdout) + eof := false + + for !eof { + line, err := reader.ReadString('\n') + if err == io.EOF { + err = nil + eof = true + } else if err != nil { + log.Println(err) + if fileName != "-" { + inputStream.Close() + } + return false + } else { + + // 0.030s + + // Line to map + mymap := make(map[string]string) + fields := strings.Split(line, ","); + for _, field := range(fields) { + kvps := strings.SplitN(field, "=", 2) + mymap[kvps[0]] = kvps[1] + } + // 0.220s + // delta 0.190s + // 27% + + // Map-to-map transform + newmap := make(map[string]string) + for _, includeField := range(includeFields) { + value, present := mymap[includeField] + if present { + newmap[includeField] = value + } + } + // 0.280s + // delta 0.060s + // 9% + + // Map to string + outs := make([]string, len(newmap)) + i := 0 + for k, v := range(newmap) { + outs[i] = k + "=" + v + i++ + } + // 0.320s + // delta 0.040s + // 6% + + out := strings.Join(outs, ",") + // 0.330s + // delta 0.010s + // 2% + + // Write to output stream + //fmt.Println("") + writer.WriteString(out) + // delta 0.390s + // 56% + + } + } + if fileName != "-" { + inputStream.Close() + } + writer.Flush() + + return true +} diff --git a/perf/nimcat.nim.txt b/perf/nimcat.nim.txt new file mode 100644 index 000000000..d73ba08e6 --- /dev/null +++ b/perf/nimcat.nim.txt @@ -0,0 +1,2 @@ +for line in stdin.lines: + echo(line) diff --git a/perf/nimcut.nim.txt b/perf/nimcut.nim.txt new file mode 100644 index 000000000..20349cbf2 --- /dev/null +++ b/perf/nimcut.nim.txt @@ -0,0 +1,15 @@ +import strutils, tables + +for line in stdin.lines: + #var map: OrderedTable[string,string] + var map = {"":""}.newOrderedTable + #var map = initTable[string, string] + #var map: OrderedTable[string, string] + #var map: newOrderedTable[string, string](16) + for word in line.split(","): + var pair = word.split("=") + #echo(pair[0]) + #echo(pair[1]) + #echo() + #map[pair[0]] = pair[1] + map.add(pair[0], pair[1])