Skip to content

Commit

Permalink
core: Replace fold in @go.printf
Browse files Browse the repository at this point in the history
While starting to work on #59, and thinking about #79, I thought about
whether I could dispense with the `fold` command altogether and whether
it would have an impact on Windows performance in particular.

The upshot is, the difference isn't dramatic, but since there's
definitely a big performance hit and I'd prefer depending on as few
external commands and subshells in general, I'm going to go ahead and
commit the change. The nice feature is that the output will always be
folded to the terminal width, since it no longer depends on the
presence of `fold`.

After implementing the folding algorithm first on macOS 10.12.2, I ran the
following command (with `/bin` for Bash 3.2, and `/usr/local/bin` for Bash
4.4.5) to produce a timing comparison, since `./go help builtins` produces a
fair amount of text printed with `@go.printf`:

  $ time PATH="/bin:$PATH" bash ./go help builtins

The running times didn't appear impacted at all when `COLUMNS == 161`. When
`COLUMNS == 40`, the new version was very very marginally slower, O(0.004s):

  Bash 3.2.57(1)-release, new implementation without `fold`
  real    0m0.062s
  user    0m0.046s
  sys     0m0.014s

  Bash 3.2.57(1)-release, new implementation without `fold`
  real    0m0.066s
  user    0m0.051s
  sys     0m0.013s

  Bash 4.4.5(1)-release, old implementation with `fold`
  real    0m0.058s
  user    0m0.041s
  sys     0m0.013s

  Bash 4.4.5(1)-release, new implementation without `fold`
  real    0m0.062s
  user    0m0.045s
  sys     0m0.013s

On Windows 10, the results weren't super dramatic, but were generally an
improvement for most Bash environments:

  Bash 4.3.46(2)-release, Git Bash from Command Prompt,
  old implementation with `fold`, COLUMNS=161
  real    0m0.156s
  user    0m0.030s
  sys     0m0.106s

  Bash 4.3.46(2)-release, Git Bash from Command Prompt,
  new implementation with `fold`, COLUMNS=161
  real    0m0.156s
  user    0m0.030s
  sys     0m0.075s

  Bash 4.3.46(2)-release, Git Bash from Command Prompt,
  old implementation with `fold`, COLUMNS=40
  real    0m0.236s
  user    0m0.045s
  sys     0m0.090s

  Bash 4.3.46(2)-release, Git Bash from Command Prompt,
  new implementation with `fold`, COLUMNS=40
  real    0m0.231s
  user    0m0.046s
  sys     0m0.153s

  Bash 4.3.46(2)-release, Git Bash,
  old implementation with `fold`, COLUMNS=161
  real    0m0.148s
  user    0m0.030s
  sys     0m0.075s

  Bash 4.3.46(2)-release, Git Bash,
  new implementation with `fold`, COLUMNS=161
  real    0m0.164s
  user    0m0.046s
  sys     0m0.107s

  Bash 4.3.46(2)-release, Git Bash,
  old implementation with `fold`, COLUMNS=40
  real    0m0.202s
  user    0m0.030s
  sys     0m0.137s

  Bash 4.3.46(2)-release, Git Bash,
  new implementation with `fold`, COLUMNS=40
  real    0m0.194s
  user    0m0.045s
  sys     0m0.091s

  Bash 4.3.48(8)-release, Cygwin Bash,
  old implementation with `fold`, COLUMNS=161
  real    0m0.135s
  user    0m0.030s
  sys     0m0.030s

  Bash 4.3.48(8)-release, Cygwin Bash,
  new implementation without `fold`, COLUMNS=161
  real    0m0.123s
  user    0m0.060s
  sys     0m0.015s

  Bash 4.3.48(8)-release, Cygwin Bash,
  old implementation with `fold`, COLUMNS=40
  real    0m0.189s
  user    0m0.076s
  sys     0m0.091s

  Bash 4.3.48(8)-release, Cygwin Bash,
  new implementation without `fold`, COLUMNS=40
  real    0m0.181s
  user    0m0.076s
  sys     0m0.046s

  Bash 4.3.11(1)-release, Windows Subsystem for Linux,
  old implementation with `fold`, COLUMNS=161
  real    0m0.093s
  user    0m0.031s
  sys     0m0.063s

  Bash 4.3.11(1)-release, Windows Subsystem for Linux,
  new implementation without `fold`, COLUMNS=161
  real    0m0.078s
  user    0m0.016s
  sys     0m0.063s

  Bash 4.3.11(1)-release, Windows Subsystem for Linux,
  old implementation with `fold`, COLUMNS=40
  real    0m0.155s
  user    0m0.016s
  sys     0m0.125s

  Bash 4.3.11(1)-release, Windows Subsystem for Linux,
  new implementation without `fold`, COLUMNS=40
  real    0m0.141s
  user    0m0.016s
  sys     0m0.109s
  • Loading branch information
mbland committed Jan 1, 2017
1 parent b12fcb4 commit 8dbc5ed
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 32 deletions.
30 changes: 20 additions & 10 deletions go-core.bash
Original file line number Diff line number Diff line change
Expand Up @@ -124,26 +124,36 @@ declare _GO_PLUGINS_PATHS=()
# initialized, _GO_PLUGINS_PATHS and _GO_SCRIPTS_DIR will be added.
declare _GO_SEARCH_PATHS=("$_GO_CORE_DIR/libexec")

# Invokes printf builtin, then folds output to $COLUMNS width if 'fold' exists.
#
# Should be used as the last step to print to standard output or error, as that
# is more efficient than calling this multiple times due to the pipe to 'fold'.
# Invokes printf builtin, then folds output to $COLUMNS width
#
# Arguments:
# everything accepted by the printf builtin except the '-v varname' option
@go.printf() {
local format="$1"
shift
local result
local line
local prefix

if [[ "$#" -eq 0 ]]; then
format="${format//\%/%%}"
fi

if command -v fold >/dev/null; then
printf "$format" "$@" | fold -s -w "$COLUMNS"
else
printf "$format" "$@"
fi
# If `format` ends with a newline, chomp it, since the loop will add one.
printf -v result "${format%\\n}" "$@"

while IFS= read -r line; do
line="${line%$'\r'}"

while [[ "${#line}" -gt "$COLUMNS" ]]; do
prefix="${line:0:$COLUMNS}"
prefix="${prefix% *}"
printf '%s\n' "$prefix"
line="${line#$prefix}"
line="${line#* }"
done

printf '%s\n' "$line"
done <<<"$result"
}

# Prints the stack trace at the point of the call.
Expand Down
6 changes: 3 additions & 3 deletions tests/core.bats
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ load environment

@test "$SUITE: produce error on cd" {
local expected
expected+='cd is only available after using "./go env" to set up your '$'\n'
expected+=$'cd is only available after using "./go env" to set up your\n'
expected+='shell environment.'

COLUMNS=60
Expand All @@ -42,7 +42,7 @@ load environment

@test "$SUITE: produce error on pushd" {
local expected
expected+='pushd is only available after using "./go env" to set up '$'\n'
expected+=$'pushd is only available after using "./go env" to set up\n'
expected+='your shell environment.'

COLUMNS=60
Expand All @@ -52,7 +52,7 @@ load environment

@test "$SUITE: produce error on unenv" {
local expected
expected+='unenv is only available after using "./go env" to set up '$'\n'
expected+=$'unenv is only available after using "./go env" to set up\n'
expected+='your shell environment.'

COLUMNS=60
Expand Down
38 changes: 19 additions & 19 deletions tests/core/printf.bats
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,14 @@

load ../environment

TEST_TEXT='1234567890 1234567890 1234567890'

setup() {
create_test_go_script '@go.printf "%s" "$@"'
}

teardown() {
remove_test_go_rootdir
}

@test "$SUITE: wrap text according to COLUMNS if fold command is available" {
run env COLUMNS=11 "$TEST_GO_SCRIPT" "$TEST_TEXT"
assert_success
assert_equal '3' "${#lines[@]}" 'number of output lines'
assert_line_equals 0 '1234567890 '
assert_line_equals 1 '1234567890 '
assert_line_equals 2 '1234567890'
}

@test "$SUITE: don't wrap text if fold command isn't available" {
run env PATH= COLUMNS=11 "$BASH" "$TEST_GO_SCRIPT" "$TEST_TEXT"
assert_success "$TEST_TEXT"
assert_equal '1' "${#lines[@]}" 'number of output lines'
@test "$SUITE: wrap text according to COLUMNS" {
create_test_go_script '@go.printf "%s" "1234567890 1234567890 1234567890"'
COLUMNS=25 run "$TEST_GO_SCRIPT"
assert_success $'1234567890 1234567890\n1234567890'
}

@test "$SUITE: escape percent signs if only one argument" {
Expand All @@ -33,3 +18,18 @@ teardown() {
run "$TEST_GO_SCRIPT" "$test_text"
assert_success "$test_text"
}

@test "$SUITE: preserve blank lines" {
local test_string=$'1234567890\n\n1234567890\n\n1234567890'
create_test_go_script "@go.printf '%s' '$test_string'"
COLUMNS=15 run "$TEST_GO_SCRIPT"
assert_success "$test_string"
}

@test "$SUITE: don't add extra newline if format ends with one" {
local test_string='1234567890'
create_test_go_script "@go.printf '%s\n' '$test_string'" \
"@go.printf '%s\n' '$test_string'"
COLUMNS=15 run "$TEST_GO_SCRIPT"
assert_success "$test_string"$'\n'"$test_string"
}

0 comments on commit 8dbc5ed

Please sign in to comment.