Δi’m a developer at optivercs6771/16s2/lectures/optiver.pdf · Δi’m a developer at optiver...

55

Upload: others

Post on 28-Jan-2021

11 views

Category:

Documents


0 download

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