Δi’m a developer at optivercs6771/16s2/lectures/optiver.pdf · Δi’m a developer at optiver...
TRANSCRIPT
-
’
-
Δ 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