
I was experimenting with Xpressive to see how it would compare with some custom, string-based numeric input parsing. The Xpressive code is over 175X slower than the custom code. Have I done anything to make it really slow? Can it be made faster? I have included the Xpressive code and the performance testing code below for your study. Can you spot anything I've done wrong? (BTW, I tried using Spirit.Qi for this -- with Boost 1.37 -- but never could get things to compile. I gave up and decided I'd wait until I can use a newer release before I fight that fight again. :-} ) FYI: bar::numeric_cast winds up calling strtod(), strtol(), etc. It is semantically equivalent to lexical_cast. #include <boost/xpressive/xpressive_static.hpp> #include <boost/xpressive/regex_actions.hpp> namespace foo { namespace op { template <class T> struct numeric_cast { typedef T result_type; template <class Value> T operator ()(Value const & _value) const { return bar::numeric_cast<T>(_value); } }; } struct direct_impl { typedef int64_t result_type; template <class Value> int64_t operator ()(Value const & _value) const { int64_t result(bar::numeric_cast<int64_t>(_value)); result *= 160000; return result; } }; struct to_price_impl { typedef int64_t result_type; template <class Value> int64_t operator ()(Value const & _value) const { int64_t result(0); if (_value) { double const value(bar::numeric_cast<double>(_value)); result = static_cast<int64_t>( std::floor(160000.0 * value + 0.5)); } return result; } template <class Value> int64_t operator ()(Value const & _numerator, Value const & _denominator) const { unsigned const numerator(bar::numeric_cast<unsigned>(_numerator)); unsigned const denominator(bar::numeric_cast<unsigned>(_denominator)); double const fraction((160000.0 * numerator) / denominator); return static_cast<int64_t>(std::floor(fraction + 0.5)); } template <class Value> int64_t operator ()(Value const & _whole, double const _fraction) const { int const whole(bar::numeric_cast<int>(_whole)); double const value(160000.0 * whole + _fraction); return static_cast<int64_t>(std::floor(value + 0.5)); } }; int64_t parse(std::string const & _value); boost::xpressive::function<direct_impl>::type const direct = {{}}; boost::xpressive::function<to_price_impl>::type const to_price = {{}}; BOOST_PROTO_DEFINE_FUNCTION_TEMPLATE( 1 , numeric_cast , boost::proto::default_domain , (boost::proto::tag::function) , ((op::numeric_cast)(typename)) ) } int64_t foo::parse(std::string const & _value) { using namespace boost::xpressive; if (_value.empty()) { return 0; } int64_t result; bool negative(false); sregex zero = as_xpr('0'); sregex sign = as_xpr('-') [ ref(negative) = true ] | as_xpr('+'); // ignore // ignore leading zeroes to avoid octal parsing sregex fraction = ( !zero >> (s1= +_d) >> !blank >> '/' >> !blank >> !zero >> (s2= +_d) ) [ ref(result) = to_price(s1, s2) ]; sregex real = ( *_d >> '.' >> *_d >> !((set= 'E', 'e', 'G', 'g') >> +_d) ) [ ref(result) = to_price(_) ]; // ignore leading zeroes to avoid octal parsing sregex whole_number = ( !zero >> (s1= +_d) >> +blank >> fraction ) [ ref(result) = to_price(s1, ref(result)) ]; // ignore leading zeroes to avoid octal parsing sregex integer = ( !zero >> (s1= +_d) ) [ ref(result) = direct(s1) ]; sregex price = *blank >> !sign >> (real | whole_number | fraction | integer) >> *space; smatch match; if (!regex_match(_value.begin(), _value.end(), match, price)) { throw price_parsing_failed(_value); } if (negative) { result = -result; } return result; } void test_performance() { Timer timer; std::string const input("1234.5678e10"); int64_t total(0); foo::parse(input); // prime the cache timer.start(); for (int i(0); i < 10000000; ++i) { int64_t price(foo::parse(input)); price += i; total += price; } timer.stop(); std::cout << timer.elapsed() << "s elapsed (total = " << total ')' << std::endl; } _____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

Stewart, Robert wrote:
I was experimenting with Xpressive to see how it would compare with some custom, string-based numeric input parsing. The Xpressive code is over 175X slower than the custom code. Have I done anything to make it really slow? Can it be made faster? <snip>
Where is the code against which you are benchmarking? From looking at the code, I can see a few areas for improvement. With each call to parse(), you construct and discard a match_results object. For optimal performance, you should reuse the match_results object to avoid extra memory allocation. Also, many of the quantifiers in your grammar do not require backtracking, so you could use keep() to turn backtracking off selectively. That's all I have for now. -- Eric Niebler BoostPro Computing http://www.boostpro.com

Eric Niebler wrote:
Stewart, Robert wrote:
I was experimenting with Xpressive to see how it would compare with some custom, string-based numeric input parsing. The Xpressive code is over 175X slower than the custom code. Have I done anything to make it really slow? Can it be made faster? <snip>
Where is the code against which you are benchmarking?
See below. As you'll see, it isn't nearly as maintainable or clear, but the wide performance difference certainly favors it.
From looking at the code, I can see a few areas for improvement. With each call to parse(), you construct and discard a match_results object. For optimal performance, you should reuse the match_results object to avoid extra memory allocation. Also, many of the quantifiers in your grammar do not require backtracking, so you could use keep() to turn backtracking off selectively.
The function is called in a one-off fashion that wouldn't permit keeping the match_results object, unfortunately. (A function local static would suffice but then the function wouldn't be thread safe.) I'm not certain I understand where to apply keep(), but I'll have a go at it. _______________________ FYI: bar::to_number() calls strtod(), strtol(), etc. template <class T> T foo::extract(char const * & _input, char const * const _description, std::string const & _value) { T result; if (!bar::to_number(result, _input, &_input, 10)) { raise_extract_failed(_description, _value); } return result; } inline int64_t foo::round(const double _value) { return static_cast<int64_t>(std::floor(_value + 0.5)); } int64_t foo::parse(std::string const & _value) { if (_value.empty()) { return 0; } size_t offset(_value.find_first_not_of("-+0123456789eE ./")); const bool invalid(std::string::npos != offset); if (invalid) { raise_parsing_failed(_value); } const char * const input(_value.c_str()); const char * const last(input + _value.length()); const char * end(input); int64_t result; (void)bar::to_number(result, end, &end, 10); const bool floating_point('.' == *end); if (floating_point) { end = input; const double value(extract<double>(end, "input", _value)); const bool negative(0.0 > value); double multiple(value * 160000.0); if (negative) { multiple = -multiple; } result = round(multiple); if (negative) { result = -result; } } else // not floating point { result *= 160000; if (end != last) { offset = _value.find_first_of('/', end - input); const bool is_fraction(std::string::npos != offset); if (is_fraction) { const bool negative(0 > result); if (negative) { result = -result; } offset = _value.find_last_of(' ', offset); const bool is_mixed_number(std::string::npos != offset); if (is_mixed_number) { const unsigned numerator( extract<unsigned>(end, "numerator", _value)); end += 1; // skip the slash const unsigned denominator( extract<unsigned>(end, "denominator", _value)); const double fraction((160000.0 * numerator) / denominator); result = round(result + fraction); } else { end += 1; // skip the slash const unsigned denominator( extract<unsigned>(end, "denominator", _value)); const double fraction(result / denominator); result = round(fraction); } if (negative) { result = -result; } } } } return result; } _____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

"Stewart, Robert" <Robert.Stewart@sig.com> wrote in message news:DF2E67F3D097004694C8428C70A3FD69046843E230@msgbal516.ds.susq.com...
I was experimenting with Xpressive to see how it would compare with some custom, string-based numeric input parsing. The Xpressive code is over 175X slower than the custom code.
Each call to parse() is constructing the sregex variables. If you make them static or move them out of the loop, it should help a lot. Regards, Dave Jenkins

Dave Jenkins wrote:
"Stewart, Robert" <Robert.Stewart@sig.com> wrote in message news:DF2E67F3D097004694C8428C70A3FD69046843E230@msgbal516.ds.s usq.com...
I was experimenting with Xpressive to see how it would compare with some custom, string-based numeric input parsing. The Xpressive code is over 175X slower than the custom code.
Each call to parse() is constructing the sregex variables. If you make them static or move them out of the loop, it should help a lot.
Thanks, but see my reply to Eric's. While that would help with the benchmark performance, the nature of the function I'm testing means I can't do that otherwise. _____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

Each call to parse() is constructing the sregex variables. If you make them static or move them out of the loop, it should help a lot.
Thanks, but see my reply to Eric's. While that would help with the benchmark performance, the nature of the function I'm testing means I can't do that otherwise.
How about making them static const? That should be thread safe.

Dave Jenkins wrote:
Each call to parse() is constructing the sregex variables. If you make them static or move them out of the loop, it should help a lot.
Thanks, but see my reply to Eric's. While that would help with the benchmark performance, the nature of the function I'm testing means I can't do that otherwise.
How about making them static const? That should be thread safe.
I'm sorry, I read your original suggestion as being the same as Eric's: reuse the match_results. Unfortunately, the semantic actions reference local variables, so I don't think I can make the sregex's static (please correct me if I'm wrong, but I can't see how multiple parallel invocations of the function could reference separate stack variables from static sregex's). I did make the sregex's const; that appears to have improved performance a bit. _____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

Stewart, Robert wrote:
Dave Jenkins wrote:
Each call to parse() is constructing the sregex variables. If you make them static or move them out of the loop, it should help a lot. Thanks, but see my reply to Eric's. While that would help with the benchmark performance, the nature of the function I'm testing means I can't do that otherwise. How about making them static const? That should be thread safe.
I'm sorry, I read your original suggestion as being the same as Eric's: reuse the match_results. Unfortunately, the semantic actions reference local variables, so I don't think I can make the sregex's static (please correct me if I'm wrong, but I can't see how multiple parallel invocations of the function could reference separate stack variables from static sregex's). I did make the sregex's const; that appears to have improved performance a bit.
You can still make the regex objects static const and use placeholders in the semantic actions. See the following section: http://www.boost.org/doc/libs/1_39_0/doc/html/xpressive/user_s_guide.html#bo... -- Eric Niebler BoostPro Computing http://www.boostpro.com

Eric Niebler wrote:
Stewart, Robert wrote:
Dave Jenkins wrote:
Each call to parse() is constructing the sregex variables. [snip] How about making them static const? That should be thread safe.
You can still make the regex objects static const and use placeholders in the semantic actions. See the following section:
http://www.boost.org/doc/libs/1_39_0/doc/html/xpressive/user_s_guide.html#bo...
I forgot about that section. Thanks for the pointer. Whoa! The performance just shot up to a mere 6X the custom code (from 175X). That might well be fast enough to keep the Xpressive version because of its readability! Thanks Eric! _____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

Whoa! The performance just shot up to a mere 6X the custom code (from 175X). That might well be fast enough to keep the Xpressive version because of its readability!
Can you create thread-local match_results objects and reuse them? If so, I think you'll see parsing dwindle to almost nothing and your semantic actions will account for the bulk of the time spent. Regards, Dave Jenkins

Why is Xpressive more readable than using lexical_cast or your numeric_cast? I've implemented specializations of lexical_cast for string->numeric conversions using strto[ld]. It is a vast speed improvement of course, and since those specializations are hidden away in a header, readability is the same as with normal lexical_casts. Maybe I'm misunderstanding your use case? Do you not know ahead of time what the type of the number will be? -Matt Dave Jenkins wrote:
Whoa! The performance just shot up to a mere 6X the custom code (from 175X). That might well be fast enough to keep the Xpressive version because of its readability!
Can you create thread-local match_results objects and reuse them? If so, I think you'll see parsing dwindle to almost nothing and your semantic actions will account for the bulk of the time spent.
Regards, Dave Jenkins

Matthew Chambers wrote:
Why is Xpressive more readable than using lexical_cast or your numeric_cast? I've implemented specializations of lexical_cast for string->numeric conversions using strto[ld]. It is a vast speed improvement of course, and since those specializations are hidden away in a header, readability is the same as with normal lexical_casts. Maybe I'm misunderstanding your use case? Do you not know ahead of time what the type of the number will be?
If you'll have a look at my first post in this thread, you'll see the Xpressive code and the grammar I've implemented. A later post shows the string-based parsing logic that uses string-to-number conversions similar to what you suggest. (Indeed, the Xpressive semantic actions involve the same sorts of conversions.) As you'll see, the code must parse whole numbers, fractions, mixed numbers, and real numbers. _____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

Dave Jenkins wrote:
Robert Stuart wrote:
Whoa! The performance just shot up to a mere 6X the custom code (from 175X). That might well be fast enough to keep the Xpressive version because of its readability!
Can you create thread-local match_results objects and reuse them? If so, I think you'll see parsing dwindle to almost nothing and your semantic actions will account for the bulk of the time spent.
Dave, thanks for spotting the obvious perf problem I missed. I can confirm that reusing the match results object largely eliminates the remaining performance problem. I tried 3 different scenarios: 1) The original code 2) Static const regexes 3) Static const regexes with reused match results objects I ran each config for 1000000 iterations and got roughly these numbers: 1) ~950 sec 2) ~45 sec 3) ~9 sec So reusing the match results object (3) results in a 5x speedup over just using static const regexes (2). That almost completely erases any performance advantage of the hand-crafted parsing code. I'll also point out this section of the docs: http://www.boost.org/doc/libs/1_39_0/doc/html/xpressive/user_s_guide.html#bo... Both of the above optimizations (reuse regexes and match_results objects) are recommended there. Thanks, -- Eric Niebler BoostPro Computing http://www.boostpro.com

Eric Niebler wrote:
Dave Jenkins wrote:
Robert Stewart wrote:
Whoa! The performance just shot up to a mere 6X the custom code (from 175X). That might well be fast enough to keep the Xpressive version because of its readability!
Can you create thread-local match_results objects and reuse them? If so, I think you'll see parsing dwindle to almost nothing and your semantic actions will account for the bulk of the time spent.
At Dave's suggestion, I tried that.
Dave, thanks for spotting the obvious perf problem I missed. I can confirm that reusing the match results object largely eliminates the remaining performance problem. I tried 3 different scenarios:
1) The original code 2) Static const regexes 3) Static const regexes with reused match results objects
I ran each config for 1000000 iterations and got roughly these numbers:
1) ~950 sec 2) ~45 sec 3) ~9 sec
So reusing the match results object (3) results in a 5x speedup over just using static const regexes (2). That almost completely erases any performance advantage of the hand-crafted parsing code.
Unfortunately, my results don't bear that out with the added overhead of locating the smatch via thread local storage. I see no difference between default constructing an smatch each time and reusing an instance from TLS.
I'll also point out this section of the docs: http://www.boost.org/doc/libs/1_39_0/doc/html/xpressive/user_s_guide.html#bo...
Both of the above optimizations (reuse regexes and match_results objects) are recommended there.
I read through some of those quickly upon an initial read through the documentation. I chose static regexes because I understood them to be faster. I did not reuse the match_results<> because they aren't thread safe. Dave's TLS suggestion hadn't occurred to me to reconcile the competing forces of reuse and no thread safety, of course. Upon rereading, I see that you note that reusing a static regex improves performance, but I'd forgotten it by the time I needed the information. May I suggest a "Performance Tuning" section that discusses such things apart from common pitfalls, etc. and stands out better in the TOC? I didn't expect to find such information in a "Tips and Tricks" section, and forgotten that I had seen it when it was wanted. _____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

"Stewart, Robert" <Robert.Stewart@sig.com> wrote in message news:DF2E67F3D097004694C8428C70A3FD69046843E622@msgbal516.ds.susq.com...
Unfortunately, my results don't bear that out with the added overhead of locating the smatch via thread local storage. I see no difference between default constructing an smatch each time and reusing an instance from TLS.
It's disappointing that locating a match_results object in thread-local storage takes approximately the same amount of time as default constructing a new one. I had hoped for better. Anyway, if you don't mind spending a little more time on this, I'd be interested in seeing a breakdown of the amount of time spent on (1) locating the match_results object in TLS, (2) parsing, and (3) semantic actions. You could determine (1) by changing match_results to static (even though this won't work in your actual case) and see what the time difference is. Then if you comment out the semantic actions, you can determine the split between parsing and semantic actions. In any case, thanks for supplying a interesting test case of Xpressive in a multi-threaded environment. Dave Jenkins

Dave Jenkins wrote:
"Stewart, Robert" <Robert.Stewart@sig.com> wrote in message news:DF2E67F3D097004694C8428C70A3FD69046843E622@msgbal516.ds.susq.com...
Unfortunately, my results don't bear that out with the added overhead of locating the smatch via thread local storage. I see no difference between default constructing an smatch each time and reusing an instance from TLS.
It's disappointing that locating a match_results object in thread-local storage takes approximately the same amount of time as default constructing a new one. I had hoped for better.
Your hopes were justified: I just noticed that while my TLS accessor returns a reference to the smatch, I saved it to a copy rather than a reference where I was using it. Correcting that mistake shows the Xpressive code taking around 1.5X the time used by the custom code. I've seen added more test cases and updated the custom code and must now correct some things in the Xpressive code to handle all of the same cases before I can do any more measurements.
Anyway, if you don't mind spending a little more time on this, I'd be interested in seeing a breakdown of the amount of time spent on (1) locating the match_results object in TLS, (2) parsing, and (3) semantic actions.
With TLS, a straightforward performance test required about 68s, whereas with a function local static, it required about 65s. Keeping the static smatch and omitting the semantic actions changed the time to about 40s.
From that, one can conclude that the matching takes about 40s, the semantic actions add 25s, and the TLS lookups add an additional 3s, in a loop of 100,000,000 iterations.
_____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

Stewart, Robert wrote:
Dave Jenkins wrote:
It's disappointing that locating a match_results object in thread-local storage takes approximately the same amount of time as default constructing a new one. I had hoped for better.
I just noticed that while my TLS accessor returns a reference to the smatch, I saved it to a copy rather than a reference where I was using it. Correcting that mistake shows the Xpressive code taking around 1.5X the time used by the custom code.
That's much better than 175X slower!
I've seen added more test cases and updated the custom code and must now correct some things in the Xpressive code to handle all of the same cases before I can do any more measurements.
At this point, most of the low hanging fruit is picked clean. If you are hungry for even more performance, you can eliminate the nested regex invocations by changing the definition of the regex objects from this: static const sregex zero = as_xpr('0'); ...to this: static const BOOST_PROTO_AUTO(zero, as_xpr('0')); Do this for all but the top-most regex (the one named "price" in your first example). You must also #include <boost/proto/proto_typeof.hpp>. When I do this, performance drops from 9.5s to 6.0s.
With TLS, a straightforward performance test required about 68s, whereas with a function local static, it required about 65s.
Keeping the static smatch and omitting the semantic actions changed the time to about 40s.
From that, one can conclude that the matching takes about 40s, the semantic actions add 25s, and the TLS lookups add an additional 3s, in a loop of 100,000,000 iterations.
Very interesting. Thanks for the measurements. -- Eric Niebler BoostPro Computing http://www.boostpro.com

Hi Robert, It would be good if you could submit your code as a case in optimizing expressive code. -Thorsten

Thorsten Ottosen wrote:
It would be good if you could submit your code as a case in optimizing expressive code.
Where and how would you have me do so? I can see developing a performance tuning example from it to put in the documentation. I don't know how much of that Eric has in mind already or if he is interested in such an addition (of his own doing or mine). _____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

"Stewart, Robert" <Robert.Stewart@sig.com> wrote in message news:DF2E67F3D097004694C8428C70A3FD6904685386F0@msgbal516.ds.susq.com...
Thorsten Ottosen wrote:
It would be good if you could submit your code as a case in optimizing expressive code.
Where and how would you have me do so? I can see developing a performance tuning example from it to put in the documentation. I don't know how much of that Eric has in mind already or if he is interested in such an addition (of his own doing or mine).
This has been an incredibly useful thread. Thanks to you all. As a potential user put off in the past by concerns over abstraction penalties with such libraries (even compile time libraries often fail to deliver in all but simple cases), I urge Eric to embrace such an example to illustrate just how powerful and maintainable a solution based on expressive can be.

Paul Baxter wrote:
"Stewart, Robert" wrote:
Thorsten Ottosen wrote:
It would be good if you could submit your code as a case in optimizing expressive code.
Where and how would you have me do so? I can see developing a performance tuning example from it to put in the documentation. I don't know how much of that Eric has in mind already or if he is interested in such an addition (of his own doing or mine).
This has been an incredibly useful thread. Thanks to you all.
As a potential user put off in the past by concerns over abstraction penalties with such libraries (even compile time libraries often fail to deliver in all but simple cases), I urge Eric to embrace such an example to illustrate just how powerful and maintainable a solution based on expressive can be.
I think this is a fine idea. All these tips and tricks are already described in a doc, but they are not described in depth from an end-user perspective. I think a performance tuning case study would make a valuable appendix. Robert, can you send me the latest version of your regex grammar and your hand-coded parser? I'll see what I can do. -- Eric Niebler BoostPro Computing http://www.boostpro.com

Eric Niebler wrote:
Paul Baxter wrote:
"Stewart, Robert" wrote:
Thorsten Ottosen wrote:
It would be good if you could submit your code as a case in optimizing expressive code.
Where and how would you have me do so? I can see developing a performance tuning example from it to put in the documentation. I don't know how much of that Eric has in mind already or if he is interested in such an addition (of his own doing or mine).
This has been an incredibly useful thread. Thanks to you all.
As a potential user put off in the past by concerns over abstraction penalties with such libraries (even compile time libraries often fail to deliver in all but simple cases), I urge Eric to embrace such an example to illustrate just how powerful and maintainable a solution based on expressive can be.
I think this is a fine idea. All these tips and tricks are already described in a doc, but they are not described in depth from an end-user perspective. I think a performance tuning case study would make a valuable appendix.
Robert, can you send me the latest version of your regex grammar and your hand-coded parser? I'll see what I can do.
Sure. I've attached both in one file, but with separate namespaces. My progression, as you can ascertain from retracing this thread, was from automatic sregexes and an automatic smatch to putting the sregexes in a namespace, which required creating placeholders, to putting the smatch into thread local storage, to using BOOST_PROTO_AUTO. That progression changed the performance of the Xpressive code from about 175X slower to less than 2X slower than the custom code. (I haven't measured against the final, tuned custom parsing code.) This code is used to parse whole numbers, real numbers, fractions, and mixed numbers from a string creating an integer from which the (possibly) fractional value can be recovered. example::lcm<T>::as() returns 160,000 as type T for that purpose because 160,000 is the least common multiple of all supported denominators. That aspect of this code should probably be removed in order to concentrate on the parsing, but was too well entrenched for me to remove. I have changed some names and namespaces from those in the original code to normalize it. I also have omitted exception throwing code and denominator validation logic. I haven't compiled since making those changes, so there may be some minor mistake in the attached code. The code references some things you'll not have access to, so allow me to explain them so you can make the necessary substitutions. - core::to_number(), uses TMP to select among several overloads of a conversion function which are wrappers around strtol(), strtod(), etc. Note that specifying a conversion radix is important to avoid octal parsing in the custom code. (Otherwise, the custom code would need to account for leading zeroes in other ways.) - core::numeric_cast is a function template that converts a string to a numeric type. It uses TMP to select among several overloads of a conversion function which are wrappers around core::to_number() and which log a debug-level message and throw std::bad_cast on failure. boost::lexical_cast should be a slower equivalent. - ThreadLocal, as you can well infer, manages memory referenced by thread local storage. (It mimics the interface of Rogue Wave's RWTThreadLocal.) FYI, direct_impl/direct_ exists because I couldn't distinguish its function call operator from one in to_price_impl/price_, without resorting to passing a dummy parameter. While it isn't strictly necessary, I chose to provide it because it avoids using double as an intermediate type. Notice that the rounding code assumes a positive value and that I manage the sign separately. The custom version was tuned via profiling, which explains the different treatment of the sign between parsing reals and fractions. Enjoy! _____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

On Fri, Jul 10, 2009 at 7:32 AM, Stewart, Robert<Robert.Stewart@sig.com> wrote:
Eric Niebler wrote:
Paul Baxter wrote:
"Stewart, Robert" wrote:
Thorsten Ottosen wrote:
It would be good if you could submit your code as a case in optimizing expressive code.
Where and how would you have me do so? I can see developing a performance tuning example from it to put in the documentation. I don't know how much of that Eric has in mind already or if he is interested in such an addition (of his own doing or mine).
This has been an incredibly useful thread. Thanks to you all.
As a potential user put off in the past by concerns over abstraction penalties with such libraries (even compile time libraries often fail to deliver in all but simple cases), I urge Eric to embrace such an example to illustrate just how powerful and maintainable a solution based on expressive can be.
I think this is a fine idea. All these tips and tricks are already described in a doc, but they are not described in depth from an end-user perspective. I think a performance tuning case study would make a valuable appendix.
Robert, can you send me the latest version of your regex grammar and your hand-coded parser? I'll see what I can do.
Sure. I've attached both in one file, but with separate namespaces. My progression, as you can ascertain from retracing this thread, was from automatic sregexes and an automatic smatch to putting the sregexes in a namespace, which required creating placeholders, to putting the smatch into thread local storage, to using BOOST_PROTO_AUTO. That progression changed the performance of the Xpressive code from about 175X slower to less than 2X slower than the custom code. (I haven't measured against the final, tuned custom parsing code.)
This code is used to parse whole numbers, real numbers, fractions, and mixed numbers from a string creating an integer from which the (possibly) fractional value can be recovered. example::lcm<T>::as() returns 160,000 as type T for that purpose because 160,000 is the least common multiple of all supported denominators. That aspect of this code should probably be removed in order to concentrate on the parsing, but was too well entrenched for me to remove.
I have changed some names and namespaces from those in the original code to normalize it. I also have omitted exception throwing code and denominator validation logic. I haven't compiled since making those changes, so there may be some minor mistake in the attached code.
The code references some things you'll not have access to, so allow me to explain them so you can make the necessary substitutions.
- core::to_number(), uses TMP to select among several overloads of a conversion function which are wrappers around strtol(), strtod(), etc. Note that specifying a conversion radix is important to avoid octal parsing in the custom code. (Otherwise, the custom code would need to account for leading zeroes in other ways.)
- core::numeric_cast is a function template that converts a string to a numeric type. It uses TMP to select among several overloads of a conversion function which are wrappers around core::to_number() and which log a debug-level message and throw std::bad_cast on failure. boost::lexical_cast should be a slower equivalent.
- ThreadLocal, as you can well infer, manages memory referenced by thread local storage. (It mimics the interface of Rogue Wave's RWTThreadLocal.)
FYI, direct_impl/direct_ exists because I couldn't distinguish its function call operator from one in to_price_impl/price_, without resorting to passing a dummy parameter. While it isn't strictly necessary, I chose to provide it because it avoids using double as an intermediate type.
Notice that the rounding code assumes a positive value and that I manage the sign separately.
The custom version was tuned via profiling, which explains the different treatment of the sign between parsing reals and fractions.
I find this quite interesting. I wonder if I might have the time tonight to make a Spirit2.1 version of this, the code would certainly be a great deal shorter. Just to make sure, from what I gathered looking at the code, you are trying to parse out a number from an ascii string that could potentially be an integer (64-bit, just digits, always base 10), a double (digits as the integer, then a period, then more digits parsed as the integer, OR a whole integer, then a space(s), followed by an int then a / then an int), it looks like that a real number can have a 'g' after it, but what is a g? I know what e's means, but g? I am also confused, it seems your types support int64 as well as double, but you only ever return an int64, why not a variant of both? Should I do this for Spirit2.1? Spirit2.1 naturally wants to use such things anyway so it is actually easier for me to do so, and the user would have a more accurate value too as they would get either an int64 or a double depending on what it parsed, I could also add in other representations like a struct of two int64's for a numerator/denominator as well for best accuracy. What would you prefer?

OvermindDL1 wrote:
I find this quite interesting. I wonder if I might have the time tonight to make a Spirit2.1 version of this, the code would certainly be a great deal shorter. Just to make sure, from what I gathered looking at the code, you are trying to parse out a number from an ascii string that could potentially be an integer (64-bit, just digits, always base 10), a double (digits as the integer, then a period, then more digits parsed as the integer, OR a whole integer, then a space(s), followed by an int then a / then an int), it looks like that a real number can have a 'g' after it, but what is a g? I know what e's means, but g? I am also confused, it seems your types support int64 as well as double, but you only ever return an int64, why not a variant of both? Should I do this for Spirit2.1? Spirit2.1 naturally wants to use such things anyway so it is actually easier for me to do so, and the user would have a more accurate value too as they would get either an int64 or a double depending on what it parsed, I could also add in other representations like a struct of two int64's for a numerator/denominator as well for best accuracy. What would you prefer? _______________________________________________
I have considered doing this myself on and off. I'm still learning Spirit 2.1 and if you were to throw this together it would be a great example of approaching the problem three different ways. I am looking forward to seeing your effort. Best Regards - michael -- ---------------------------------- Michael Caisse Object Modeling Designs www.objectmodelingdesigns.com

On Mon, Jul 13, 2009 at 10:22 PM, Michael Caisse<boost@objectmodelingdesigns.com> wrote:
OvermindDL1 wrote:
I find this quite interesting. I wonder if I might have the time tonight to make a Spirit2.1 version of this, the code would certainly be a great deal shorter. Just to make sure, from what I gathered looking at the code, you are trying to parse out a number from an ascii string that could potentially be an integer (64-bit, just digits, always base 10), a double (digits as the integer, then a period, then more digits parsed as the integer, OR a whole integer, then a space(s), followed by an int then a / then an int), it looks like that a real number can have a 'g' after it, but what is a g? I know what e's means, but g? I am also confused, it seems your types support int64 as well as double, but you only ever return an int64, why not a variant of both? Should I do this for Spirit2.1? Spirit2.1 naturally wants to use such things anyway so it is actually easier for me to do so, and the user would have a more accurate value too as they would get either an int64 or a double depending on what it parsed, I could also add in other representations like a struct of two int64's for a numerator/denominator as well for best accuracy. What would you prefer? _______________________________________________
I have considered doing this myself on and off. I'm still learning Spirit 2.1 and if you were to throw this together it would be a great example of approaching the problem three different ways.
I am looking forward to seeing your effort.
Spirit2.1 is by far the most easy Spirit to date, as well as the fastest, beats out many hand-written and tuned parsers as well. Should not be hard to do, and I think I would prefer to return a variant of all possible types as I see no point cutting out information, let the user decide what info they want, unless someone tells me they only want anything/everything as an int64, then I can do it that way too. But still, what would the 'g' in a number like 2.4g5 do, I know what e does, but g?

But still, what would the 'g' in a number like 2.4g5 do, I know what e does, but g? < Perhaps this follows the notation for a double much like used in printf formatting? http://en.wikipedia.org/wiki/Printf 3/4 of the way down giving an imprecise but general description Looking forward to seeing the threeway comparison - really helpful for users particularly as each trade maintainability, learning curve and speed in different ways.

Okay, I made a Spirit version and I am wanting to compare times, however no one has posted any compilable code here yet, nor test cases, nor anything I can use, so I am just guessing that it is correct since I have no data to run through it. Since none of the above code actually compiles as-is, and I do not feel like dealing with incomplete examples, here are the times on my computer using the code below. When parsing "42", this would be an example of the fastest case: char const *s1 = "42", *e1 = s1 + std::strlen(s1); boost::long_long_type result; size_t counter = 1000000; while(counter--) parse_price_spirit(s1, e1, result); This executed in no real measurable time... might have been optimized out, let me check the assembly, nope, very much not optimized out, the loop is run, the function is not inlined, and it is indeed called 1000000 times. Okay, running this with 10000000 (7 zero's, 10 million loops, 9 million extra then the above tests) instead takes about ~1.5 seconds. Now running the test with "42.5", which should be the *slowest* possible case for the code I wrote, with 1000000 (6 zero's, one million) it took still no noticeable time. With 10000000 (7 zero's, 10 million) iterations it took roughly about ~1.5 seconds again. I would really love to have the same test cases and scaffolds that everyone else is using so I can do some real timings though. And yes, my parser, for a string it returns -> value: "42" -> 6720000 (42*160000) "42 1/2" -> 6800000 (42*16000 + (1*16000)/2) "42.5" -> 6800000 (42*16000 + floor(160000.0*0.5 + 0.5)) "4.2e1" -> 6720000 (42*160000) This is comparing with someone elses above timings, have no clue of his compile type, hardware, anything, and he did not post his code, but these are the times that he posted:
1) The original code 2) Static const regexes 3) Static const regexes with reused match results objects
I ran each config for 1000000 iterations and got roughly these numbers:
1) ~950 sec 2) ~45 sec 3) ~9 sec
All of the above I did was using MSVC 2k5 in release mode in Windows XP on an old AMD Athlon 1.8ghz Opteron processor. If anyone can give me the above code (his original version and the xpressive version) as compilable files so I do not need to deal with anything, then I can run a proper, full, and millisecond detailed timings. Based on my above timings, either my ancient 1.8ghz computer is rather massively faster then his computer, or Spirit2.1 is much faster then both xpressive and the original code (which someone else said above, the optimized xpressive takes about 20% or so longer I think?). The code, I whipped it up quickly, not pretty, just getting it functional since the guidelines are not very well set, do note, this is not how I normally make my Spirit parsers, but if someone gives me the source of the other things, then I will do this one properly once I confirm that this parses what needs to be parsed (to compile this, make sure you are running Boost Trunk!): #include <boost/tr1/cmath.hpp> #include <boost/config/warning_disable.hpp> #include <boost/spirit/include/qi.hpp> #include <boost/spirit/include/phoenix_core.hpp> #include <boost/spirit/include/phoenix_operator.hpp> BOOST_STATIC_ASSERT(sizeof(boost::long_long_type) == 8); boost::long_long_type tmpResult; inline void dotNumber(const double a1) { tmpResult += static_cast<boost::long_long_type>(std::floor(160000.0*a1 + 0.5)); } template <typename Iterator> bool parse_price_spirit(Iterator first, Iterator last, boost::long_long_type &c) { using boost::spirit::qi::double_; using boost::spirit::qi::_1; using boost::spirit::qi::_2; using boost::spirit::qi::phrase_parse; using boost::spirit::qi::lexeme; using boost::spirit::qi::lit; using boost::spirit::ascii::space; using boost::spirit::ascii::blank; using boost::phoenix::ref; using boost::spirit::long_long; using boost::long_long_type; bool r = phrase_parse(first, last, // Begin grammar // I did not put this in a grammar class because I am being // lazy, plenty of examples of that anyway, and no, it is not // faster here, it is the same speed whether inline here or in // a grammar class. ( (long_long[ref(tmpResult)=(_1*160000)] >> !lit('.') >> -(long_long >> '/' >> long_long)[ref(tmpResult)+=160000*_1/_2]) | double_[&dotNumber] ) // End grammar ,blank); if (!r || first != last) // fail if we did not get a full match return false; c = tmpResult; return r; }

Paul Baxter wrote:
But still, what would the 'g' in a number like 2.4g5 do, I know what e does, but g? < Perhaps this follows the notation for a double much like used in printf formatting?
http://en.wikipedia.org/wiki/Printf 3/4 of the way down giving an imprecise but general description
Are you referring to the "Type" table that lists "g" and "G" format specifiers? That's what I latched onto much too readily when scanning the manpage and later realized when working on the custom parsing code but didn't correct in the Xpressive parser. Those are not used in the output; they are used to indicate the desired formatting. _____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

On Wed, Jul 15, 2009 at 6:54 AM, Stewart, Robert<Robert.Stewart@sig.com> wrote:
Paul Baxter wrote:
But still, what would the 'g' in a number like 2.4g5 do, I know what e does, but g? < Perhaps this follows the notation for a double much like used in printf formatting?
http://en.wikipedia.org/wiki/Printf 3/4 of the way down giving an imprecise but general description
Are you referring to the "Type" table that lists "g" and "G" format specifiers? That's what I latched onto much too readily when scanning the manpage and later realized when working on the custom parsing code but didn't correct in the Xpressive parser. Those are not used in the output; they are used to indicate the desired formatting.
I gathered that, hence my code only supports e/E, no g/G. Look on my previous email.

OvermindDL1 wrote:
Eric Niebler wrote:
Paul Baxter wrote:
Thorsten Ottosen wrote:
It would be good if you could submit your code as a case in optimizing expressive code.
[snip] This has been an incredibly useful thread. Thanks to you all.
As a potential user put off in the past by concerns over abstraction penalties with such libraries (even compile time libraries often fail to deliver in all but simple cases), I urge Eric to embrace such an example to illustrate just how powerful and maintainable a solution based on expressive can be.
I think this is a fine idea. All these tips and tricks are already described in a doc, but they are not described in depth from an end-user perspective. I think a performance tuning case study would make a valuable appendix.
Robert, can you send me the latest version of your regex grammar and your hand-coded parser? I'll see what I can do.
Sure. I've attached both in one file, but with separate namespaces. [snip] This code is used to parse whole numbers, real numbers, fractions, and mixed numbers from a string creating an integer from which the (possibly) fractional value can be recovered. example::lcm<T>::as() returns 160,000 as type T for that purpose because 160,000 is the least common multiple of all supported denominators. That aspect of this code should probably be removed in order to concentrate on the
On Fri, Jul 10, 2009 at 7:32 AM, Stewart, Robert<Robert.Stewart@sig.com> wrote: parsing, but was too well entrenched for me to remove. [snip] I find this quite interesting. I wonder if I might have the time tonight to make a Spirit2.1 version of this, the code would certainly be a great deal shorter.
That would be great. I am interested to know if I was doing anything wrong or was just trying more than Spirit v2 was capable of doing in Boost 1.37, which is what I needed to use in this case.
Just to make sure, from what I gathered looking at the code, you are trying to parse out a number from an ascii string that could potentially be an integer (64-bit, just digits, always base 10), a double (digits as the integer, then a period, then more digits parsed as the integer, OR a whole integer, then a space(s), followed by an int then a / then an int), it looks like that a real number can have a 'g' after it, but what is a g? I know what e's means, but g?
As described above, I'm looking for whole numbers, reals, fractions, and mixed numbers. The whole numbers are 64b integers. The reals are any of various formats such as you might get from printf(). The "g" and "G" were from a rapid, overzealous scan of the manpage; I forgot to remove them when I realized they weren't appropriate (they are not supported in the custom version, you'll note). The fractions are expected to have a positive denominator, and a possibly signed numerator. The mixed numbers are a whitespace delimited combination of a whole number and a fraction, except the numerator must be non-negative in that case. As you surmised, the input is assumed to be an ASCII string.
I am also confused, it seems your types support int64 as well as double, but you only ever return an int64, why not a variant of both?
The purpose of this code was to produce a 64b integer because the result represents a (possibly fractional) dollar amount: a price. The 160,000 least common multiple provides rounding for all supported denominators so all supported amounts can be represented exactly in the 64b integer type (the range is, of course, reduced in doing so).
Should I do this for Spirit2.1? Spirit2.1 naturally wants to use such things anyway so it is actually easier for me to do so, and the user would have a more accurate value too as they would get either an int64 or a double depending on what it parsed, I could also add in other representations like a struct of two int64's for a numerator/denominator as well for best accuracy. What would you prefer?
In order to compare your Spirit code against the two versions I supplied, your code would need to behave the same. Otherwise, you must alter the two versions I supplied to match what you choose to provide in your Spirit version. The purpose of this exercise is to compare the code and performance, so they must all perform the same task. _____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

From: "Stewart, Robert" <Robert.Stewart@sig.com>
Your hopes were justified: I just noticed that while my TLS accessor returns a reference to the smatch, I saved it to a copy rather than a reference where I was using it. Correcting that mistake shows the Xpressive code taking around 1.5X the time used by the custom code.
I've seen added more test cases and updated the custom code and must now correct some things in the Xpressive code to handle all of the same cases before I can do any more measurements.
Thanks for doing this work Rob. It's been a really fruitful exercise. I had one other question on performance improvement, mostly for Eric. Could you eliminate/minimize the need for match_results when semantic actions are capturing the results? In Rob's code, he's not even looking at the match_results because his semantic actions handle the data. Can you optionally turn off the match_results capture in that case to save space and time? Regards, Dave Jenkins P.S. If I'm slow to respond, it's because I'm on vacation and internet access is spotty.

Stewart, Robert wrote:
Dave Jenkins wrote:
"Stewart, Robert" wrote:
I was experimenting with Xpressive to see how it would compare with some custom, string-based numeric input parsing. The Xpressive code is over 175X slower than the custom code.
Each call to parse() is constructing the sregex variables. If you make them static or move them out of the loop, it should help a lot.
Thanks, but see my reply to Eric's. While that would help with the benchmark performance, the nature of the function I'm testing means I can't do that otherwise.
Really what you're saying is: "I can't/don't want to use xpressive in the ways that are *documented* to make it perform well. Why is it slow?" You already have your answer. -- Eric Niebler BoostPro Computing http://www.boostpro.com

Eric Niebler wrote:
Really what you're saying is: "I can't/don't want to use xpressive in the ways that are *documented* to make it perform well. Why is it slow?" You already have your answer.
So be it. I was just hoping I had done something silly and could rearrange things to improve the performance. Thanks for your attention. _____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.
participants (9)
-
Dave Jenkins
-
Eric Niebler
-
Kim Scheibel
-
Matthew Chambers
-
Michael Caisse
-
OvermindDL1
-
Paul Baxter
-
Stewart, Robert
-
Thorsten Ottosen