Skip to content

Commit

Permalink
Iridas .cube and other text format parsing optimizations
Browse files Browse the repository at this point in the history
Primarily driven by a wish to increase performance of parsing .cube
files. But the functions that are added or changed are used across
parsing of all/most of text based formats.

With these changes, parsing "Khronos PBR Neutral" .cube file (5.4MB)
on Ryzen 5950X / VS2022 Release build: 167ms -> 123ms.

- Add locale independent IsSpace(char). Somewhat similar to 3aab90d,
  whitespace trimming perhaps should not be locale dependent.
- Add IsEmptyOrWhiteSpace() and use that inside ParseUtils::nextline,
  instead of doing Trim(line).empty().
- Add StringUtils::StartsWith(char) and use that in various parsers
  that were constructing whole std::string object just to check for a
  single character.
- When building for C++17 or later, NumberUtils can use standard
  <charconv> from_chars functions (except on Apple platforms, where
  those are not implemented for floating point types as of Xcode 15).
  This has advantage of not having to deal with errno or locales. Saves
  some thread local storage accesses and function calls (e.g. on Windows
  errno is actually a function call).
- There's a CMake setup change that adds /Zc:__cplusplus flag for MSVC;
  for backwards compat reasons it does not report proper C++ version
  detection defines otherwise.

Signed-off-by: Aras Pranckevicius <[email protected]>
  • Loading branch information
aras-p committed Oct 1, 2024
1 parent 6998d6d commit 769e887
Show file tree
Hide file tree
Showing 14 changed files with 122 additions and 20 deletions.
3 changes: 3 additions & 0 deletions share/cmake/utils/CompilerFlags.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ if(USE_MSVC)
)
endif()

# Make MSVC compiler report correct __cplusplus version (otherwise reports 199711L)
set(PLATFORM_COMPILE_OPTIONS "${PLATFORM_COMPILE_OPTIONS};/Zc:__cplusplus")

# Explicitely specify the default warning level i.e. /W3.
# Note: Do not use /Wall (i.e. /W4) which adds 'informational' warnings.
set(PLATFORM_COMPILE_OPTIONS "${PLATFORM_COMPILE_OPTIONS};/W3")
Expand Down
4 changes: 2 additions & 2 deletions src/OpenColorIO/LookParse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ void LookParseResult::Token::parse(const std::string & str)
{
// Assert no commas, colons, or | in str.

if(StringUtils::StartsWith(str, "+"))
if(StringUtils::StartsWith(str, '+'))
{
name = StringUtils::LeftTrim(str, '+');
dir = TRANSFORM_DIR_FORWARD;
}
// TODO: Handle --
else if(StringUtils::StartsWith(str, "-"))
else if(StringUtils::StartsWith(str, '-'))
{
name = StringUtils::LeftTrim(str, '-');
dir = TRANSFORM_DIR_INVERSE;
Expand Down
4 changes: 2 additions & 2 deletions src/OpenColorIO/ParseUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -672,13 +672,13 @@ bool nextline(std::istream &istream, std::string &line)
{
line.resize(line.size() - 1);
}
if(!StringUtils::Trim(line).empty())
if(!StringUtils::IsEmptyOrWhiteSpace(line))
{
return true;
}
}

line = "";
line.clear();
return false;
}

Expand Down
4 changes: 2 additions & 2 deletions src/OpenColorIO/fileformats/FileFormat3DL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,11 @@ CachedFileRcPtr LocalFileFormat::read(std::istream & istream,
if(lineParts.empty()) continue;
if (lineParts.size() > 0)
{
if (StringUtils::StartsWith(lineParts[0], "#"))
if (StringUtils::StartsWith(lineParts[0], '#'))
{
continue;
}
if (StringUtils::StartsWith(lineParts[0], "<"))
if (StringUtils::StartsWith(lineParts[0], '<'))
{
// Format error: reject files that could be
// formatted as xml.
Expand Down
6 changes: 3 additions & 3 deletions src/OpenColorIO/fileformats/FileFormatIridasCube.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ LocalFileFormat::read(std::istream & istream,
{
++lineNumber;
// All lines starting with '#' are comments
if (StringUtils::StartsWith(line,"#")) continue;
if (StringUtils::StartsWith(line,'#')) continue;

line = StringUtils::Lower(StringUtils::Trim(line));

Expand Down Expand Up @@ -310,10 +310,10 @@ LocalFileFormat::read(std::istream & istream,

do
{
line = StringUtils::Trim(line);
line = StringUtils::LeftTrim(line);

// All lines starting with '#' are comments
if (StringUtils::StartsWith(line,"#")) continue;
if (StringUtils::StartsWith(line,'#')) continue;

if (line.empty()) continue;

Expand Down
2 changes: 1 addition & 1 deletion src/OpenColorIO/fileformats/FileFormatIridasItx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ CachedFileRcPtr LocalFileFormat::read(std::istream & istream,
{
++lineNumber;
// All lines starting with '#' are comments
if(StringUtils::StartsWith(line,"#")) continue;
if(StringUtils::StartsWith(line,'#')) continue;

// Strip, lowercase, and split the line
parts = StringUtils::SplitByWhiteSpaces(StringUtils::Lower(StringUtils::Trim(line)));
Expand Down
2 changes: 1 addition & 1 deletion src/OpenColorIO/fileformats/FileFormatPandora.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ CachedFileRcPtr LocalFileFormat::read(std::istream & istream,
if(parts.empty()) continue;

// Skip all lines starting with '#'
if(StringUtils::StartsWith(parts[0],"#")) continue;
if(StringUtils::StartsWith(parts[0],'#')) continue;

if(parts[0] == "channel")
{
Expand Down
2 changes: 1 addition & 1 deletion src/OpenColorIO/fileformats/FileFormatResolveCube.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ CachedFileRcPtr LocalFileFormat::read(std::istream & istream,
++lineNumber;

// All lines starting with '#' are comments
if(StringUtils::StartsWith(line,"#"))
if(StringUtils::StartsWith(line,'#'))
{
if(headerComplete)
{
Expand Down
2 changes: 1 addition & 1 deletion src/OpenColorIO/fileformats/FileFormatSpi1D.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ CachedFileRcPtr LocalFileFormat::read(std::istream & istream,
}
}
}
while (istream.good() && !StringUtils::StartsWith(headerLine,"{"));
while (istream.good() && !StringUtils::StartsWith(headerLine,'{'));
}

if (version == -1)
Expand Down
2 changes: 1 addition & 1 deletion src/OpenColorIO/fileformats/FileFormatTruelight.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ CachedFileRcPtr LocalFileFormat::read(std::istream & istream,
if(parts.empty()) continue;

// Parse header metadata (which starts with #)
if(StringUtils::StartsWith(parts[0],"#"))
if(StringUtils::StartsWith(parts[0],'#'))
{
if(parts.size() < 2) continue;

Expand Down
2 changes: 1 addition & 1 deletion src/OpenColorIO/fileformats/FileFormatVF.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ CachedFileRcPtr LocalFileFormat::read(std::istream & istream,

if(parts.empty()) continue;

if(StringUtils::StartsWith(parts[0],"#")) continue;
if(StringUtils::StartsWith(parts[0],'#')) continue;

if(!in3d)
{
Expand Down
48 changes: 45 additions & 3 deletions src/utils/NumberUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@
#ifndef INCLUDED_NUMBERUTILS_H
#define INCLUDED_NUMBERUTILS_H

// With C++17, we can use <charconv> from_chars.
//
// That has advantage of not dealing with locale / errno,
// which might involve locks or thread local storage accesses.
//
// Except on Apple platforms, where (as of Xcode 15 / Apple Clang 15)
// these are not implemented for float/double types.
#if __cpp_lib_to_chars >= 201611L && !defined(__APPLE__)
#define USE_CHARCONV_FROM_CHARS
#include <charconv>
#endif

#if defined(_MSC_VER)
#define really_inline __forceinline
#else
Expand Down Expand Up @@ -56,12 +68,21 @@ static const Locale loc;

really_inline from_chars_result from_chars(const char *first, const char *last, double &value) noexcept
{
errno = 0;
if (!first || !last || first == last)
{
return {first, std::errc::invalid_argument};
}

#ifdef USE_CHARCONV_FROM_CHARS
if (first < last && first[0] == '+')
{
++first;
}
std::from_chars_result res = std::from_chars(first, last, value);
return from_chars_result{ res.ptr, res.ec };
#else

errno = 0;
char * endptr = nullptr;

double
Expand All @@ -88,16 +109,26 @@ really_inline from_chars_result from_chars(const char *first, const char *last,
{
return {first, std::errc::argument_out_of_domain};
}
#endif
}

really_inline from_chars_result from_chars(const char *first, const char *last, float &value) noexcept
{
errno = 0;
if (!first || !last || first == last)
{
return {first, std::errc::invalid_argument};
}

#ifdef USE_CHARCONV_FROM_CHARS
if (first < last && first[0] == '+')
{
++first;
}
std::from_chars_result res = std::from_chars(first, last, value);
return from_chars_result{ res.ptr, res.ec };
#else

errno = 0;
char *endptr = nullptr;

float
Expand Down Expand Up @@ -132,16 +163,26 @@ really_inline from_chars_result from_chars(const char *first, const char *last,
{
return {first, std::errc::argument_out_of_domain};
}
#endif
}

really_inline from_chars_result from_chars(const char *first, const char *last, long int &value) noexcept
{
errno = 0;
if (!first || !last || first == last)
{
return {first, std::errc::invalid_argument};
}

#ifdef USE_CHARCONV_FROM_CHARS
if (first < last && first[0] == '+')
{
++first;
}
std::from_chars_result res = std::from_chars(first, last, value);
return from_chars_result{ res.ptr, res.ec };
#else

errno = 0;
char *endptr = nullptr;

long int
Expand Down Expand Up @@ -170,6 +211,7 @@ really_inline from_chars_result from_chars(const char *first, const char *last,
{
return {first, std::errc::argument_out_of_domain};
}
#endif
}
} // namespace NumberUtils
} // namespace OCIO_NAMESPACE
Expand Down
24 changes: 22 additions & 2 deletions src/utils/StringUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ inline unsigned char Upper(unsigned char c)
}
}

// Checks if character is whitespace (space, tab, newline or other
// non-printable char). Does not take locale into account.
inline bool IsSpace(char c)
{
return c <= ' ';
}

// Return the lower case string.
inline std::string Lower(std::string str)
{
Expand Down Expand Up @@ -95,6 +102,13 @@ inline bool StartsWith(const std::string & str, const std::string & prefix)
return str.size() >= prefix.size() && 0 == str.compare(0, prefix.size(), prefix);
}

// Return true if the string starts with the prefix character.
// Note: The comparison is case sensitive.
inline bool StartsWith(const std::string& str, char prefix)
{
return !str.empty() && str.front() == prefix;
}

// Starting from the left, trim the character.
inline std::string LeftTrim(std::string str, char c)
{
Expand All @@ -106,7 +120,7 @@ inline std::string LeftTrim(std::string str, char c)
// Starting from the left, trim all the space characters i.e. space, tabulation, etc.
inline std::string LeftTrim(std::string str)
{
const auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(static_cast<unsigned char>(ch)); });
const auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !IsSpace(ch); });
str.erase(str.begin(), it);
return str;
}
Expand All @@ -123,7 +137,7 @@ inline std::string RightTrim(std::string str, char c)
inline std::string RightTrim(std::string str)
{
const auto it =
std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(static_cast<unsigned char>(ch)); });
std::find_if(str.rbegin(), str.rend(), [](char ch) { return !IsSpace(ch); });
str.erase(it.base(), str.end());
return str;
}
Expand All @@ -148,6 +162,12 @@ inline void Trim(StringVec & list)
}
}

// Checks if string is empty or white-space only.
inline bool IsEmptyOrWhiteSpace(const std::string& str)
{
return std::find_if(str.begin(), str.end(), [](char ch) { return !IsSpace(ch); }) == str.end();
}

// Split a string content using an arbitrary separator.
inline StringVec Split(const std::string & str, char separator)
{
Expand Down
37 changes: 37 additions & 0 deletions tests/cpu/fileformats/FileFormatIridasCube_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,43 @@ OCIO_ADD_TEST(FileFormatIridasCube, read_failure)
}
}

OCIO_ADD_TEST(FileFormatIridasCube, whitespace_handling)
{
const std::string SAMPLE =
"# comment\n"
"# comment with trailing space \n"
"# next up various forms of empty lines\n"
"\n"
" \n"
" \t \n"
"# whitespace before keywords or after data should be supported\n"
" LUT_3D_SIZE \t 2 \t\n"
"\t \tDOMAIN_MIN 0.25 0.5 0.75\n"
"\n"
"DOMAIN_MAX\t1.5\t2.5\t3.5\n"

"0.0 0.0 0.0\n"
"# comments in between data should be ignored\n"
" 1.0 0.0 \t 0.0\n"
"0.0 1.0 0.0 \n"
" 1.0 1.0 0.0\n"
" \n"
"0.0 0.0 1.0\n"
"1.0 0.0 1.0\n"
"0.0 1.0 1.0\n"
"1.0 1.0 1.0\n"
" \n"
"\n";

OCIO::LocalCachedFileRcPtr file;
OCIO_CHECK_NO_THROW(file = ReadIridasCube(SAMPLE));
OCIO_CHECK_EQUAL(file->domain_min[0], 0.25f);
OCIO_CHECK_EQUAL(file->domain_min[2], 0.75f);
OCIO_CHECK_EQUAL(file->domain_max[0], 1.5f);
OCIO_CHECK_EQUAL(file->domain_max[2], 3.5f);
OCIO_CHECK_EQUAL(file->lut3D->getArray().getLength(), 2);
}

OCIO_ADD_TEST(FileFormatIridasCube, no_shaper)
{
// check baker output
Expand Down

0 comments on commit 769e887

Please sign in to comment.