-
Notifications
You must be signed in to change notification settings - Fork 74k
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
Speed up safe_strtod and safe_strtof functions by using double-conversion library #12102
Changes from all commits
29dd2d5
6070d82
c716f4b
4045f77
13efa30
9e38bda
168a1fc
b71463b
a3667c6
4e829b2
bb1bd80
6fdc1f3
01080ce
3031b05
8975a93
2865637
9174026
32dbc5c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Copyright 2017 The TensorFlow Authors. All Rights Reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# ============================================================================== | ||
include (ExternalProject) | ||
|
||
set(double_conversion_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/double_conversion/src/double_conversion) | ||
set(double_conversion_URL https://github.com/google/double-conversion.git) | ||
set(double_conversion_TAG 5664746) | ||
set(double_conversion_BUILD ${double_conversion_INCLUDE_DIR}) | ||
set(double_conversion_LIBRARIES ${double_conversion_BUILD}/double-conversion/libdouble-conversion.so) | ||
set(double_conversion_INCLUDES ${double_conversion_BUILD}) | ||
|
||
if(WIN32) | ||
set(double_conversion_STATIC_LIBRARIES ${double_conversion_BUILD}/double-conversion/$(Configuration)/double-conversion.lib) | ||
else() | ||
set(double_conversion_STATIC_LIBRARIES ${double_conversion_BUILD}/double-conversion/libdouble-conversion.a) | ||
endif() | ||
|
||
set(double_conversion_HEADERS | ||
"${double_conversion_INCLUDE_DIR}/double-conversion/bignum-dtoa.h" | ||
"${double_conversion_INCLUDE_DIR}/double-conversion/cached-powers.h" | ||
"${double_conversion_INCLUDE_DIR}/double-conversion/double-conversion.h" | ||
"${double_conversion_INCLUDE_DIR}/double-conversion/fixed-dtoa.h" | ||
"${double_conversion_INCLUDE_DIR}/double-conversion/strtod.h" | ||
"${double_conversion_INCLUDE_DIR}/double-conversion/bignum.h" | ||
"${double_conversion_INCLUDE_DIR}/double-conversion/diy-fp.h" | ||
"${double_conversion_INCLUDE_DIR}/double-conversion/fast-dtoa.h" | ||
"${double_conversion_INCLUDE_DIR}/double-conversion/ieee.h" | ||
"${double_conversion_INCLUDE_DIR}/double-conversion/utils.h" | ||
) | ||
|
||
ExternalProject_Add(double_conversion | ||
PREFIX double_conversion | ||
GIT_REPOSITORY ${double_conversion_URL} | ||
GIT_TAG ${double_conversion_TAG} | ||
DOWNLOAD_DIR "${DOWNLOAD_LOCATION}" | ||
BUILD_IN_SOURCE 1 | ||
INSTALL_COMMAND "" | ||
CMAKE_CACHE_ARGS | ||
-DCMAKE_BUILD_TYPE:STRING=Release | ||
-DCMAKE_VERBOSE_MAKEFILE:BOOL=OFF | ||
-DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,9 @@ limitations under the License. | |
#include <locale> | ||
#include <unordered_map> | ||
|
||
#include "double-conversion/double-conversion.h" | ||
|
||
#include "tensorflow/core/lib/strings/str_util.h" | ||
#include "tensorflow/core/lib/strings/stringprintf.h" | ||
#include "tensorflow/core/platform/logging.h" | ||
#include "tensorflow/core/platform/macros.h" | ||
|
@@ -32,72 +35,15 @@ namespace tensorflow { | |
|
||
namespace { | ||
|
||
template <typename T> | ||
T locale_independent_strtonum(const char* str, const char** endptr) { | ||
static const std::unordered_map<string, T> special_nums = { | ||
{"inf", std::numeric_limits<T>::infinity()}, | ||
{"+inf", std::numeric_limits<T>::infinity()}, | ||
{"-inf", -std::numeric_limits<T>::infinity()}, | ||
{"infinity", std::numeric_limits<T>::infinity()}, | ||
{"+infinity", std::numeric_limits<T>::infinity()}, | ||
{"-infinity", -std::numeric_limits<T>::infinity()}, | ||
{"nan", std::numeric_limits<T>::quiet_NaN()}, | ||
{"+nan", std::numeric_limits<T>::quiet_NaN()}, | ||
{"-nan", -std::numeric_limits<T>::quiet_NaN()}, | ||
}; | ||
std::stringstream s(str); | ||
|
||
// Check if str is one of the special numbers. | ||
string special_num_str; | ||
s >> special_num_str; | ||
|
||
for (int i = 0; i < special_num_str.length(); ++i) { | ||
special_num_str[i] = | ||
std::tolower(special_num_str[i], std::locale::classic()); | ||
} | ||
|
||
auto entry = special_nums.find(special_num_str); | ||
if (entry != special_nums.end()) { | ||
*endptr = str + (s.eof() ? static_cast<std::iostream::pos_type>(strlen(str)) | ||
: s.tellg()); | ||
return entry->second; | ||
} else { | ||
// Perhaps it's a hex number | ||
if (special_num_str.compare(0, 2, "0x") == 0 || | ||
special_num_str.compare(0, 3, "-0x") == 0) { | ||
return strtol(str, const_cast<char**>(endptr), 16); | ||
} | ||
} | ||
// Reset the stream | ||
s.str(str); | ||
s.clear(); | ||
// Use the "C" locale | ||
s.imbue(std::locale::classic()); | ||
|
||
T result; | ||
s >> result; | ||
|
||
// Set to result to what strto{f,d} functions would have returned. If the | ||
// number was outside the range, the stringstream sets the fail flag, but | ||
// returns the +/-max() value, whereas strto{f,d} functions return +/-INF. | ||
if (s.fail()) { | ||
if (result == std::numeric_limits<T>::max()) { | ||
result = std::numeric_limits<T>::infinity(); | ||
s.clear(s.rdstate() & ~std::ios::failbit); | ||
} else if (result == -std::numeric_limits<T>::max()) { | ||
result = -std::numeric_limits<T>::infinity(); | ||
s.clear(s.rdstate() & ~std::ios::failbit); | ||
} | ||
} | ||
|
||
if (endptr) { | ||
*endptr = | ||
str + | ||
(s.fail() ? static_cast<std::iostream::pos_type>(0) | ||
: (s.eof() ? static_cast<std::iostream::pos_type>(strlen(str)) | ||
: s.tellg())); | ||
} | ||
return result; | ||
static inline const double_conversion::StringToDoubleConverter& StringToFloatConverter() { | ||
const static double_conversion::StringToDoubleConverter converter( | ||
double_conversion::StringToDoubleConverter::ALLOW_LEADING_SPACES | ||
| double_conversion::StringToDoubleConverter::ALLOW_HEX | ||
| double_conversion::StringToDoubleConverter::ALLOW_TRAILING_SPACES | ||
| double_conversion::StringToDoubleConverter::ALLOW_CASE_INSENSIBILITY, | ||
0., 0., "inf", "nan" | ||
); | ||
return converter; | ||
} | ||
|
||
} // namespace | ||
|
@@ -165,8 +111,8 @@ char* DoubleToBuffer(double value, char* buffer) { | |
// larger than the precision we asked for. | ||
DCHECK(snprintf_result > 0 && snprintf_result < kFastToBufferSize); | ||
|
||
full_precision_needed = | ||
locale_independent_strtonum<double>(buffer, nullptr) != value; | ||
auto parsed_value = double{}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why this change? (also, we could possibly use DoubleToStringConverter to implement all of DoubleToBuffer?) (but see the question about android code size as well). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The previous implementation of DoubleToBuffer function was using old locale_independent_strtonum which is replaced by double_conversion::StringToDoubleConverter with slightly differen interface. But it is a good point to use only one function to convert str to double everywhere here. Consider double_conversion::StringToDoubleConverter only as hidden implementation details of safe_strto{d,f}. This is a good idea to use double-conversion for {Double,Float}ToBuffer functions. I can do it here or in another pull request. |
||
full_precision_needed = !safe_strtod(buffer, &parsed_value) || parsed_value != value; | ||
} | ||
|
||
if (full_precision_needed) { | ||
|
@@ -302,25 +248,23 @@ bool safe_strtou32(StringPiece str, uint32* value) { | |
} | ||
|
||
bool safe_strtof(const char* str, float* value) { | ||
const char* endptr; | ||
*value = locale_independent_strtonum<float>(str, &endptr); | ||
while (isspace(*endptr)) ++endptr; | ||
// Ignore range errors from strtod/strtof. | ||
// The values it returns on underflow and | ||
// overflow are the right fallback in a | ||
// robust setting. | ||
return *str != '\0' && *endptr == '\0'; | ||
int processed_characters_count = -1; | ||
auto len = str_util::Strnlen(str, kFastToBufferSize); | ||
*value = StringToFloatConverter().StringToFloat( | ||
str, | ||
len, | ||
&processed_characters_count); | ||
return processed_characters_count > 0; | ||
} | ||
|
||
bool safe_strtod(const char* str, double* value) { | ||
const char* endptr; | ||
*value = locale_independent_strtonum<double>(str, &endptr); | ||
while (isspace(*endptr)) ++endptr; | ||
// Ignore range errors from strtod/strtof. | ||
// The values it returns on underflow and | ||
// overflow are the right fallback in a | ||
// robust setting. | ||
return *str != '\0' && *endptr == '\0'; | ||
int processed_characters_count = -1; | ||
auto len = str_util::Strnlen(str, kFastToBufferSize); | ||
*value = StringToFloatConverter().StringToDouble( | ||
str, | ||
len, | ||
&processed_characters_count); | ||
return processed_characters_count > 0; | ||
} | ||
|
||
char* FloatToBuffer(float value, char* buffer) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a little unfortunate that mobile builds will end up using it, when I assume they don't need the extra speed of parsing (since they will only be parsing a few attribute values, not large files of text). Do you know how large the double-conversion library is when compiled for android?
(one option if the net change in binary size would be too large is to have the double conversion library only used by the op that needs it for speed).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not so large actually. The size of the compiled static library is 486KiB on my laptop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, the actual used size in a statically linked program may be smaller, but this seems pretty big for android - in some cases, we can get the whole android binary down to <2MB. I don't know which is a better solution - using ifdefs to avoid using this library on android, or use the library only in the performance critical kernels?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stripped binary build with optimisation level 3 has size 77KiB. By the way, I can figure out what can we throw out from the library in the android build.