-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reduce write()s for builtins #9229
Conversation
This at least halves the number of "write()" calls we do if it goes to a pipe or the terminal, or reduces them by 75% if there is a description. This makes ```fish complete -c foo -xa "(seq 50000)" complete -C"foo " ``` faster by 1.33x.
This writes the output once per argument instead of once per format or escaped char. An egregious case: ```fish printf (string repeat -n 200 \\x7f)%s\n (string repeat -n 2000 aaa\n) ``` Has been sped up by ~20x by reducing write() calls from 40000 to 200. Even a simple ```fish printf %s\n (string repeat -n 2000 aaa\n) ``` should now be ~1.2x faster by issuing 2000 instead of 4000 write calls (the `\n` was written separately!).
This basically immediately issues a "write()" if it's to a pipe or the terminal. That means we can reduce syscalls and improve performance, even by doing something like ```c++ streams.out.append(somewcstring + L"\n"); ``` instead of ```c++ streams.out.append(somewcstring); streams.out.push_back(L'\n'); ``` Some benchmarks of the ```fish for i in (string repeat -n 2000 \n) $thing end ``` variety: 1. `set` (printing variables) sped up 1.75x 2. `builtin -n` 1.60x 3. `jobs` 1.25x (with 3 jobs) 4. `functions` 1.20x 5. `math 1 + 1` 1.1x 6. `pwd` 1.1x Piping yields similar results, there is no real difference when outputting to a command substitution.
The impact here depends on the command and how much output it produces. It's possible to get up to 1.5x - `string upper` being a good example, or a no-op `string match '*'`. But the more the command actually needs to do, the less of an effect this has.
39529e0
to
f98e2f9
Compare
These changes look good and the wins are appreciable. I had a concern about excessive buffering but we already eschew builtins for cases where fish's internal pipeline buffering causes burdensome delays (such as using |
For the most part, this is about "buffering" a line. That's hardly "excessive". This is about instead of this: write("completion");
write("\t");
write("description");
write("\n"); doing this: string line = "completion" + "\t" + "description" + "\n";
write(line); Additionally, this is about what happens when the builtin writes into a pipe - that means either to an external process or the terminal. These will be changed: string match "*" | command grep
printf %s\n foo bar baz
string repeat -n 5000 aaa
# Only the `string length` writes to an fd directly
string repeat -n 5000 aaa | string length
math 5 + 5 >/tmp/ten These cases won't be: string repeat -n 5000 aaa | while read -l foo; true; end
set -l fivethousandAs (string repeat -n 5000 A) |
This looks fine to me, but it does impose some awkwardness on the builtins. A more pleasant-to-use but complex alternative would be to support buffering directly in
and in buffer's destructor, it flushes its data to its origin stream, in one go. Either that approach or what you've done here is fine with me, just wanted to toss that out as an alternative. |
I have thought about that, but it's not really any less effort if you want that line-buffering. Basically you would replace the I don't really want to buffer by length, because I'm imagining the latency characteristics might be weird? What if we have 490 bytes ready, we flush every 500 bytes and the next 10 bytes take a second because this is a super tricky regex match? What would be possible would be a "append_line" that appends the |
Regarding a possible |
prefer streams.out.append(foo) streams.out.push_back(L'\n') vs e.g. foo.append(L"\n"); streams.out.append(foo)
This is an alternative to the very common pattern of ```rust streams.err.append(output); streams.err.append1('\n'); ``` Which has negative performance implications, see fish-shell#9229 It takes `Into<WString>` to hopefully avoid allocating anew when the argument is a WString with leftover capacity
This is an alternative to the very common pattern of ```rust streams.err.append(output); streams.err.append1('\n'); ``` Which has negative performance implications, see #9229 It takes `Into<WString>` to hopefully avoid allocating anew when the argument is a WString with leftover capacity
Description
Our io_streams_t is, when it comes to pipes (including the terminal), an extremely thin wrapper over
write(2)
.Whenever you do
streams.out.append()
orstreams.out.push_back()
that corresponds to onewrite(2)
. This means doing something likeis extremely slow. The pattern we see a lot is
streams.out.append(somestring); streams.out.push_back(L'\n');
So, this PR tries to reduce how often we do that, sometimes by constructing strings on-the-fly like
streams.out.append(somestring + L"\n");
Or doing some buffering.
I'd like to call out one egregious case, which is
printf
. This one wouldout.push_back
for every character escape, sowould issue 400000 (four hundred thousand)
write(2)
calls. Now it has been reduced to 2000 (one per argument excluding the format string - I experimented with doing one big write but that turned out to be slower in some cases and would of course have worse latency and memory requirements). This used to take ~400ms, now it takes 16ms.The rest of the wins are more modest, ranging from ~1.1x to 1.7x.
TODOs: