Or “How much can we over-engineer an accumulator” ?
Preview:
Citation preview
- Slide 1
- or How much can we over-engineer an accumulator ?
- Slide 2
- What are we trying to achieve? Code reuse Supporting multiple
number formats Functionality customisation
- Slide 3
- What are templates?
- Slide 4
- Defer specification of types
- Slide 5
- Introducing the Accumulator class accum { private: int value_;
public: accum() : value_(0) {} void add(int n) { value_ += n; } int
result() { return value_; } };
- Slide 6
- Templating the Accumulator template class accum { private: int
value_; public: accum() : value_(0) {} void add(int n) { value_ +=
n; } int result() { return value_; } }; Add template declaration
Replace int with template parameter
- Slide 7
- Templating the Accumulator template class accum { private: Inc
value_; public: accum() : value_(0) {} void add(Inc n) { value_ +=
n; } Inc result() { return value_; } }; Add template declaration
Replace int with template parameter Generalise code that relied on
int
- Slide 8
- Templating the Accumulator template class accum { private: Inc
value_; public: accum() : value_() {} void add(Inc n) { value_ +=
n; } Inc result() { return value_; } }; Add template declaration
Generalise code that relied on int Replace int with template
parameter
- Slide 9
- Using the Accumulator accum acc1; accum > acc2; accum acc3;
Same as original code complex is part of the standard library We
can even use it to concatenate strings
- Slide 10
- What are templates Defer specification of types Allowed types
determined by required expressions
- Slide 11
- What are the expressions? template class accum { private: Inc
value_; public: accum() : value_() {} void add(Inc n) { value_ +=
n; } Inc result() { return value_; } }; Has a += operator Default
construction Can be copied The required set of operations is a
Concept
- Slide 12
- Concepts Entirely determined by valid expressions Generally
exist only in documentation The accum template class takes one
argument that must be Incrementable An Incremental class is default
and copy constructable and have the following valid expressions
where i1 and i2 are instances of Incrementable: i1 += i2//
Increment operator The accum template class takes one argument that
must be Incrementable An Incremental class is default and copy
constructable and have the following valid expressions where i1 and
i2 are instances of Incrementable: i1 += i2// Increment
operator
- Slide 13
- Concepts accum acc1; accum > acc2; accum acc3; Has built in
+= operator Have overloaded += operators
- Slide 14
- What happens if you fail? The compiler will error The compiler
has no knowledge of the concept The error message could be pretty
messy
- Slide 15
- What happens if you fail? accum acc1; accum > acc2;
acc2.add(a_list); Compiles fine Templates are only evaluated if
used error: no match for operator+= in ((accum
>*)this)->accum >::value_ += n error: no match for
operator+= in ((accum >*)this)->accum >::value_ += n This
doesnt refer to anything on this slide
- Slide 16
- What are templates Defer specification of types Allowed types
determined by required expressions Evaluated at compile time
- Slide 17
- Compilation Preprocessor Compilation Code Generation Synthesis
Templates evaluated here HDL generated here template class accum
{}; accum a; accum b; template class accum {}; accum a; accum b;
class accum_int {}; class accum_double {}; accum_int a;
accum_double b; class accum_int {}; class accum_double {};
accum_int a; accum_double b;
- Slide 18
- What are templates? Defer specification of types Allowed types
determined by required expressions Evaluated at compile time
Operate on semantics rather than syntax
- Slide 19
- Why not Macros? Macros Templates #define MIN(x,y) x accum_mult;
Non type parameters Default parameters Initialise value_ with
parameter Initialise value_ with parameter Addition typedef doesnt
change as default value can be used Addition typedef doesnt change
as default value can be used
- Slide 38
- Bringing it all together template class accum { int value_;
public: accum() : value_(Initial) {} void add(int n) { value_ =
Func()(value_, n); } int result() { return value_; } }; Reintroduce
Inc template parameter Reintroduce Inc template parameter
- Slide 39
- Bringing it all together template class accum { Inc value_;
public: accum() : value_(Initial) {} void add(Inc n) { value_ =
Func()(value_, n); } Inc result() { return value_; } }; Reintroduce
Inc template parameter Reintroduce Inc template parameter Make
addition the default
- Slide 40
- Bringing it all together template, int Initial = 0> class
accum { Inc value_; public: accum() : value_(Initial) {} void
add(Inc n) { value_ = Func()(value_, n); } Inc result() { return
value_; } }; Reintroduce Inc template parameter Reintroduce Inc
template parameter Make addition the default
- Slide 41
- Bringing it all together template, int Initial = 0> class
accum { Inc value_; public: accum() : value_(Initial) {} void
add(Inc n) { value_ = Func()(value_, n); } Inc result() { return
value_; } }; What happens if T cannot be constructed from an int?
What happens if T cannot be constructed from an int? What about the
maximum for a float/double? What about the maximum for a
float/double?
- Slide 42
- Someone Elses Problem template, int Initial = 0> class accum
{ Inc value_; public: accum() : value_(Initial) {} void add(Inc n)
{ value_ = Func()(value_, n); } Inc result() { return value_; } };
Make the user provide a suitable initial value Make the user
provide a suitable initial value
- Slide 43
- Someone Elses Problem template > class accum { Inc value_;
public: accum(Inc v = Inc()) : value_(v) {} void add(Inc n) {
value_ = Func()(value_, n); } Inc result() { return value_; } };
Make the user provide a suitable initial value Make the user
provide a suitable initial value
- Slide 44
- Everyone Elses Problem template class ALU { Acc1 acc1_; Acc2
acc2_; public: ALU(): acc1_(), acc2_() {} }; We have to let users
pass the correct defaults What should go here?
- Slide 45
- Factory Fun template > class accum { Inc value_; public:
accum(Inc v = Inc()) : value_(v) {} void add(Inc n) { value_ =
Func()(value_, n); } Inc result() { return value_; } }; Define a
simple factory
- Slide 46
- Factory Fun template struct default_factory { T create() {
return T(); } }; template > class accum { Inc value_; public:
accum(Inc v = Inc()) : value_(v) {} void add(Inc n) { value_ =
Func()(value_, n); } Inc result() { return value_; } }; Define a
simple factory Add the factory as a template argument
- Slide 47
- Factory Fun template struct default_factory { T create() {
return T(); } }; template, typename Fact = default_factory >
class accum { Inc value_; public: accum(Inc v = Inc()) : value_(v)
{} void add(Inc n) { value_ = Func()(value_, n); } Inc result() {
return value_; } }; Define a simple factory Add the factory as a
template argument Use the factory to initialise value_
- Slide 48
- Factory Fun template struct default_factory { T create() {
return T(); } }; template, typename Fact = default_factory >
class accum { Inc value_; public: accum(Fact f = Fact()) :
value_(f.create()) {} void add(Inc n) { value_ = Func()(value_, n);
} Inc result() { return value_; } }; Define a simple factory Add
the factory as a template argument Use the factory to initialise
value_
- Slide 49
- Factory Fun template struct mult_factory { T create() { return
T(1); } }; typedef accum, mult_factory > int_mult; Our
multiplication accum requires a new factory to give the correct
initial value Our multiplication accum requires a new factory to
give the correct initial value We can then typedef away the
horribleness
- Slide 50
- What about Max/Min? template struct max_factory { T create() {
return /* max value of T */;} }; Need a template that does
different things depending on type Need a template that does
different things depending on type
- Slide 51
- Specialisation template struct max_factory; template struct
max_factory { int create() { return INT_MAX; } }; template struct
max_factory { double create() { return DBL_MAX; } } We cant do
anything here so leave it undefined We cant do anything here so
leave it undefined Give the compiler a version which works for ints
One of these required for every type We probably ought to make this
possible to reuse
- Slide 52
- Traits template struct num_limits {}; template struct num_limts
{ static int max() { return INT_MAX; } static int min() { return
INT_MIN; } } template struct max_factory { T create() { return
num_limits ::max(); } } Create an undefined class Specialise for
what we care about Specialise for what we care about Use the trait
class to implement the factory
- Slide 53
- Built-in traits #include template struct max_factory { T
create() { return std::numeric_limits ::max(); } }; template struct
min_factory { T create() { return std::numeric_limits ::min(); } };
Why reinvent the wheel?
- Slide 54
- (Hopefully) Final - Factories template struct default_factory {
T create() { return T(); } }; template struct mult_factory { T
create() { return T(1); } }; template struct max_factory { T
create() { return std::numeric_limits ::max(); } }; template struct
min_factory { T create() { return std::numeric_limits ::min(); }
};
- Slide 55
- (Hopefully) Final - Accumulator template, typename Fact =
default_factory > class accum { Inc value_; public: accum(Fact f
= Fact()) : value_(f.create()) {} void add(Inc n) { value_ =
Func()(value_, n); } Inc result() { return value_; } };
- Slide 56
- (Hopefully) Final - Client Code typedef accum string_append;
typedef accum, mult_factory > double_mult; typedef accum,
max_factory > int_and; We could make this nicer with MORE traits
but we have to stop somewhere. We could make this nicer with MORE
traits but we have to stop somewhere.
- Slide 57
- Compile time dimensional analysis
- Slide 58
- Consider a simple unit system Three base units meter for length
second for time kilogram for mass All derived units have a scaling
factor of one Only integer powers
- Slide 59
- Define our quantity template struct quantity { double value;
quantity(double value):value(value) {} }; typedef quantity meter;
typedef quantity second; typedef quantity kilogram; Length, time
and mass are template parameters Length, time and mass are template
parameters Constructor to make creating quantities easier
Constructor to make creating quantities easier Construction can be
implicit from a double
- Slide 60
- Addition template quantity operator+ (quantity lhs, quantity
rhs) { return lhs.value + rhs.value; } The two units have to be the
same Add the values Function can take any quantity type Function
can take any quantity type
- Slide 61
- Multiplication template quantity operator*(quantity lhs,
quantity rhs) { return lhs.value * rhs.value; } The two quantities
may be different types Return type sums the powers of the base
units Return type sums the powers of the base units The only code
executed at runtime is the multiplication Division is similar
- Slide 62
- Using our system typedef quantity speed; typedef quantity
acceleration; speed s = meters(2) / seconds(1); acceleration a = s
/ seconds(1); speed s2 = meters(2) * seconds(1); Compile time
error
- Slide 63
- Benefits All dimensional analysis is compile time only No
runtime overhead No effect on synthesisability All this is provided
by Boost in a more general way Preprocessor issues prevent
synthesis (for now)
- Slide 64
- Well some of you asked
- Slide 65
- What is it? We can get the compiler to perform computation
Discovered rather than invented A (sort of) real world use of
functional programming
- Slide 66
- Hello World! (-ish) template struct factorial { static const
int value = ???; }; Can we make value equal to the factorial of I ?
Can we make value equal to the factorial of I ? No loops, but we
can recurse
- Slide 67
- Hello World! (-ish) template struct factorial { static const
int value = I * factorial ::value; }; template struct factorial {
static const int value = 1; } We just copy the mathematical
recursive definition We just copy the mathematical recursive
definition Specialisation to implement the base case Specialisation
to implement the base case
- Slide 68
- Use cases? Static loop unrolling
- Slide 69
- Loop Unrolling int accumulate(int value) { static accum acc;
acc.add(value); return acc.result(); } Higher level synthesis
wrapper functions need to hold state statically Higher level
synthesis wrapper functions need to hold state statically What
happens if we want to compose wrapper functions?
- Slide 70
- Loop Unrolling int accumulate(int value) { static accum acc;
acc.add(value); return acc.result(); } int accum_wrapper(int value)
{ if (value % 2 == 0) return accumulate(value); else return
accumulate(value); } Wed like to accumulate odd and even numbers
separately. Wed like to accumulate odd and even numbers separately.
Both accumulate function calls will use the same static data
- Slide 71
- Loop Unrolling template int accumulate(int value) { static
accum acc; acc.add(value); return acc.result(); } int
accum_wrapper(int value) { if (value % 2 == 0) return
accumulate(value); else return accumulate(value); } Make the
function a template Use different template parameters for each
instance Use different template parameters for each instance No
effect on functionality, just allows us to create independent
instances No effect on functionality, just allows us to create
independent instances
- Slide 72
- Loop Unrolling template int accumulate(int value) { static
accum acc; acc.add(value); return acc.result(); } int
accum_wrapper(int value) { if (value % 2 == 0) return accumulate
(value); else return accumulate (value); } Make the function a
template Use different template parameters for each instance Use
different template parameters for each instance No effect on
functionality, just allows us to create independent instances No
effect on functionality, just allows us to create independent
instances What if we want to control the number of instances?
- Slide 73
- Loop Unrolling template struct looper { static int loop(int
value, int key) { if (key == I) return accumulate (value); else
return looper ::loop(value, key); } } template struct looper {
static int loop(int value, int key) { return accumulate (value); }
} key is the runtime provided index Recursive call to next index
Base case to avoid infinite recursion
- Slide 74
- Loop Unrolling int accum_wrapper(int value) { return looper
::loop(value, value % 2); } int accum_wrapper(int value) { return
looper ::loop(value, value % 16); } This is the maximum index
rather than the loop iterator count This is the maximum index
rather than the loop iterator count Easily extendable to 16 (or
more) accumulators
- Slide 75
- Use Cases? Static loop unrolling Type conditional compilation
Calculation of bus widths/ranges Other things I havent thought
of
- Slide 76
- Questions? Comments?