0% found this document useful (0 votes)
23 views71 pages

The Implementation of Value Types - Lawrence Crowl - CppCon 2014

Uploaded by

alan88w
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
23 views71 pages

The Implementation of Value Types - Lawrence Crowl - CppCon 2014

Uploaded by

alan88w
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
You are on page 1/ 71

The Implementation of

Value Types

Lawrence Crowl

CppCon 2014
Introduction

(read the notes)


Definition of Value Types
 Identity is not important.

The address of an object does not affect its operations.

Operating on a copy of an object is indistinguishable from
acting on the original object.
 Operations on an object are independent of context.

All copies are semantically deep copies.

Operations do not change the context.
Most Familiar Value Types
 Built-in arithmetic types are the prototype.

They have no references to other state.

They have meaning even when they have no address.
 Strings get used as value types.

Sort of.

It is complicated.
Attributes of Built-in Arithmetic Types
 well-understood  compact representation
operations  efficient copy and move
 efficient operations  efficient parameters
 well-understood  relatively alias insensitive
properties
 concurrency friendly
 literals
 heavily optimizable
 constant initialization
 portable (within limits)
 non-type template args
Ad-Hoc Representation
int apple_quality = 3;
int orange_quality = 4;
if ( apple_quality + orange_quality ) ...

std::string border = "green";


if ( border == "#00FF00" ) ...
Why not new value types?
FEAR
Concerns with Abstraction
 It will be too slow.
 It will take too long to write.
 It will have bugs.
 It will take too long to learn.
 It will not do what is expected.
 It will not be available elsewhere.
Overcome the Fear
Operations
Mission Creep
 cardinal numbers + subtraction => integers.

New representation.
 integers + division => ???

commercial: same representation, redefined division

engineering: floating point, approximate division

mathematical: rational, accurate division
Operation Categories
 Must Have
 Nice to Have
 Exuberant Generalization
 Foolish Consistency
Properties
Mathematical
 Idempotent?  Totally ordered?
 Identities?  Strictly weakly ordered?
 Zeros?  Partially ordered?
 Commutative?
 Distributive?
Representational
 Are there trap values?
 Do all bit patterns in the implementation represent a
value?
 Are they distinct values?
Semantic
 Is ill-formed use detectable?

If so, is it desirable?

In debugging?
 Have you mixed concepts of a container and a value?

std::string
Syntactic
 Are operators appropriate and consistent with
conventions?
 Do you want free functions for symmetric argument
conversion?
 Does the associativity of the operator match efficient
association?
String Append

string a, b, c, d;

a += b += c += d;
// a += (b += (c += d));

((a += b) += c) += d;

a << b << c << d;
Generality
Literal Types
 May be used to define compile-time constants.
 May be used to compute array sizes.
 May be used as non-type template arguments.
Literal Types
 A literal type is

a scalar type,

a reference type,

an array of literal type,

or a literal class type.
 Literal class types:

have only literal types for non-static data members and bases;

is an aggregate or has at lest one constexpr constructor
that is not a copy or move constructor;

and have a trivial destructor.
Literal Values

probability operator""_p(long double v) {
return probability(v);
}

probability x;
probability y = x * 0.3_p;
Representation
Redundancy
template <typename T>
struct read_mostly_complex {
T real, imaginary;
T angle, magnitude;
....
};
Padding
 double, int64_t, long long  long double
 pointer  ptrdiff_t
 long  size_t
 float, int32_t, char32_t
 int
 int16_t, char16_t, short
 char
Hotness
 Cache use has a large impact on performance.
 Minimize the number of cache lines your type typically
uses.
 Put hot and cold fields on different cache lines.
 Put fields accessed together on the same cache line.
Copy and Move
Trivially Copyable
 Basic types are trivially copyable.
 Contents can be bit-blasted.
 Contents can be passed in registers.
 It is testable:

#include <type_traits>

std::is_trivially_copyable<TYPE>::value ? ....
Requirements
 Fields are all trivially copyable.
 There are no non-trivial (copy or move) (constructors or
assignment operators).
 The destructor is trivial.
 There are no virtual bases or functions.
What if the type is big?
 Locality of access is often more important than space
efficiency.

But not always.
What if your type is too big?
 Implement logical copying without physical copying via
copy on write.
 Reference counting works well for non-circular
structures.

But make sure your reference counting is thread friendly.
 Improve locality by embedding small values rather than
reference counting.
Moving
type(const type& a)
: p(new content(*a.p)) {
}

type(type&& a)
: p(a.p) {
a.p = std::nullptr;
}
Parameter Passing
Two Choices
 Pass by value.
 Pass by const reference.
Slicing
class B {
virtual bool d() { return false; } };
class D : B {
virtual bool d() { return true; } };
bool g( B a) { return a.d(); }
bool h(const B& a) { return a.d(); }
g(D()) == false && h(D()) == true
Pass by Value
extern type va1(type input);
extern type va2(type input);
void vf1(type& output, type input) {
output += va1(input);
output += va2(input);
}
Direct Pass by Value
 Requires a trivially copyable class.
 Copy argument to the stack (e.g. IA32).

Work is equivalent to a memcpy.
 Copy small arguments to a register (e.g. AMD64).

May spill argument registers.
 Do neither (e.g. SPARC32).

Instead use indirect pass by value.
Indirect Pass by Value
 Works with non-trivally copyable types.
 Allocate a temporary local variable of the type.
 Copy the argument to the temporary.
 Pass a pointer to the temporary into the function.
 Access the content of the parameter indirectly.
 Deallocate the temporary on return.
Pass by Const Ref
extern type ra1(const type& input);
extern type ra2(const type& input);
void rf1(type& output, type& input) {
output += ra1(input);
output += ra2(input);
}
Parameter Recommendations
 Pass by value when the type is

small (<= 2 pointers) and

trivially copyable.
 Pass by const ref when

the type is large and

alias detection is relatively cheap.
 Otherwise do some experiments.
Result Passing
Return by Value!
Named Return Value Optimization
type function(....) {
type result;
....
if ( .... ) {
....
return result;
}
....
return result;
}
Aliasing
Approaches
 Ignore the problem.
 Document the problem.

List possible overwrites in the comments.

Use the restrict qualifier that C++ does not have.
 Overcome the problem.
Copy a Potentially Aliasing Parameter
void rf3(type& output, const type& input) {
type temp = input;
output += rf1(temp);
output += rf2(temp);
}
Conditionally Copy
void rf3(type& output, const type& input) {
if ( &output == &input ) {
type temp = input;
output += rf1(temp);
output += rf2(temp);
} else {
....
}
}
Conditionally Do Not Copy
type& type::operator =(const type& a) {
if ( this != &a ) {
delete p;
p = new content(*a.p);
}
}
Order Reads Before Writes
void rf4(type& output, const type& input) {
type temp1 = ra1(input);
type temp2 = ra2(input);
output += temp1;
output += temp2;
}
Aliasing Fields
template <typename T>
T& complex<T>::operator *=(const T& a) {
real = real * a.real - imag * a.imag;
imag = real * a.imag + imag * a.real;
return *this;
}
Read Caching Fields
template <typename T>
T& complex<T>::operator *=(const T& a) {
T a_real = a.real, a_imag = a.imag;
T t_real = real, t_imag = imag;
real = t_real * a_real - t_imag * a_imag;
imag = t_real * a_imag + t_imag * a_real;
return *this;
}
Conflicts
Global State
 Changes to global state must not affect logical operation
results.

Access to constant state is fine.

Memory allocation is fine.

Physically shared state must be protected against concurrent
access.
 Operations must not affect global state.

Physically shared state must be protected against concurrent
access.
 I/O only for debugging and performance analysis.
Static Initialization Order
constexpr type::type(int arg)
: field(arg) { }
type v(3);
Concurrency
 Reduce aliasing otherwise.
 Make const reference parameters concurrent-read safe.

The deep argument is only read in all operations, or

any access is protected with locks or atomics.
Exceptions
 Where possible, make operations noexcept.
 Otherwise, make operations exception safe.

An exception leaves the object in the state it had at the
beginning of the operation..
Allocate Before Changes
type& type::operator =(const type& a) {
if ( this != &a ) {
content *q = new content(*a.p);
delete p;
p = q;
}
}
Recover Resources
type& type::operator =(const type& a) {
if ( this != &a ) {
content *q = new content(*a.p);
try {
delete p;
} catch ( ... ) {
delete q;
rethrow;
}
p = q;
}
}
Optimization
Responsibilities
 You

choose the representation;

implement the operations;

reduce aliasing; and

reduce memory accesses.
 The compiler

does most everything else.
Avoid Redundant Memory Access
 Loading a pointer twice is inefficient unless you are
waiting for someone else to change it.
 The keyword this is an implicit pointer and inhibits
optimization.

Aggressively cache field reads.

Write back cached field.
Inlining
 Inlining may be a long-term commitment.
 Constexpr implies inlining.
 Inlining increases code bloat and puts pressure on the
cache.
 Inline when the body is no bigger than the call.
 Otherwise, inline when you have evidence of
performance gains.
Portability
IBM System\360
Follow Along
 Follow the standard.
 Follow the compilers.
 Follow the authors.
 Follow the tools.
Choose Portable Types

int64_t num_humans;

for ( size_t i = 0; i < v.size(); ++i )
.... v[i] ....;

int c = getchar();
Summary
Invest

 A good value type takes time to develop.



operations, properties, generality

representation, copy and move, parameters, results

aliasing, conflicts, optimization, portability
Profit
 Reduced client development time.

Semantics are clarified early.

Many mistakes are caught earlier.

Abstraction handles help debugging.
 Reduced execution costs.

Better implementations.

Abstraction handles help performance analysis.
Share
 Code sharing websites
 Articles, talks and tutorials
 Boost
 C++ Technical Specifications
 The C++ Standard

You might also like