Uploaded image for project: 'Apache Arrow'
  1. Apache Arrow
  2. ARROW-3321

[C++] Improve integer parsing performance

Details

    • Improvement
    • Status: Resolved
    • Major
    • Resolution: Fixed
    • 0.10.0
    • 0.11.0
    • C++

    Description

      According to the number-parsing-benchmark, parsing integers from strings currently runs at around ~5M items/sec. (on Ubuntu 18.04). We should be able to do better. This will be important for CSV parsing performance.

      Attachments

        Issue Links

          Activity

            githubbot ASF GitHub Bot logged work - 24/Sep/18 13:24
            • Time Spent:
              10m
               
              pitrou opened a new pull request #2619: ARROW-3321: [C++] Improve integer parsing performance
              URL: https://github.com/apache/arrow/pull/2619
               
               
                 Before:
                 ```
                 ---------------------------------------------------------------------
                 Benchmark Time CPU Iterations
                 ---------------------------------------------------------------------
                 BM_IntegerParsing<Int8Type> 1388 ns 1387 ns 502861 5.49911M items/s
                 BM_IntegerParsing<Int16Type> 1475 ns 1475 ns 468724 5.17179M items/s
                 BM_IntegerParsing<Int32Type> 1730 ns 1729 ns 405693 4.41194M items/s
                 BM_IntegerParsing<Int64Type> 2131 ns 2131 ns 328192 3.58034M items/s
                 BM_IntegerParsing<UInt8Type> 1238 ns 1238 ns 572573 6.16483M items/s
                 BM_IntegerParsing<UInt16Type> 1302 ns 1301 ns 537960 5.86206M items/s
                 BM_IntegerParsing<UInt32Type> 1391 ns 1391 ns 502859 5.4857M items/s
                 BM_IntegerParsing<UInt64Type> 1637 ns 1637 ns 427832 4.661M items/s
                 BM_FloatParsing<FloatType> 4437 ns 4436 ns 156887 1.71973M items/s
                 BM_FloatParsing<DoubleType> 4593 ns 4592 ns 152459 1.66129M items/s
                 ```
                 
                 After:
                 ```
                 ---------------------------------------------------------------------
                 Benchmark Time CPU Iterations
                 ---------------------------------------------------------------------
                 BM_IntegerParsing<Int8Type> 23 ns 23 ns 29800687 324.788M items/s
                 BM_IntegerParsing<Int16Type> 27 ns 27 ns 26593165 287.438M items/s
                 BM_IntegerParsing<Int32Type> 34 ns 34 ns 20689813 226.211M items/s
                 BM_IntegerParsing<Int64Type> 49 ns 49 ns 14256379 155.424M items/s
                 BM_IntegerParsing<UInt8Type> 17 ns 17 ns 42295211 454.911M items/s
                 BM_IntegerParsing<UInt16Type> 16 ns 16 ns 42663172 464.397M items/s
                 BM_IntegerParsing<UInt32Type> 21 ns 21 ns 33372432 363.209M items/s
                 BM_IntegerParsing<UInt64Type> 33 ns 33 ns 21502295 234.255M items/s
                 BM_FloatParsing<FloatType> 4554 ns 4553 ns 153207 1.67565M items/s
                 BM_FloatParsing<DoubleType> 4579 ns 4578 ns 152304 1.66651M items/s
                 ```
                 

              ----------------------------------------------------------------
              This is an automated message from the Apache Git Service.
              To respond to the message, please log on GitHub and use the
              URL above to go to the specific comment.
               
              For queries about this service, please contact Infrastructure at:
              users@infra.apache.org
            githubbot ASF GitHub Bot logged work - 24/Sep/18 14:57
            githubbot ASF GitHub Bot logged work - 24/Sep/18 17:39
            • Time Spent:
              10m
               
              kszucs commented on issue #2619: ARROW-3321: [C++] Improve integer parsing performance
              URL: https://github.com/apache/arrow/pull/2619#issuecomment-424061191
               
               
                 That's an impressive speedup!

              ----------------------------------------------------------------
              This is an automated message from the Apache Git Service.
              To respond to the message, please log on GitHub and use the
              URL above to go to the specific comment.
               
              For queries about this service, please contact Infrastructure at:
              users@infra.apache.org
            githubbot ASF GitHub Bot logged work - 25/Sep/18 11:42
            • Time Spent:
              10m
               
              wesm commented on a change in pull request #2619: ARROW-3321: [C++] Improve integer parsing performance
              URL: https://github.com/apache/arrow/pull/2619#discussion_r220156928
               
               

               ##########
               File path: cpp/src/arrow/util/parsing.h
               ##########
               @@ -108,81 +109,213 @@ class StringConverter<DoubleType> : public StringToFloatConverterMixin<DoubleTyp
               
               // NOTE: HalfFloatType would require a half<->float conversion library
               
              +namespace detail {
              +
              +#define PARSE_UNSIGNED_ITERATION(C_TYPE) \
              + if (length > 0) { \
              + uint8_t digit = static_cast<uint8_t>(*s++ - '0'); \
              + result = static_cast<C_TYPE>(result * 10U); \
              + length--; \
              + if (ARROW_PREDICT_FALSE(digit > 9U)) { \
              + /* Non-digit */ \
              + return false; \
              + } \
              + result = static_cast<C_TYPE>(result + digit); \
              + }
              +
              +#define PARSE_UNSIGNED_ITERATION_LAST(C_TYPE) \
              + if (length > 0) { \
              + if (ARROW_PREDICT_FALSE(result > std::numeric_limits<C_TYPE>::max() / 10U)) { \
              + /* Overflow */ \
               
               Review comment:
                 At some point do you think we might want to return a "reason" as an out argument when the parser returns false? Could be follow up work

              ----------------------------------------------------------------
              This is an automated message from the Apache Git Service.
              To respond to the message, please log on GitHub and use the
              URL above to go to the specific comment.
               
              For queries about this service, please contact Infrastructure at:
              users@infra.apache.org
            githubbot ASF GitHub Bot logged work - 25/Sep/18 11:43
            • Time Spent:
              10m
               
              wesm closed pull request #2619: ARROW-3321: [C++] Improve integer parsing performance
              URL: https://github.com/apache/arrow/pull/2619
               
               
                 

              This is a PR merged from a forked repository.
              As GitHub hides the original diff on merge, it is displayed below for
              the sake of provenance:

              As this is a foreign pull request (from a fork), the diff is supplied
              below (as it won't show otherwise due to GitHub magic):

              diff --git a/cpp/src/arrow/util/parsing-util-test.cc b/cpp/src/arrow/util/parsing-util-test.cc
              index b126b8211b..9fa5ffb7a9 100644
              --- a/cpp/src/arrow/util/parsing-util-test.cc
              +++ b/cpp/src/arrow/util/parsing-util-test.cc
              @@ -119,13 +119,16 @@ TEST(StringConversion, ToInt8) {
               
                 AssertConversion(converter, "0", 0);
                 AssertConversion(converter, "127", 127);
              + AssertConversion(converter, "0127", 127);
                 AssertConversion(converter, "-128", -128);
              + AssertConversion(converter, "-00128", -128);
               
                 // Non-representable values
                 AssertConversionFails(converter, "128");
                 AssertConversionFails(converter, "-129");
               
                 AssertConversionFails(converter, "");
              + AssertConversionFails(converter, "-");
                 AssertConversionFails(converter, "0.0");
                 AssertConversionFails(converter, "e");
               }
              @@ -134,13 +137,18 @@ TEST(StringConversion, ToUInt8) {
                 StringConverter<UInt8Type> converter;
               
                 AssertConversion(converter, "0", 0);
              + AssertConversion(converter, "26", 26);
                 AssertConversion(converter, "255", 255);
              + AssertConversion(converter, "0255", 255);
               
                 // Non-representable values
              - // AssertConversionFails(converter, "-1");
              + AssertConversionFails(converter, "-1");
                 AssertConversionFails(converter, "256");
              + AssertConversionFails(converter, "260");
              + AssertConversionFails(converter, "1234");
               
                 AssertConversionFails(converter, "");
              + AssertConversionFails(converter, "-");
                 AssertConversionFails(converter, "0.0");
                 AssertConversionFails(converter, "e");
               }
              @@ -150,13 +158,16 @@ TEST(StringConversion, ToInt16) {
               
                 AssertConversion(converter, "0", 0);
                 AssertConversion(converter, "32767", 32767);
              + AssertConversion(converter, "032767", 32767);
                 AssertConversion(converter, "-32768", -32768);
              + AssertConversion(converter, "-0032768", -32768);
               
                 // Non-representable values
                 AssertConversionFails(converter, "32768");
                 AssertConversionFails(converter, "-32769");
               
                 AssertConversionFails(converter, "");
              + AssertConversionFails(converter, "-");
                 AssertConversionFails(converter, "0.0");
                 AssertConversionFails(converter, "e");
               }
              @@ -165,13 +176,17 @@ TEST(StringConversion, ToUInt16) {
                 StringConverter<UInt16Type> converter;
               
                 AssertConversion(converter, "0", 0);
              + AssertConversion(converter, "6660", 6660);
                 AssertConversion(converter, "65535", 65535);
              + AssertConversion(converter, "065535", 65535);
               
                 // Non-representable values
              - // AssertConversionFails(converter, "-1");
              + AssertConversionFails(converter, "-1");
                 AssertConversionFails(converter, "65536");
              + AssertConversionFails(converter, "123456");
               
                 AssertConversionFails(converter, "");
              + AssertConversionFails(converter, "-");
                 AssertConversionFails(converter, "0.0");
                 AssertConversionFails(converter, "e");
               }
              @@ -181,13 +196,16 @@ TEST(StringConversion, ToInt32) {
               
                 AssertConversion(converter, "0", 0);
                 AssertConversion(converter, "2147483647", 2147483647);
              + AssertConversion(converter, "02147483647", 2147483647);
                 AssertConversion(converter, "-2147483648", -2147483648LL);
              + AssertConversion(converter, "-002147483648", -2147483648LL);
               
                 // Non-representable values
                 AssertConversionFails(converter, "2147483648");
                 AssertConversionFails(converter, "-2147483649");
               
                 AssertConversionFails(converter, "");
              + AssertConversionFails(converter, "-");
                 AssertConversionFails(converter, "0.0");
                 AssertConversionFails(converter, "e");
               }
              @@ -196,13 +214,17 @@ TEST(StringConversion, ToUInt32) {
                 StringConverter<UInt32Type> converter;
               
                 AssertConversion(converter, "0", 0);
              + AssertConversion(converter, "432198765", 432198765UL);
                 AssertConversion(converter, "4294967295", 4294967295UL);
              + AssertConversion(converter, "04294967295", 4294967295UL);
               
                 // Non-representable values
              - // AssertConversionFails(converter, "-1");
              + AssertConversionFails(converter, "-1");
                 AssertConversionFails(converter, "4294967296");
              + AssertConversionFails(converter, "12345678901");
               
                 AssertConversionFails(converter, "");
              + AssertConversionFails(converter, "-");
                 AssertConversionFails(converter, "0.0");
                 AssertConversionFails(converter, "e");
               }
              @@ -212,13 +234,16 @@ TEST(StringConversion, ToInt64) {
               
                 AssertConversion(converter, "0", 0);
                 AssertConversion(converter, "9223372036854775807", 9223372036854775807LL);
              + AssertConversion(converter, "09223372036854775807", 9223372036854775807LL);
                 AssertConversion(converter, "-9223372036854775808", -9223372036854775807LL - 1);
              + AssertConversion(converter, "-009223372036854775808", -9223372036854775807LL - 1);
               
                 // Non-representable values
                 AssertConversionFails(converter, "9223372036854775808");
                 AssertConversionFails(converter, "-9223372036854775809");
               
                 AssertConversionFails(converter, "");
              + AssertConversionFails(converter, "-");
                 AssertConversionFails(converter, "0.0");
                 AssertConversionFails(converter, "e");
               }
              @@ -230,10 +255,11 @@ TEST(StringConversion, ToUInt64) {
                 AssertConversion(converter, "18446744073709551615", 18446744073709551615ULL);
               
                 // Non-representable values
              - // AssertConversionFails(converter, "-1");
              + AssertConversionFails(converter, "-1");
                 AssertConversionFails(converter, "18446744073709551616");
               
                 AssertConversionFails(converter, "");
              + AssertConversionFails(converter, "-");
                 AssertConversionFails(converter, "0.0");
                 AssertConversionFails(converter, "e");
               }
              diff --git a/cpp/src/arrow/util/parsing.h b/cpp/src/arrow/util/parsing.h
              index 8efc6143c8..4f2dc7894a 100644
              --- a/cpp/src/arrow/util/parsing.h
              +++ b/cpp/src/arrow/util/parsing.h
              @@ -22,6 +22,7 @@
               #include <locale>
               #include <sstream>
               #include <string>
              +#include <type_traits>
               
               #include "arrow/type.h"
               #include "arrow/type_traits.h"
              @@ -108,81 +109,213 @@ class StringConverter<DoubleType> : public StringToFloatConverterMixin<DoubleTyp
               
               // NOTE: HalfFloatType would require a half<->float conversion library
               
              +namespace detail {
              +
              +#define PARSE_UNSIGNED_ITERATION(C_TYPE) \
              + if (length > 0) { \
              + uint8_t digit = static_cast<uint8_t>(*s++ - '0'); \
              + result = static_cast<C_TYPE>(result * 10U); \
              + length--; \
              + if (ARROW_PREDICT_FALSE(digit > 9U)) { \
              + /* Non-digit */ \
              + return false; \
              + } \
              + result = static_cast<C_TYPE>(result + digit); \
              + }
              +
              +#define PARSE_UNSIGNED_ITERATION_LAST(C_TYPE) \
              + if (length > 0) { \
              + if (ARROW_PREDICT_FALSE(result > std::numeric_limits<C_TYPE>::max() / 10U)) { \
              + /* Overflow */ \
              + return false; \
              + } \
              + uint8_t digit = static_cast<uint8_t>(*s++ - '0'); \
              + result = static_cast<C_TYPE>(result * 10U); \
              + C_TYPE new_result = static_cast<C_TYPE>(result + digit); \
              + if (ARROW_PREDICT_FALSE(--length > 0)) { \
              + /* Too many digits */ \
              + return false; \
              + } \
              + if (ARROW_PREDICT_FALSE(digit > 9U)) { \
              + /* Non-digit */ \
              + return false; \
              + } \
              + if (ARROW_PREDICT_FALSE(new_result < result)) { \
              + /* Overflow */ \
              + return false; \
              + } \
              + result = new_result; \
              + }
              +
              +inline bool ParseUnsigned(const char* s, size_t length, uint8_t* out) {
              + uint8_t result = 0;
              +
              + PARSE_UNSIGNED_ITERATION(uint8_t);
              + PARSE_UNSIGNED_ITERATION(uint8_t);
              + PARSE_UNSIGNED_ITERATION_LAST(uint8_t);
              + *out = result;
              + return true;
              +}
              +
              +inline bool ParseUnsigned(const char* s, size_t length, uint16_t* out) {
              + uint16_t result = 0;
              +
              + PARSE_UNSIGNED_ITERATION(uint16_t);
              + PARSE_UNSIGNED_ITERATION(uint16_t);
              + PARSE_UNSIGNED_ITERATION(uint16_t);
              + PARSE_UNSIGNED_ITERATION(uint16_t);
              + PARSE_UNSIGNED_ITERATION_LAST(uint16_t);
              + *out = result;
              + return true;
              +}
              +
              +inline bool ParseUnsigned(const char* s, size_t length, uint32_t* out) {
              + uint32_t result = 0;
              +
              + PARSE_UNSIGNED_ITERATION(uint32_t);
              + PARSE_UNSIGNED_ITERATION(uint32_t);
              + PARSE_UNSIGNED_ITERATION(uint32_t);
              + PARSE_UNSIGNED_ITERATION(uint32_t);
              + PARSE_UNSIGNED_ITERATION(uint32_t);
              +
              + PARSE_UNSIGNED_ITERATION(uint32_t);
              + PARSE_UNSIGNED_ITERATION(uint32_t);
              + PARSE_UNSIGNED_ITERATION(uint32_t);
              + PARSE_UNSIGNED_ITERATION(uint32_t);
              +
              + PARSE_UNSIGNED_ITERATION_LAST(uint32_t);
              + *out = result;
              + return true;
              +}
              +
              +inline bool ParseUnsigned(const char* s, size_t length, uint64_t* out) {
              + uint64_t result = 0;
              +
              + PARSE_UNSIGNED_ITERATION(uint64_t);
              + PARSE_UNSIGNED_ITERATION(uint64_t);
              + PARSE_UNSIGNED_ITERATION(uint64_t);
              + PARSE_UNSIGNED_ITERATION(uint64_t);
              + PARSE_UNSIGNED_ITERATION(uint64_t);
              +
              + PARSE_UNSIGNED_ITERATION(uint64_t);
              + PARSE_UNSIGNED_ITERATION(uint64_t);
              + PARSE_UNSIGNED_ITERATION(uint64_t);
              + PARSE_UNSIGNED_ITERATION(uint64_t);
              + PARSE_UNSIGNED_ITERATION(uint64_t);
              +
              + PARSE_UNSIGNED_ITERATION(uint64_t);
              + PARSE_UNSIGNED_ITERATION(uint64_t);
              + PARSE_UNSIGNED_ITERATION(uint64_t);
              + PARSE_UNSIGNED_ITERATION(uint64_t);
              + PARSE_UNSIGNED_ITERATION(uint64_t);
              +
              + PARSE_UNSIGNED_ITERATION(uint64_t);
              + PARSE_UNSIGNED_ITERATION(uint64_t);
              + PARSE_UNSIGNED_ITERATION(uint64_t);
              + PARSE_UNSIGNED_ITERATION(uint64_t);
              +
              + PARSE_UNSIGNED_ITERATION_LAST(uint64_t);
              + *out = result;
              + return true;
              +}
              +
              +#undef PARSE_UNSIGNED_ITERATION
              +#undef PARSE_UNSIGNED_ITERATION_LAST
              +
              +} // namespace detail
              +
               template <class ARROW_TYPE>
              -class StringConverter<ARROW_TYPE, enable_if_signed_integer<ARROW_TYPE>> {
              +class StringToUnsignedIntConverterMixin {
                public:
                 using value_type = typename ARROW_TYPE::c_type;
               
              - StringConverter() { ibuf.imbue(std::locale::classic()); }
              -
                 bool operator()(const char* s, size_t length, value_type* out) {
              - static constexpr bool need_long_long = sizeof(value_type) > sizeof(long); // NOLINT
              - static constexpr value_type min_value = std::numeric_limits<value_type>::min();
              - static constexpr value_type max_value = std::numeric_limits<value_type>::max();
              -
              - ibuf.clear();
              - ibuf.str(std::string(s, length));
              - if (need_long_long) {
              - long long res; // NOLINT
              - ibuf >> res;
              - *out = static_cast<value_type>(res); // may downcast
              - if (res < min_value || res > max_value) {
              - return false;
              - }
              - } else {
              - long res; // NOLINT
              - ibuf >> res;
              - *out = static_cast<value_type>(res); // may downcast
              - if (res < min_value || res > max_value) {
              - return false;
              - }
              + if (ARROW_PREDICT_FALSE(length == 0)) {
              + return false;
                   }
              - // XXX Should we reset errno on failure?
              - return !ibuf.fail() && ibuf.eof();
              + // Skip leading zeros
              + while (length > 0 && *s == '0') {
              + length--;
              + s++;
              + }
              + return detail::ParseUnsigned(s, length, out);
                 }
              +};
               
              - protected:
              - std::istringstream ibuf;
              +template <>
              +class StringConverter<UInt8Type> : public StringToUnsignedIntConverterMixin<UInt8Type> {};
              +
              +template <>
              +class StringConverter<UInt16Type> : public StringToUnsignedIntConverterMixin<UInt16Type> {
              +};
              +
              +template <>
              +class StringConverter<UInt32Type> : public StringToUnsignedIntConverterMixin<UInt32Type> {
              +};
              +
              +template <>
              +class StringConverter<UInt64Type> : public StringToUnsignedIntConverterMixin<UInt64Type> {
               };
               
               template <class ARROW_TYPE>
              -class StringConverter<ARROW_TYPE, enable_if_unsigned_integer<ARROW_TYPE>> {
              +class StringToSignedIntConverterMixin {
                public:
                 using value_type = typename ARROW_TYPE::c_type;
              -
              - StringConverter() { ibuf.imbue(std::locale::classic()); }
              + using unsigned_type = typename std::make_unsigned<value_type>::type;
               
                 bool operator()(const char* s, size_t length, value_type* out) {
              - static constexpr bool need_long_long =
              - sizeof(value_type) > sizeof(unsigned long); // NOLINT
              - static constexpr value_type max_value = std::numeric_limits<value_type>::max();
              + static constexpr unsigned_type max_positive =
              + static_cast<unsigned_type>(std::numeric_limits<value_type>::max());
              + // Assuming two's complement
              + static constexpr unsigned_type max_negative = max_positive + 1;
              + bool negative = false;
              + unsigned_type unsigned_value = 0;
               
              - ibuf.clear();
              - ibuf.str(std::string(s, length));
              - // XXX The following unfortunately allows negative input values
              - if (need_long_long) {
              - unsigned long long res; // NOLINT
              - ibuf >> res;
              - *out = static_cast<value_type>(res); // may downcast
              - if (res > max_value) {
              + if (ARROW_PREDICT_FALSE(length == 0)) {
              + return false;
              + }
              + if (*s == '-') {
              + negative = true;
              + s++;
              + if (--length == 0) {
                       return false;
                     }
              + }
              + // Skip leading zeros
              + while (length > 0 && *s == '0') {
              + length--;
              + s++;
              + }
              + if (!ARROW_PREDICT_TRUE(detail::ParseUnsigned(s, length, &unsigned_value))) {
              + return false;
              + }
              + if (negative) {
              + if (ARROW_PREDICT_FALSE(unsigned_value > max_negative)) {
              + return false;
              + }
              + *out = static_cast<value_type>(-static_cast<value_type>(unsigned_value));
                   } else {
              - unsigned long res; // NOLINT
              - ibuf >> res;
              - *out = static_cast<value_type>(res); // may downcast
              - if (res > max_value) {
              + if (ARROW_PREDICT_FALSE(unsigned_value > max_positive)) {
                       return false;
                     }
              + *out = static_cast<value_type>(unsigned_value);
                   }
              - // XXX Should we reset errno on failure?
              - return !ibuf.fail() && ibuf.eof();
              + return true;
                 }
              -
              - protected:
              - std::istringstream ibuf;
               };
               
              +template <>
              +class StringConverter<Int8Type> : public StringToSignedIntConverterMixin<Int8Type> {};
              +
              +template <>
              +class StringConverter<Int16Type> : public StringToSignedIntConverterMixin<Int16Type> {};
              +
              +template <>
              +class StringConverter<Int32Type> : public StringToSignedIntConverterMixin<Int32Type> {};
              +
              +template <>
              +class StringConverter<Int64Type> : public StringToSignedIntConverterMixin<Int64Type> {};
              +
               } // namespace internal
               } // namespace arrow
               


               

              ----------------------------------------------------------------
              This is an automated message from the Apache Git Service.
              To respond to the message, please log on GitHub and use the
              URL above to go to the specific comment.
               
              For queries about this service, please contact Infrastructure at:
              users@infra.apache.org
            githubbot ASF GitHub Bot logged work - 25/Sep/18 11:51
            • Time Spent:
              10m
               
              pitrou commented on a change in pull request #2619: ARROW-3321: [C++] Improve integer parsing performance
              URL: https://github.com/apache/arrow/pull/2619#discussion_r220160191
               
               

               ##########
               File path: cpp/src/arrow/util/parsing.h
               ##########
               @@ -108,81 +109,213 @@ class StringConverter<DoubleType> : public StringToFloatConverterMixin<DoubleTyp
               
               // NOTE: HalfFloatType would require a half<->float conversion library
               
              +namespace detail {
              +
              +#define PARSE_UNSIGNED_ITERATION(C_TYPE) \
              + if (length > 0) { \
              + uint8_t digit = static_cast<uint8_t>(*s++ - '0'); \
              + result = static_cast<C_TYPE>(result * 10U); \
              + length--; \
              + if (ARROW_PREDICT_FALSE(digit > 9U)) { \
              + /* Non-digit */ \
              + return false; \
              + } \
              + result = static_cast<C_TYPE>(result + digit); \
              + }
              +
              +#define PARSE_UNSIGNED_ITERATION_LAST(C_TYPE) \
              + if (length > 0) { \
              + if (ARROW_PREDICT_FALSE(result > std::numeric_limits<C_TYPE>::max() / 10U)) { \
              + /* Overflow */ \
               
               Review comment:
                 Not sure, because if we're calling out to an external parser (as we'll keep doing for floating-point parsing), it may not give us a reason.

              ----------------------------------------------------------------
              This is an automated message from the Apache Git Service.
              To respond to the message, please log on GitHub and use the
              URL above to go to the specific comment.
               
              For queries about this service, please contact Infrastructure at:
              users@infra.apache.org

            People

              apitrou Antoine Pitrou
              apitrou Antoine Pitrou
              Votes:
              0 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved:

                Time Tracking

                  Estimated:
                  Original Estimate - Not Specified
                  Not Specified
                  Remaining:
                  Remaining Estimate - 0h
                  0h
                  Logged:
                  Time Spent - 1h
                  1h