Showing posts with label cppcon_2016. Show all posts
Showing posts with label cppcon_2016. Show all posts

Oct 4, 2021

[CppCon 2016] The speed of concurrency - note

Reference:
The speed of concurrency:
https://www.youtube.com/watch?v=9hJkWwHDDxs
The Art of Multiprocessor Programming


Easy to say but performance hit in reality (trade-offs):
Don’t communicate by sharing memory, share memory by communicating. (Golang)


Rule of performance

  • Never guess about performance!
  • Measurements must be relevant

Lock-free algorithms do not always provide better performance,
due to it might fall into forever wait, e.g CAS.


Wait-free programs

Each thread will complete its task in finite number of steps (finite amount of time) no matter what other threads are doing.
At all times, all threads make progress toward the final result “Step” is not the same as “time”

Lock-free programs

programs without locks (duh!); at least one thread makes progress no matter what other threads are doing.
Lock-based programs are not guaranteed to make progress toward the result in all cases.
In particular, consider what would happen if the thread holding the lock is waiting on another lock? That was held by the first thread?

Wait-free (or lock-free) does not mean “data-sharing-free”

Jan 5, 2019

[Design][Software Engineering][C++] Using/Design type effectively - Ben Deane@Blizzard

"On the whole, I'm inclined to say that when in doubt, make a new type."
                                                                 – Martin Fowler, When to Make a Type
"Don't set a flag; set the data."
                                                                 – Leo Brodie, Thinking Forth



Considering this talk provides an essential abstraction design for Type.
Yet Golang's multiple variables return programming paradigm should design as follows the concept of considering them as a whole into sum type.


Types as sets of values:

Type, like math's function, defines value domain.
If types' value domain are same, we could consider they are equivalent.
(But not 'equality')
Algebraically, a type is the number of values that inhabit it.

e.g.
How many values?
bool;  // 2, true, false
char;  // 256
void;  // 0
struct Foo {};  // 1
enum FireSwampDangers : int8_t {   // 3
    FLAME_SPURTS,
    LIGHTNING_SAND,
    ROUSES
};

template <typename T> // as many values as T
struct Foo {
    T m_t;
};


Aggregating Types:

When two types are "concatenated" into one compound type,
we multiply the # of inhabitants of the components.
This kind of compounding gives us a product type.
e.g
How many values?
std::pair<char, bool>;  // 256 * 2

struct Foo {  // 256 * 2
    char a;
    bool b;
};

std::tuple<bool, bool, bool>;  // 2 * 2 * 2 = 8

template <typename T, typename U>  // (# of values in T) * (# of values in U)
struct Foo {
    T m_t;
    U m_u;
};


Alternating Types:

When two types are "alternated" into one compound type,
we add the # of inhabitants of the components.
This kind of compounding gives us a sum type.
e.g.
How many values?
std::optional<char>;  // 256 + 1
std::variant<char, bool>;  // 256 + 2

template <typename T, typename U>  // (# of values in T) + (# of values in U)
struct Foo {
    std::variant<T, U>;
}


Function Types:

The number of values of a function is the number of different ways we can draw arrows between the inputs and the outputs.
When we have a function from A to B,
we raise the # of inhabitants of B to the power of the # of inhabitants of A.
Curring, foundation of Lambda Calculus : https://en.wikipedia.org/wiki/Currying
e.g.
How many values?
bool f(bool);  // 2^2 = 4
char f(bool);  // 256 ^ 2

enum class Foo
{
    BAR,
    BAZ,
    QUUX
};
char f(Foo);   // 256 ^ 3

template <class T, class U>  // U ^ T
U f(T);


The above definition gives us how to present equivalent type:

e.g.
Equivalence:
template <typename T>
struct Foo {
    std::variant<T, T> m_v;
};
template <typename T>
struct Bar {
    T m_t;
    bool m_b;
};


Algebraic Datatypes:

  • the ability to reason about equality of types
  • to find equivalent formulations
    • more natural
    • more easily understood
    • more efficient
  • to identify mismatches between state spaces and the types used to
    implement them
  • to eliminate illegal states by making them inexpressible


Making illegal states unrepresentable:

std::variant is a game changer because it allows us to (more) properly express types,
so that (more) illegal states are un-representable.

Let's using sum types (variant, optional) as well as product types (structs):
e.g
Old way:
enum class ConnectionState {
    DISCONNECTED,
    CONNECTING,
    CONNECTED,
    CONNECTION_INTERRUPTED
};

struct Connection {
    ConnectionState m_connectionState;
    std::string m_serverAddress;
    ConnectionId m_id;
    std::chrono::system_clock::time_point m_connectedTime;
    std::chrono::milliseconds m_lastPingTime;
    Timer m_reconnectTimer;
};

New way:
struct Connection {
    std::string m_serverAddress;

    struct Disconnected {};
    struct Connecting {};
    struct Connected {
        ConnectionId m_id;
        std::chrono::system_clock::time_point m_connectedTime;
        std::optional<std::chrono::milliseconds> m_lastPingTime;};

    struct ConnectionInterrupted {
        std::chrono::system_clock::time_point m_disconnectedTime;
        Timer m_reconnectTimer;};

    std::variant<Disconnected,
      Connecting,
      Connected,
      ConnectionInterrupted> m_connection;
};

Old way:
class Friend {
std::string m_alias;
bool m_aliasPopulated;
...
};

New way:
class Friend {
std::optional<std::string> m_alias;
...
};


Thus, we have a new design pattern for modern C++:

  • Command
  • Composite
  • State
  • Interpreter
The addition of sum types to C++ offers an alternative formulation for some
design patterns.
State machines and expressions are naturally modeled with sum types.


Designing with types:

std::variant and std::optional are valuable tools that allow us to model
the state of our business logic more accurately.
When you match the types to the domain accurately, certain categories of
tests just disappear. (Consider Data Oriented Design)

Fitting types to their function more accurately makes code easier to
understand and removes pitfalls.
The bigger the code-base and the more vital the functionality, the more
value there is in correct representation with types.


Using types to constrain behavior:

"Phantom types" is one technique that helps us to model the behavior of
our business logic in the type system. Illegal behavior becomes a type error.
e.g.
Old ways:
std::string GetFormData();
std::string SanitizeFormData(const std::string&);
void ExecuteQuery(const std::string&);

template <typename T>
struct FormData {
    explicit FormData(const string& input) : m_input(input) {}
    std::string m_input;
};
struct sanitized {};
struct unsanitized {};

New ways:
FormData<unsanitized> GetFormData();

std::optional<FormData<sanitized>>
SanitizeFormData(const FormData<unsanitized>&);

void ExecuteQuery(const FormData<sanitized>&);


Total functions:

  • A total function is a function that is defined for all inputs in its domain.
  • Writing total functions with well-typed signatures can tell us a lot about functionality.
  • Using types appropriately makes interfaces unsurprising, safer to use and harder to misuse.
  • Total functions make more test categories vanish.
  • Effectively using types can reduce test code.


Name this function:

(having lambda calculus knowledge is essential to understand what's going on next)
template <typename T>
T f(T);
// identity
// int f(int);

template <typename T, typename U>
T f(pair<T, U>);
// first

template <typename T>
T f(bool, T, T);
// select

template <typename T, typename U>
U f(function<U(T)>, T);
// apply or call

template <typename T>
vector<T> f(vector<T>);
// reverse, shuffle, ...

template <typename T>
optional<T> f(vector<T>);

template <typename T, typename U>
vector<U> f(function<U(T)>, vector<T>);
// transform

template <typename T>
vector<T> f(function<bool(T)>, vector<T>);
// remove_if, partition, ...

template <typename K, typename V>
optional<V> f(map<K, V>, K);
// lookup

template <typename T>
T f(vector<T>);
// Not possible! It's a partial function - the vector might be empty.
// T& vector<T>::front();

template <typename T>
T f(optional<T>);
// Not possible!

template <typename K, typename V>
V f(map<K, V>, K);
// Not possible! (The key might not be in the map.)
// V& map<K, V>::operator[](const K&);


Take away:

  • Make illegal states unrepresentable
  • Use std::variant and std::optional for formulations that are
    • more natural
    • fit the business logic state better
  • Use phantom types for safety
    • Make illegal behavior a compile error
  • Write total functions
    • Unsurprising behavior
    • Easy to use, hard to misuse


Reference:

[golang][c++] padding https://vsdmars.blogspot.com/2018/09/golangc-padding.html

Nov 14, 2018

[Cppcon 2016] High Performance Code 201: Hybrid Data Structures - Chandler Carruth


std::vector's problem, no SSO(small size optimization)
Why? Because STD says when move a vector, it's iterator can't be invalidated.
That implies std::vector's iterator as pointer points to heap memory.
However, SSO vector when moves, it copies, and invalidates the iterators.

Domain specific data structure has it's purpose of efficiency.
(Less corner cases, easier to design.)

Besides domain specific data structure,
why not just using customized allocator with std::vector?

Thus SSO for vector becomes:
template<typename T, int N>
using SmallVector = std::vector<T, short_alloc<T, N>>;

void fun(){
    SmallVector<int, 4>::allocator_type::arena_type a;
    SmallVector<int> v{a};
}

But...
It doesn't work well with interface boundary,
i.e  due to std::vector has allocator as type argument.
void jump(SmallVector<int, 4> &v);

void fun(){
    // Taking short_alloc<int, 8>
    SmallVector<int, 8>::allocator_type::arena_type a;
    SmallVector<int> v{a};
    jump(v);  // doesn't work... callee taking short_alloc<int, 4>
}

Another issue, callee's return type could reference to memory on callee's stack..
i.e
SmallVector<int, 4> fun(){
    SmallVector<int, 4>::allocator_type::arena_type a;
    SmallVector<int> v{a};
    return v; // BAD
}

All of all, the SmallVector loses it's value semantics.
Which is IMPORTANT.

With Domain Specific Type, these issues solved.
SmallVector type in Clang:
template <typename T, unsigned N>
class SmallVector : public SmallVectorImpl<T> {
  typedef typename SmallVectorImpl<T>::U U; // expected-error {{no type named 'U' in 'SmallVectorImpl<CallSite>'}}
  enum {

    MinUs = (static_cast<unsigned int>(sizeof(T))*N + // expected-error {{invalid application of 'sizeof' to an incomplete type 'CallSite'}}
             static_cast<unsigned int>(sizeof(U)) - 1) /
            static_cast<unsigned int>(sizeof(U)),
    NumInlineEltsElts = MinUs
  };
  U InlineElts[NumInlineEltsElts];
public:
  SmallVector() : SmallVectorImpl<T>(NumInlineEltsElts) {
  }

};

Small-size optimization is best when the values are small.
(in C++, copy by value is the default mechanism, although not being well/widely known...)

Design:
  1. Give large objects address identity.
    i.e Use object's memory address as identity avoids object's content equality test.
  2. SmallVector<std::unique_ptr<BigObject>, 4> Objects;
    
    BumpPtrAllocator impl, purpose, make BigObject as compact as possible on heap memory.
    // FAST
    class BumpPtrAllocator {
        constexpr int SlabSize = 4096;
        SmallVector<void *, 4> Slabs;
        void *CurPtr, *End;
    
    public:
        void *Allocate(int Size) P
            if (Size >= (End - CurPtr)) {
                CurPtr = malloc(SlabSize);
                End = CurPtr + SlabSize;
                Slabs.push_back(CurPtr);
            }
    
            void *Ptr = CurPtr;
            CurPtr += Size;
            return Ptr;
        }
        // ...
    };
    
  3. If pointers are too large, use an index.
  4. Aggressively pack the bits.
    PointerIntPair:
    http://llvm.org/doxygen/classllvm_1_1PointerIntPair.html
    PointerEmbeddedInt:
    http://llvm.org/doxygen/classllvm_1_1PointerEmbeddedInt.html
    TinyPtrVector:
    http://llvm.org/doxygen/classllvm_1_1TinyPtrVector.html
    Thus, we have SmallMutiMap:
  5. template<typename KeyT, typename ValueT>
    using SmallMultiMap = SmallDenseMap<KeyT, TinyPtrVector<ValueT>>;
    
  6. Use bitfields everywhere.
  7. Sometimes, we need an ordering.
    i.e comparison operator.
  8. Where possible, sort the vector.
    i.e gives you a linear BST, works well with CPU pre-fetching.
  9. What if there's no intrinsic ordering?
    We have SmallSetVector:
    (Has a set, has a vector, when insert, check data in set, and insert into vector.)
    http://llvm.org/doxygen/classllvm_1_1SmallSetVector.html

May 30, 2018

[C++][Language][Design] function as first class citizen in C++

Reference:
https://yinwang0.wordpress.com/2013/12/24/oop/
Prof. Peter Norvig on design pattern [1998]

https://github.com/boostcon/cppnow_presentations_2015/blob/master/files/functions_want_to_be_free.pdf



  • Focus on the goal, not the syntax
    • maximize encapsulation
    • Not to write member functions
    • Note: Or, if it's pure, consider writing it as callable object, which introduce a layer of 'namespace' and take advantage of EBO(Empty base class optimizations), [no_unique_address], refer to: [C++][accu2018] Tricks library implementation to know), i.e no constructor call overhead, class as a layer of namespace.
  • Prefer non-friend free functions
    • Isolates changes in implementation
    • Forces implementation via the public interface
  • Write them as algorithm instead member function.

How?
  • Implement functions in terms of other functions
  • Everything left over cannot be a normal function

Header:
<utility>

Keep using std::move_if_noexcept in mind.

With testing equivalence (keep Regular type in mind):
  • operator==(lhs, rhs)
  • operator<(lhs, rhs)
  • use above 2 to do the rest of test.

Swap:
  • Member swap should be deprecated
  • Free function swap should rarely be specialized
  • std::exchange
template<typename T>
void swap(T & lhs, T & rhs) {
rhs = std::exchange(lhs, std::move(rhs));
}


Erase:


repeat_n:
  • Range library.

operators that must be members:
  • operator=
  • operator[]
  • operator()
  • operator->

Sum up:
  • If it must be a member, make it a member
    • Virtual functions
    • Member operators
    • Constructors
  • If it can be a non-friend function, make it free
    • Only if no loss of efficiency
      • Remember insert (carefully designed)
  • Otherwise, maximize consistency

ISO Reference:

Oct 11, 2016

[cppcon 2016] CppCon 2016: Chandler Carruth “Garbage In, Garbage Out: Arguing about Undefined Behavior..."



reference:

Narrow contract:
  • Checkable (probabilistically) at runtime.
  • Provide significant value: bug finding, simplification, and/or optimization.
  • Easily explained and taught to programmers.
  • Not widely violated by existing code that works correctly and as intended.
Shifting unsigned int over 32 bit is _also_ an UB.

https://google.github.io/styleguide/cppguide.html#Integer_Types
Use signed int and take advantage of signed int arithmetic UB for optimization.
Especially on 64-bit platform due to pointer is 64-bit and int is 32 bit.

Oct 4, 2016

[c++] [cppcon2016] Constant fun

enum  bm { b0 = 0x1, b1 = 0x2, b2 = 0x4 };
int b = bm::b0 | bm::b1;

//---
enum  bm { b0 = 0x1, b1 = 0x2, b2 = 0x4 };
bm b = bm::b0 | bm::b1; // bad conversion

//--
enum  bm { b0 = 0x1, b1 = 0x2, b2 = 0x4 };

bm b = bm(bm::b0 | bm::b1); //OK

//--
enum class bm { b0 = 0x1, b1 = 0x2, b2 = 0x4 };
bm b = bm(bm::b0 | bm::b1); // no such op!!

//--
enum class bm { b0 = 0x1, b1 = 0x2, b2 = 0x4 };

constexpr bm operator| (bm v0, bm v1) {
return bm(int(v0) | int(v1));
}

bm b = bm::b0 | bm::b1;

switch (b) {
case bm::b0 | bm::b1: /*...*/; // OK due to constexpr operator
}

//--


[cppcon2016] [c++] Improving Performance Through Compiler Switches - Tim Haines

Oct 3, 2016

[cppcon2016] extern "C": Talking to C Programmers About C++

extern "C": Talking to C Programmers About C++
https://goo.gl/V4wJG3
www.dansaks.com


What’s a Data Type?
A data type is a bundle of compile-time properties for an object:
• size and alignment
• set of valid values
• set of permitted operations

“If you’re arguing, you’re losing.” 

Sep 30, 2016

[cppcon2016] c++ Practical Performance Practices -note

Practical Performance Practices

https://github.com/CppCon/CppCon2016/blob/master/Tutorials/Practical%20Performance%20Practices/Practical%20Performance%20Practices%20-%20Jason%20Turner%20-%20CppCon%202016.pdf

Make well-performing code 'by default'

Prefer containers in descendent order:

  • std::array
  • std::vector
  • Then only differ if you need specific behavior.
    Make sure you understand what the library has to do.

 

Always const - Complex Initialization


  • Use IIFE (Immediately-invoked Function Expressions), i.e lambda Expressions.
  • Always Initialize When Const Isn't Practical.
  • Don't Recalculate Values - Calculate on First Use.
  • Branching is slower
  • Atomic is even more slower
  • Calculate At Construction



  • Don't Disable Move Operations / Use Rule of 0
  • Avoid Named Temporaries



  • Avoid object copying
  • Avoid ( shared_ptr ) Copies (use shared_ptr.get())
  • Avoid Automatic Conversions (shared_ptr<Base> <-> shared_ptr<Derived>)
  • Avoid automatic conversions
  • Don't pass smart pointers
  • Make conversion operations explicit
  • Don't use std::endl => '\n' << std::flush


----
Summarize:

  • Avoid shared_ptr
  • Avoid std::endl
  • Always const
  • Always initialize with meaningful values
  • Don't recalculate immutable results


---
Smaller Code Is Faster Code


  • Common code in non-template base class.
  • Prefer return unique_ptr<> from factories
  • Avoid std::function<>
  • Use Lambdas


[cppcon2016][c++] Leak-Freedom in C++ - note

CppCon 2016 - Herb Sutter “Leak-Freedom in C++... By Default.”-
https://goo.gl/2TNgQI
--
  • Ensure an object will be destroyed once it is no longer needed.
  • Correct by construction!
template<typename T>
using Pimpl = const unique_ptr<T>;
unique_ptr<data[]> ptr;


Double linked list:
  • a unique_ptr to the next
  • a raw ptr to the back
shared_ptr with alias constructor:
  • Do not violate layering.
  • Don't create ownership cycles across modules by owning 'upward' (violates layering)
  • Use weak_ptr to break cycles.

How?
  • Don't pass an owner down to 'unknow code' that might store it.
    e.g storing a shared_ptr
  • Simple.
    Use weak_ptr inside the call back callable object which point
    to outside resource.
ownership types:
  • 1 object, 1 owner:
    unique_ptr
  • 1 object , n owners:
    shared_ptr
  • N objects, 1 or n owners:
    deffered_ptr
Reachability is a property of the while group.
Not detectable from 1 object, or subgroup.

Idea:
heap for deffered_ptr is isolated.
Each module could have one isolated heap.
github.com/hsutter/gcpp