Upload
others
View
11
Download
0
Embed Size (px)
Citation preview
’
Δ I’m a developer at Optiver
ΔPrior to Optiver I was an analyst and developer in the financial services industry
ΔPhD in Computer Science (2003)
ΔOptiver is a market maker
ΔPerformance and reliability are critical
ΔOur core trading applications are written in C++
ΔWe hire graduates and offer internships
ΔOpenings in February 2017
ΔKeep an eye on www.optiver.com/sydney/
ΔC++11 and C++14 at Optiver
ΔC++ Performance Tips
ΔHow Optiver does Testing and Building
ΔC++ hadn’t even been standardised
ΔBoost didn’t yet exist
ΔMemory was manually (mis)managed
ΔHP had only recently made their implementation of STL freely available
ΔMost of Optiver’s code is still C++03
ΔSpecifiers
– auto and constexpr
Δ static_assert
Δ Lambdas
ΔStandard Library Features
– emplace for performance
ΔWhat is wrong with this picture? Streaming::Writer* writer; // Some code here writer = reinterpret_cast(context);
ΔWe typically use “auto” where the type is clear from the initializer auto writer = reinterpret_cast(context);
ΔPrevents uninitialized variable errors
ΔDRY vs WET
ΔWe avoid using “auto” where the type is unclear, or where the type is easily written.
auto obscure = SomeObscureFunction(data);
int a = 4;
auto b = a; // just use ‘int b’
Δ But auto can make code more readable, especially in for loops:
std::vector mVec; // C++03: for (std::vector::iterator iter = mVec.begin(), iter != mVec.end(), ++iter) { /* code */ } // C++11: for (const auto& myPair : iter) { /* code */}
ΔConsider this enum class enum class MessageId : unit32_t { A123 = 0x00007B41, B456 = 0x0001C842, … }
Δ Better: constexpr uint32_t GenID(char type_c, uint16_t number_n) { return uint32_t(type_c) | (uint32_t(number_n)
ΔUsing static_assert to test a constexpr function
static_assert(GenID(‘A’, 123) == 0x00007B41, “Check GenID”);
ΔUsing static_assert to check types class SomeClass { … char mDate[DATE_SIZE]; … } … static_assert( sizeof(mDate) == sizeof(thirdparty->date_s), “check your dates” ); memcpy(mDate, thirdparty->date_s, DATE_SIZE);
ΔOn connection to an exchange we must
– Query for updated instrument definitions,
– Query for up-to-date prices, etc. etc.
– Indicate application readiness (another query)
Δ There are lots of query methods like this: bool Session::A123RequestInstruments(Connection* connection) { char buffer[MAX_SIZE]; uint32_t len = sizeof(buffer); Message query{‘A’, 123}; if (!connection->execute(query, buffer, len)) { return false; } if (len == 0) { return false; } // Do something with the contents of buffer… }
Δ Use a templated query with a lambda: template inline bool Query(Connection* connection, Lambda lambda) { char buffer[MAX_SIZE]; uint32_t len = sizeof(buffer); Message query{type_c, n}; if (!connection->execute(query, buffer, len)) { return false; } return lambda(buffer, len); }
ΔWe can use the template like so: bool Session::A123RequestInstruments(Connection* connection) { return Query( connection, [&](const char* buffer, const uint32_t len) -> bool { if (len == 0) { return false; } return ProcessItems(connection, buffer, len); }); }
ΔShould we code in assembler?
Δ Just do the basics right – KISS
ΔUse the standard library! (DRY)
ΔMake sure you’re using the right algorithm Δ The optimization loop:
write_some_code(); test_the_code(); while (!fast_enough()) { try { profile_the_code(); optimize_the_code(); test_the_code(); } catch (…) { fix_the_code(); test_the_code(); } }
Δ Avoid copying things – Pass by (const) ref instead of value
– Take advantage RVO, copy elision and inlining
– Use emplace on containers, std::move
Δ Avoid the heap wherever possible – If necessary allocate at startup, not in critical code
Δ Use constexpr to offload computation to compiler
Δ Finalize classes
Δ Pre-increment may be faster than post
Δ push_back vs C++11 emplace_back std::deque mRequestQueue; // Compiler *may* elide the copy, but there is no guarantee. mRequestQueue.push_back(QueueItem{request_tag, key}); // QueueItem constructed in-place, no copying/moving needed. mRequestQueue.emplace_back(request_tag, key);
ΔShort circuiting bool b = false; bool SomeLongFunction(…) { … } // SomeLongFunction will always be called here // It’s result is irrelevant because b is false. if (SomeLongFunction(…) && b) {} // Here, b is false and the code doesn’t execute // SomeLongFunction() at all. if (b && SomeLongFunction(…)) {}
ΔCache friendly code
– Fast memory is expensive
– Therefore processors use small amounts
– Data from slow, but cheap, memory is cached
in fast memory
– We want to take advantage of this
ΔTemporal locality:
– If a piece of data is accessed it is likely to be
used again soon
ΔSpatial locality:
– If a piece of data is accessed, other “nearby”
data items are likely to be used soon
ΔTaking advantage of the cache
– Choose containers wisely (e.g. vector vs list)
– Know how data is represented (e.g. two-
dimensional arrays)
– Avoid unpredictable branching
ΔConsider the following loop long arr_sum(long arr[ARR_SIZE][ARR_SIZE]) { long sum = 0; for (int i = 0; i < ARR_SIZE; ++i) { for (int j = 0; j < ARR_SIZE; ++j) { sum += arr[i][j]; } } return sum; }
Δ In memory the array looks like this:
[0][0] [0][1] [0][2] … [1][0] [1][1] [1][2] … [2][0] [2][1] …
Time CPU Iterations
Using arr[i][j] Mean 418,268 ns 418,103 ns 1380
Standard Dev. 10,483 ns 10,425 ns 0
Using arr[j][i] Mean 1,807,484 ns 1,807,291 ns 365
Standard Dev. 8,966 ns 8,870 ns 0
ΔFunctional correctness
– Does my change work?
– Have I broken anything else?
– To prevent others breaking “my” code
Δ Integration and Release
– Does my change work with other systems?
– Will my configuration work in production?
– To prove quality to exchange, auditors, etc.
ΔDeveloper productivity
– Is my code well designed?
– Is it extendable and maintainable?
– Can others understand my code and APIs?
– Can I upgrade external dependencies?
To avoid looking stpuid!
Unit tests
Application tests
Integration
tests
Staging
Unit tests
Application tests
Integration
tests
Staging
Individual classes/methods with no external dependencies.
Unit tests
Application tests
Integration
tests
Staging
Tests a single application from the outside as a black box. Uses an unmodified binary but stubs dependencies.
Unit tests
Application tests
Integration
tests
Staging
Tests integration between N Optiver applications or external systems. Will run multiple application processes.
Unit tests
Application tests
Integration
tests
Staging
Full production-like environment, isolated from real exchange.
Fuzzier assertions
Harder to maintain
Slower to run
∴ Less tests
Unit tests
Application tests
Integration
tests
Staging
Fuzzier assertions
Harder to maintain
Slower to run
∴ Less tests
Unit tests
Application tests
Integration
tests
Staging
1000s
100s
10s
1s
ΔA unit test tests
– The smallest possible piece of behaviour
– Isolated code
– All permutations and edge cases
ΔA unit test also
– Makes it obvious what is broken and why
– Runs quickly
ΔProvide confidence
ΔDrive good software design
ΔSupport refactoring
Δ Improves developer productivity
#include
using namespace testing;
TEST(Multiplication, MixedNumbersProduceNegativeProduct)
{
Calculator calculator;
EXPECT_LT(calculator.multiply(2, -3), 0);
}
TEST(Multiplication, NegativeNumbersProducePositiveProduct)
{
Calculator calculator;
EXPECT_GT(calculator.multiply(-2, -3), 0);
}
int main(int argc, char** argv)
{
::testing::GTEST_FLAG(throw_on_failure) = false;
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
#include
using namespace testing;
TEST(Multiplication, MixedNumbersProduceNegativeProduct)
{
Calculator calculator;
EXPECT_THAT(calculator.multiply(2, -3), IsNegative());
}
TEST(Multiplication, NegativeNumbersProducePositiveProduct)
{
Calculator calculator;
EXPECT_THAT(calculator.multiply(-2, -3), Not(IsNegative()));
}
int main(int argc, char** argv)
{
::testing::GTEST_FLAG(throw_on_failure) = false;
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
ΔAn application test tests
– That an application behaves correctly
– With respect to a requirement specification
– Without knowledge of its internal structures
ΔApplication tests may also
– Test performance
– Test security
– Etc.
Application under test
Server Client TCP TCP
Unit Test
Unit Test
Unit Test
Integration Test
Application Test
stub
stub
Unit tests
Application tests
Integration
tests
Staging
ΔWe use continuous integration extensively
ΔOn each commit
– Software is built
– Unit tests are executed
– Application tests are executed
ΔThe release process is integrated