Skip to content
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

Merged
merged 18 commits into from
Nov 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
29dd2d5
Add double-conversion library to third_party
akindyakov Aug 8, 2017
6070d82
Strnlen function to str_util.h
akindyakov Aug 8, 2017
c716f4b
Use double-conversion lib for safe_strto{d,f} implementation
akindyakov Aug 8, 2017
4045f77
Path for double-conversion library is no longer needed, just use new …
akindyakov Sep 28, 2017
13efa30
Use more obvious arguments names for Strnlen function
akindyakov Sep 28, 2017
9e38bda
Merge master from upstream
akindyakov Sep 28, 2017
168a1fc
Fixed DoubleToBuffer function in case of [full_precision_needed]
akindyakov Oct 1, 2017
b71463b
add double-conversion compilation in makefile builds
akindyakov Oct 4, 2017
a3667c6
Build double-conversion directly inside tensorflow/contrib/makefile/M…
akindyakov Oct 9, 2017
4e829b2
Put double_conversion to the rest of targets including number.* as src
akindyakov Oct 9, 2017
bb1bd80
Sort external dependencies and make Sanity Checks happy
akindyakov Oct 10, 2017
6fdc1f3
Add test cases with trailing and leading whitespace characters to //t…
akindyakov Oct 25, 2017
01080ce
Remove octal numbers support from safe_strtod and safe_strtof
akindyakov Oct 25, 2017
3031b05
Add double-conversion library to the cmake build
akindyakov Oct 26, 2017
8975a93
Merge branch 'master' into speed_up_csv_reader
martinwicke Nov 7, 2017
2865637
Prepend lib/ to cmake lib path as per mrry instructions
drpngx Nov 8, 2017
9174026
Merge branch 'master' into speed_up_csv_reader
martinwicke Nov 14, 2017
32dbc5c
Add to double-conversion win32 build output path: fix up windows CMa…
akindyakov Nov 27, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions tensorflow/contrib/cmake/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ include(protobuf)
include(re2)
include(cub)
include(sqlite)
include(double_conversion)
if (tensorflow_BUILD_CC_TESTS)
include(googletest)
endif()
Expand All @@ -178,6 +179,7 @@ set(tensorflow_EXTERNAL_LIBRARIES
${protobuf_STATIC_LIBRARIES}
${re2_STATIC_LIBRARIES}
${sqlite_STATIC_LIBRARIES}
${double_conversion_STATIC_LIBRARIES}
)
set(tensorflow_EXTERNAL_DEPENDENCIES
zlib_copy_headers_to_destination
Expand All @@ -196,6 +198,7 @@ set(tensorflow_EXTERNAL_DEPENDENCIES
fft2d
re2
sqlite_copy_headers_to_destination
double_conversion
)

include_directories(
Expand All @@ -218,6 +221,7 @@ include_directories(
${PROTOBUF_INCLUDE_DIRS}
${re2_INCLUDE_DIR}
${sqlite_INCLUDE_DIR}
${double_conversion_INCLUDE_DIR}
)

if(tensorflow_ENABLE_SSL_SUPPORT)
Expand Down
54 changes: 54 additions & 0 deletions tensorflow/contrib/cmake/external/double_conversion.cmake
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
)
9 changes: 8 additions & 1 deletion tensorflow/contrib/makefile/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ HOST_INCLUDES := \
-I$(MAKEFILE_DIR)/downloads/gemmlowp \
-I$(MAKEFILE_DIR)/downloads/nsync/public \
-I$(MAKEFILE_DIR)/downloads/fft2d \
-I$(MAKEFILE_DIR)/downloads/double_conversion \
-I$(HOST_GENDIR)
ifeq ($(HAS_GEN_HOST_PROTOC),true)
HOST_INCLUDES += -I$(MAKEFILE_DIR)/gen/protobuf-host/include
Expand Down Expand Up @@ -125,7 +126,9 @@ PROTO_TEXT := $(HOST_BINDIR)proto_text
# The list of dependencies is derived from the Bazel build file by running
# the gen_file_lists.sh script on a system with a working Bazel setup.
PROTO_TEXT_CC_FILES := $(shell cat $(MAKEFILE_DIR)/proto_text_cc_files.txt)
PROTO_TEXT_PB_CC_LIST := $(shell cat $(MAKEFILE_DIR)/proto_text_pb_cc_files.txt)
PROTO_TEXT_PB_CC_LIST := \
$(shell cat $(MAKEFILE_DIR)/proto_text_pb_cc_files.txt) \
$(wildcard tensorflow/contrib/makefile/downloads/double_conversion/double-conversion/*.cc)
PROTO_TEXT_PB_H_LIST := $(shell cat $(MAKEFILE_DIR)/proto_text_pb_h_files.txt)

# Locations of the intermediate files proto_text generates.
Expand Down Expand Up @@ -171,6 +174,7 @@ INCLUDES := \
-I$(MAKEFILE_DIR)/downloads/gemmlowp \
-I$(MAKEFILE_DIR)/downloads/nsync/public \
-I$(MAKEFILE_DIR)/downloads/fft2d \
-I$(MAKEFILE_DIR)/downloads/double_conversion \
-I$(PROTOGENDIR) \
-I$(PBTGENDIR)
ifeq ($(HAS_GEN_HOST_PROTOC),true)
Expand Down Expand Up @@ -326,6 +330,8 @@ $(MARCH_OPTION) \
-I$(MAKEFILE_DIR)/downloads/gemmlowp \
-I$(MAKEFILE_DIR)/downloads/nsync/public \
-I$(MAKEFILE_DIR)/downloads/fft2d \
-I$(MAKEFILE_DIR)/downloads/double_conversion \
-I$(MAKEFILE_DIR)/gen/protobuf/include \
-I$(MAKEFILE_DIR)/gen/protobuf_android/$(ANDROID_ARCH)/include \
-I$(PROTOGENDIR) \
-I$(PBTGENDIR)
Expand Down Expand Up @@ -543,6 +549,7 @@ $(wildcard tensorflow/core/platform/*/*.cc) \
$(wildcard tensorflow/core/platform/*/*/*.cc) \
$(wildcard tensorflow/core/util/*.cc) \
$(wildcard tensorflow/core/util/*/*.cc) \
$(wildcard tensorflow/contrib/makefile/downloads/double_conversion/double-conversion/*.cc) \
tensorflow/core/util/version_info.cc
# Remove duplicates (for version_info.cc)
CORE_CC_ALL_SRCS := $(sort $(CORE_CC_ALL_SRCS))
Expand Down
2 changes: 2 additions & 0 deletions tensorflow/contrib/makefile/download_dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ NSYNC_URL="$(grep -o 'https://mirror.bazel.build/github.com/google/nsync/.*tar\.
PROTOBUF_URL="$(grep -o 'https://mirror.bazel.build/github.com/google/protobuf/.*tar\.gz' "${BZL_FILE_PATH}" | head -n1)"
RE2_URL="$(grep -o 'https://mirror.bazel.build/github.com/google/re2/.*tar\.gz' "${BZL_FILE_PATH}" | head -n1)"
FFT2D_URL="$(grep -o 'http.*fft\.tgz' "${BZL_FILE_PATH}" | grep -v bazel-mirror | head -n1)"
DOUBLE_CONVERSION_URL="$(grep -o "https.*google/double-conversion.*\.zip" "${BZL_FILE_PATH}" | head -n1)"
ABSL_URL="$(grep -o 'https://github.com/abseil/abseil-cpp/.*tar.gz' "${BZL_FILE_PATH}" | head -n1)"

# TODO(petewarden): Some new code in Eigen triggers a clang bug with iOS arm64,
Expand Down Expand Up @@ -74,6 +75,7 @@ download_and_extract "${NSYNC_URL}" "${DOWNLOADS_DIR}/nsync"
download_and_extract "${PROTOBUF_URL}" "${DOWNLOADS_DIR}/protobuf"
download_and_extract "${RE2_URL}" "${DOWNLOADS_DIR}/re2"
download_and_extract "${FFT2D_URL}" "${DOWNLOADS_DIR}/fft2d"
download_and_extract "${DOUBLE_CONVERSION_URL}" "${DOWNLOADS_DIR}/double_conversion"
download_and_extract "${ABSL_URL}" "${DOWNLOADS_DIR}/absl"

replace_by_sed 's#static uint32x4_t p4ui_CONJ_XOR = vld1q_u32( conj_XOR_DATA );#static uint32x4_t p4ui_CONJ_XOR; // = vld1q_u32( conj_XOR_DATA ); - Removed by script#' \
Expand Down
9 changes: 8 additions & 1 deletion tensorflow/core/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,9 @@ cc_library(
"platform/types.h",
] + glob(tf_additional_proto_hdrs()) + glob(tf_env_time_hdrs()),
copts = tf_copts(),
deps = tf_lib_proto_parsing_deps(),
deps = tf_lib_proto_parsing_deps() + [
"@double_conversion//:double-conversion",
],
)

# This build rule (along with :lib_internal, :framework, and
Expand Down Expand Up @@ -1016,6 +1018,7 @@ cc_library(
deps = [
":protos_all_cc_impl",
"//third_party/eigen3",
"@double_conversion//:double-conversion",
"@nsync//:nsync_cpp",
"@protobuf_archive//:protobuf",
],
Expand All @@ -1040,6 +1043,7 @@ cc_library(
":protos_all_cc_impl",
"//third_party/eigen3",
"//third_party/fft2d:fft2d_headers",
"@double_conversion//:double-conversion",
"@fft2d//:fft2d",
"@gemmlowp//:gemmlowp",
"@nsync//:nsync_cpp",
Expand Down Expand Up @@ -1106,6 +1110,7 @@ cc_library(
deps = [
":protos_all_cc_impl",
"//third_party/eigen3",
"@double_conversion//:double-conversion",
Copy link
Contributor

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).

Copy link
Contributor Author

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.

Copy link
Contributor

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?

Copy link
Contributor Author

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.

"@protobuf_archive//:protobuf",
],
alwayslink = 1,
Expand All @@ -1128,6 +1133,7 @@ cc_library(
deps = [
":protos_all_cc_impl",
"//third_party/eigen3",
"@double_conversion//:double-conversion",
"@nsync//:nsync_cpp",
"@protobuf_archive//:protobuf",
],
Expand Down Expand Up @@ -1488,6 +1494,7 @@ cc_library(
"//tensorflow/core/platform/default/build_config:platformlib",
"@snappy",
"@zlib_archive//:zlib",
"@double_conversion//:double-conversion",
"@protobuf_archive//:protobuf",
] + tf_protos_all_impl(),
)
Expand Down
112 changes: 28 additions & 84 deletions tensorflow/core/lib/strings/numbers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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{};
Copy link
Contributor

Choose a reason for hiding this comment

The 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).

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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) {
Expand Down Expand Up @@ -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) {
Expand Down