From a700acadfa146a3937afd6e4e8ce861e2cc6d1f5 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sun, 4 Nov 2018 15:53:31 -0800 Subject: [PATCH] Implement fish_wcstod and adopt it in builtin_test wcstod_l is enormously slow on the Mac. This makes arithmetic comparisons using builtin_test about 250% as fast on macOS. --- src/builtin_test.cpp | 2 +- src/fish_tests.cpp | 19 +++++++++++++++++++ src/wutil.cpp | 21 +++++++++++++++++++++ src/wutil.h | 2 +- 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/builtin_test.cpp b/src/builtin_test.cpp index 747620cc4..07ae926b9 100644 --- a/src/builtin_test.cpp +++ b/src/builtin_test.cpp @@ -634,7 +634,7 @@ static bool parse_double(const wchar_t *arg, double *out_res) { while (arg && *arg != L'\0' && iswspace(*arg)) arg++; errno = 0; wchar_t *end = NULL; - *out_res = wcstod_l(arg, &end, fish_c_locale()); + *out_res = fish_wcstod(arg, &end); // Consume trailing spaces. while (end && *end != L'\0' && iswspace(*end)) end++; return errno == 0 && end > arg && *end == L'\0'; diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index d2e2fd73a..62fbd2f11 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -2285,6 +2285,24 @@ static void test_test() { do_test(run_test_test(0, L"4611686018427387904 -ge 4611686018427387904")); } +static void test_wcstod() { + say(L"Testing fish_wcstod"); + auto tod_test = [](const wchar_t *a, const char *b) { + char *narrow_end = nullptr; + wchar_t *wide_end = nullptr; + double val1 = wcstod(a, &wide_end); + double val2 = strtod(b, &narrow_end); + do_test((isnan(val1) && isnan(val2)) || fabs(val1 - val2) <= __DBL_EPSILON__); + do_test(wide_end - a == narrow_end - b); + }; + tod_test(L"", ""); + tod_test(L"1.2", "1.2"); + tod_test(L"1.5", "1.5"); + tod_test(L"-1000", "-1000"); + tod_test(L"0.12345", "0.12345"); + tod_test(L"nope", "nope"); +} + /// Testing colors. static void test_colors() { say(L"Testing colors"); @@ -4949,6 +4967,7 @@ int main(int argc, char **argv) { if (should_test_function("ifind_fuzzy")) test_ifind_fuzzy(); if (should_test_function("abbreviations")) test_abbreviations(); if (should_test_function("test")) test_test(); + if (should_test_function("wcstod")) test_wcstod(); if (should_test_function("path")) test_path(); if (should_test_function("pager_navigation")) test_pager_navigation(); if (should_test_function("pager_layout")) test_pager_layout(); diff --git a/src/wutil.cpp b/src/wutil.cpp index 2a9762752..39c069afa 100644 --- a/src/wutil.cpp +++ b/src/wutil.cpp @@ -700,6 +700,27 @@ unsigned long long fish_wcstoull(const wchar_t *str, const wchar_t **endptr, int return result; } +/// Like wcstod(), but wcstod() is enormously expensive on some platforms so this tries to have a +/// fast path. +double fish_wcstod(const wchar_t *str, wchar_t **endptr) { + // The "fast path." If we're all ASCII and we fit inline, use strtod(). + char narrow[128]; + size_t len_plus_0 = 1 + wcslen(str); + auto is_ascii = [](wchar_t c) { return c >= 0 && c <= 127; }; + if (len_plus_0 <= sizeof narrow && std::all_of(str, str + len_plus_0, is_ascii)) { + // Fast path. Copy the string into a local buffer and run strtod() on it. + std::copy(str, str + len_plus_0, narrow); + char *narrow_endptr = nullptr; + double ret = strtod(narrow, endptr ? &narrow_endptr : nullptr); + if (endptr) { + assert(narrow_endptr && "narrow_endptr should not be null"); + *endptr = const_cast(str + (narrow_endptr - narrow)); + } + return ret; + } + return wcstod_l(str, endptr, fish_c_locale()); +} + file_id_t file_id_t::from_stat(const struct stat &buf) { file_id_t result = {}; result.device = buf.st_dev; diff --git a/src/wutil.h b/src/wutil.h index 971247283..5bb76e235 100644 --- a/src/wutil.h +++ b/src/wutil.h @@ -131,7 +131,7 @@ int fish_wcstoi(const wchar_t *str, const wchar_t **endptr = NULL, int base = 10 long fish_wcstol(const wchar_t *str, const wchar_t **endptr = NULL, int base = 10); long long fish_wcstoll(const wchar_t *str, const wchar_t **endptr = NULL, int base = 10); unsigned long long fish_wcstoull(const wchar_t *str, const wchar_t **endptr = NULL, int base = 10); -double fish_wcstod(const wchar_t *str, const wchar_t **endptr); +double fish_wcstod(const wchar_t *str, wchar_t **endptr); /// Class for representing a file's inode. We use this to detect and avoid symlink loops, among /// other things. While an inode / dev pair is sufficient to distinguish co-existing files, Linux