Software design requires a solid understanding of memory, especially as it pertains to type relationships, and dynamic behavior, in object-oriented design. An understanding of the overhead of copying and cloning supports an evaluation of short- and long-term costs and benefits of various design choices as they impact performance and maintainability.
Software Design: Impact of Memory Usage (Copying, Cloning and Aliases)
1. Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
Object-‐Oriented
Design
Memory
Usage:
Impact
on
Design
Correctness and Efficiency:
Copying, Cloning and Aliases
2. Design
QuesEons
• How
to
design
classes
for
correct
&
efficient
memory
usage?
• What
details
wrt
memory
are
hidden
in
C++?
• What
details
wrt
memory
are
hidden
in
C#?
Copyright@2015
Adair
Dingle
3. Key
ObservaEons
• ImplementaEon
language
does
affect
design
• Data
need
not
always
be
copied
• Memory
ownership
should
be
tracked
– Unintended
aliases
may
lead
to
data
corrupEon
– C++
memory
leaks
– C#
performance
degradaEon
Copyright@2015
Adair
Dingle
4. Chapter
4:
Memory
Program
Memory
Overview
Analysis
of
the
impact
of
design
on
memory
use
• Memory
AbstracEon
• Heap
Memory
• Memory
Overhead
• Memory
Management
Programmer’s
PerspecEve
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
5. Standard
Views
of
Memory
• Memory
management
is
a
background
process
– handled
by
OperaEng
System
– Not
a
concern
for
developer
• AbstracEon
frees
the
programmer
from
– tedious,
low-‐level
pla[orm-‐dependent
details
– Managing
specific
memory
locaEons
=>
Portable
code,
developed
quickly
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
6. So`ware
designer
perspecEve
• Memory
is
uniform:
no
disEncEon
made
between
– the
cache
and
secondary
store
• Memory
is
unlimited
resource
– virtual
memory
– Need
worry
only
about
unbounded
recursion
• Memory
cheap/free
– No
cost
for
allocaEon/access/deallocaEon
• Memory
safe
– Data
corrupEon
not
anEcipated
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
7. Table
3.9
CompuEng
System
Views
Ideal Reality Masking Construct Cost
Infinite memory Memory Bounded Virtual memory Paging
Uniform memory
access
Memory Hierarchy HLL code access not
differentiated
Secondary store slower
and more expensive
Dedicated CPU Time-sliced Parallel threads Context switch
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
8. Program
Memory
• Data
segment:
Run-‐Eme
stack
–
staEc
allocaEon
– Holds
acEvaEon
records
associated
with
funcEon
calls
• Program
counter
• Local
data
(passed
parameters,
local
declaraEons)
– main()
is
a
(the
‘first’)
funcEon
– Efficient!!
(but
rigid)
• Register
aids
in
handling
stack
frames
(acEvaEon
records)
• Stack
frames
fixed
size
(with
possible
variable
porEon)
• Data
segment:
Heap
–
dynamic
allocaEon
– Memory
for
run-‐Eme
allocaEons
– Less
efficient,
more
flexible
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
9. Table
4.1
Common
DifficulEes
with
Program
Memory
Memory Problem Cause Consequence Effect
Data corruption Hidden
aliases
Ownership undermined Data values
overwritten
Performance
Degradation
Fragmented
heap
Allocator: more time to
find free memory
Software slowed
Poor Scalability
Memory Leak Heap memory
not collected
Heap memory unusable Lost resource
C++ Memory Leak Handle to
memory lost
Memory inaccessible Memory allocated
but unusable
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
10. Heap
Memory
• Flexible
memory
allocaEon
– size
and
frequency
of
allocaEon
may
vary
from
run
to
run
– Supports
allocaEon
dependent
on
environmental
factors
• Run-‐Eme
overhead
(=>
performance
hit)
– Explicit
allocaEon
• may
slow
down
if
the
heap
is
fragmented
• May
be
rejected
if
free
blocks
of
heap
memory
too
small
– Explicit
deallocaEon
• C++
model:
programmer
responsible
for
releasing
memory
– Implicit
deallocaEon
• C#/Java:
execuEon
suspended
if
the
garbage
collector
runs
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
11. C++:
Explicit
deallocaEon
• C++
designed
to
be
backward
compaEble
with
C
– Emphases
on
efficiency
and
control
retained
• Explicit
deallocaEon
– means
of
retaining
control
of
heap
management
– efficient
amorEzaEon
of
the
cost
of
memory
deallocaEon
• With
NO
implicit
deallocaEon,
C++
programs
– not
subject
to
suspension
• for
a
run
of
the
garbage
collector
– suitable
for
real-‐Eme
systems
• BUT,
…,
Memory
leaks
due
to
so`ware
design
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
12. Example
4.1
C++
AllocaEon
of
(Heap)
Memory
at
Run-‐Time
// “ptr” is a pointer variable allocated on the stack
// “ptr” holds the address of the heap object return by new
MyType* ptr = new MyType; // #A: MyType object allocated
// deallocate heap memory via call to delete operator
delete ptr; // #B: MyType object deallocated
// null out pointer to indicate it ‘points to nothing’
ptr = 0; // #C: programmer must reset pointer
// pointers can also hold the address of an array
ptr = new MyType[10]; // #D: 10 MyType objects allocated
…
// must use delete[] when deallocating an array on heap
delete[] ptr; // #E: 10 MyType objects deallocated
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
13. Example
4.2
C++
primiEves:
allocaEon/deallocaEon
clear
// function code: primitives used, no class objects
// application programmer ERROR: NEW not matched with DELETE
void leakMemory()
{ int* heapData;
heapData = new int[100];
…
return;
} // memory leak obvious: no explicit deallocation
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
14. Example
4.2
conEnued
// function code ok: NEW matched with DELETE
void noMemoryLeak()
{ int* heapData;
heapData = new int[100]; …
// heap memory explicitly deallocated: delete matches new
delete[] heapData;
return;
}
// function code ok: access to heap memory passed back to caller
// CALLER MUST ASSUME RESPONSIBILITY (ownership) FOR HEAP MEMORY
int* passMemory()
{ int* heapData;
heapData = new int[100]; …
return heapData;
}
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
15. C#/Java:
Implicit
deallocaEon
• Java
designed
10
years
a`er
C++
– memory
errors
in
C++
code
widely
known
• premature
deallocaEons,
data
corrupEon,
leaks
– Java
designers
decided
to
• remove
memory
management
responsibiliEes
from
programmer
• rely
on
garbage
collecEon
for
reclaiming
heap
memory
– C#
(“Microso`’s
Java”
)followed
suit
• BUT,
data
corrupEon
and
memory
leaks
sEll
occur
– garbage
collecEon
is
not
a
perfect
process.
– C#/Java
programmer
should
zero
out
references
=>
So`ware
designers
should
track
memory
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
16. Tracking
memory
ownership
• DIFFICULT
– Who
owns
this
data?
– Who
deallocates
it?
• MulEple
handles
to
the
same
data
– Explicit
aliases
and
call
by
reference
– difficult
to
track
all
handles,
especially
if
scope
differs.
• Class
construct
hides
data
– not
obvious
to
client
when
heap
memory
is
allocated
– Constructors
can
assume
memory
ownership
– Other
class
methods
can
transfer
memory
ownership.
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
17. Example
4.3
C++:
Why
Memory
Leaks?
// function code looks correct
void whyLeakMemory1()
{ hiddenLeak* naive;
naive = new hiddenLeak[100];
…
delete[] naive; // delete[] matches new[]
}
void whyLeakMemory2(hiddenLeak localVar)
{ … }
void whyLeakMemory3()
{ hiddenLeak steal;
hiddenLeak share;
…
steal = share;
return;
}
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
18. Example
4.4
C++
class
without
proper
memory
management
//IMPROPERLY DESIGNED: heap memory allocated (constructor)
// MISSING: destructor, copy constructor, overloaded =
class hiddenLeak{
private:
int* heapData;
int size;
public:
hiddenLeak(unsigned s = 100)
{ size = s; heapData = new int[size]; }
…
};
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
19. Example
4.5
C++
class
with
proper
memory
management
// class definition, .h file, memory managed properly
class noLeak{
private:
int* heapData;
int size;
public:
noLeak(unsigned s = 100)
{ size = s; heapData = new int[size]; }
// copy constructor supports call by value
noLeak(const noLeak&);
// overloaded = supports deep copying
void operator=(const noLeak&);
// destructor deallocates heapData
~noLeak() { delete[] heapData; }
…
};
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
20. C++
deep
copying
// copy constructor supports call by value
noLeak::noLeak(const noLeak& src)
{ size = src.size;
heapData = new int[size];
for (int k = 0; k < size; k++)
heapData[k] = src.heapData[k];
}
// overloaded = supports deep copying; check for self-assignment
void noLeak::operator=(const noLeak& src)
{ if (this != &src)
{ delete[] heapData;
size = src.size;
heapData = new int[size];
for (int k = 0; k < size; k++)
heapData[k] = src.heapData[k];
}
}
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
21. Example
4.6
C++
class
with
copying
suppressed
class noCopy{
private:
int* heapData;
int size;
// copying suppressed!
noCopy(const noCopy&);
void operator=(const noCopy&);
public:
noCopy(unsigned s = 100)
{ size = s; heapData = new int[size]; }
// destructor deallocates heapData
~noCopy () { delete[] heapData; }
…
};
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
22. Shallow
vs
Deep
Copying
• Shallow
copying
performs
first-‐level
bitwise
copy
– shallow
copy
of
an
address
establishes
an
alias
• Deep
copying
performs
two-‐Eer
copy
with
objects
– For
an
object
with
heap
memory
• the
address
is
not
just
copied
• a
new
‘copy’
of
the
heap
memory
is
allocated
and
iniEalized
• the
address
of
this
replica
is
then
copied
– Requires
C++
copy
constructor
or
C#/Java
Cloneable
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
23. Example
4.8
C#
Cloning
public class uCopy: ICloneable
{ private anotherClass refd;
// ‘anotherClass’ instance allocated on heap
// address of subobject held in reference ‘ref’
public uCopy()
{ refd = new anotherClass(); }
…
// deep copy: more heap memory allocated via clone
// subobject copied into new memory
public object Clone()
{ uCopy local = this.MemberwiseClone() as uCopy;
local.refd = this.refd.Clone() as anotherClass;
return local;
}
…
}
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
24. Example
4.8
C#
Cloning..
conEnued
// application code
// #A: uCopy object allocated
uCopy u1 = new uCopy();
// intuitive but yields shallow copy
// #B: shallow copy of uCopy object
uCopy u2 = u1;
// deep copy: must cast to retrieve type information
// #C: clone of uCopy object
u2 = u1.Clone() as uCopy;
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
25. Suppress
Copying
• Copying
may
be
suppressed
when
undesirable
– Copying
large
registries
(hash
tables,
etc.
)
– GeneraEon
of
temporaries
– C++
supports
move
semanEcs
• Deep
copying
is
more
expensive
than
shallow
copying.
• Shallow
copying
may
lead
to
data
corrupEon.
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
26. Circumvent
Copying:
Move
SemanEcs
• C++11
• Enhance
Performance
by
avoiding
copying
– Acquire
heap
memory
of
passed
argument
(object)
• Applicable
for
temporaries
– Save
constructor/destructor
pairing
• Compiler
decides
whether
to
invoke
– Copy
constructor
or
move
constructor
– Overloaded
assignment
or
move
assignment
• Class
designer
need
only
define
addiEonal
methods
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
27. C++
11
move
semanEcs:
denoted
by
“&&”
// move constructor supports efficient call by value
noLeak::noLeak(const noLeak&& src)
{ size = src.size;
heapData = src.heapData;
src.size = 0; src.heapData = nullptr;
}
// move assignment exchanges ownership
// return reference to support chained assignment
noLeak& noLeak::operator=(const noLeak&& src)
{ swap( size, src.size);
swap( heapData, src.heapData);
return *this;
}
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
28. Garbage
CollecEon
• implicit
deallocaEon
• triggered
by
run-‐Eme
evaluaEon
of
heap
– Heap
too
fragmented
or
insufficient
memory
available
• running
program
must
be
suspended
– EXPENSIVE,
not
feasible
for
many
real-‐Eme
systems
– No
overhead
if
garbage
collector
does
NOT
run
• All
acEve
data
is
marked
– remaining
‘garbage’
is
removed
– allocated
but
unused
blocks
of
heap
memory
released.
• Insufficient
heap
memory
– remedied
by
the
reclamaEon
of
‘garbage’
– data
allocated
but
no
longer
used
is
released
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
29. Example
4.9
Classic
Mark
and
Sweep
Algorithm
for
IdenEfying
Garbage
// start with direct references, the root set:
// all visible variables (active memory) at time of sweep
// trace out to all variable indirectly referenced
void markSweep()
{ for each Object r in rootSet
mark(r);
}
// if heap object marked: KEEP
// clear marked status in preparation for subsequent sweeps
// if heap object unmarked: RECLAIM (garbage)
void sweep()
{ for each Object x on heap
if (x.marked) x.marked = false;
else release(x);
}
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
30. Example
4.9
conEnued
// recursive depth-first marking
// terminates when all reachable objects marked
void mark(Object x)
{
if (!x.marked)
{ x.marked = true;
for each Object y referenced by x
mark(y);
}
}
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
31. Reference
CounEng
Another
form
of
Implicit
deallocaEon
• AllocaEon
sets
(iniEal)
reference
count
to
‘1’
for
a
data
item
• Each
subsequent
alias
(handle)
increments
reference
count
• Reference
count
decremented
when
handle
goes
out
of
scope
• (heap)
Data
deallocated
when
reference
count
is
‘0’
Drawbacks
• Unavoidable
overhead
– for
all
run-‐Eme
allocaEons
and
deallocaEons
• Cycles
cannot
be
detected
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
32. Implicit
DeallocaEon
Flaws
Garbage
collecEon
cannot
accurately
detect
all
garbage
• Lingering
references
prevent
memory
from
being
collected
– Live
reference
to
dead
object
keeps
dead
object
allocated
• Garbage
collector
cannot
accurately
discern
valid
references
• Reference
counEng
cannot
detect
cycles
– Objects
with
references
to
other
objects
keep
object
alive
– Problem
when
cycle
of
objects
is
not
referenced
externally
-‐>
‘garbage’,
missed,
not
deallocated
because
reference
count
not
zero
• Weak
references
may
ameliorate
the
occurrence
of
cycles.
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
33. CompacEon
Shi`
allocated
blocks
a`er
garbage
collector
runs
– Non-‐trivial,
expensive!
• Allocated
memory
blocks
moved
to
one
end
of
heap
– free
memory
is
available
in
large,
conEguous
blocks
⇒ Repairs
fragmented
heap
• An
excessively
fragmented
heap
– may
cause
severe
performance
degradaEon
– may
trigger
compacEon
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
34. Figure
4.5
C++
Design
Guidelines
to
prevent
memory
leaks
Match
every
new
with
a
delete
Class
design:
DEFINE
constructor
&
destructor
Class
design:
DEFINE
(or
suppress)
copy
constructor
&
overloaded
assignment
operator
Use
Reference
Count
to
track
aliases
Increment
with
each
added
reference
Decrement
when
alias
goes
out
of
scope
Deallocate
when
count
is
zero
Explicitly
transfer
ownership
Pass
pointer
by
reference
Assume
ownership
Reset
passed
(old
owner’s)
pointer
to
null
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
35. C++
class
design
C++
classes
with
internally
allocated
heap
memory
• MUST
make
explicit
design
decisions
with
respect
to
copying
– suppress
copying,
employ
move
semanEcs,
support
deep
copying
• MUST
define
a
destructor
– To
deallocate
heap
memory
when
an
object
goes
out
of
scope
• A
constructor
should
be
defined
– set
the
object
in
an
iniEal
state
– Including
iniEal
configuraEon
of
heap
memory
usage.
• C++
objects
– for
efficiency,
allocate
on
the
stack
– for
polymorphic
behavior,
allocate
on
the
heap
(see
chapter
7)
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
36. Common
Best
PracEces
• Make
explicit
design
decision
wrt
copying
– C++:
suppress,
support
deep
copying,
use
move
semanEcs
– C#/Java:
rely
on
shallow
copying,
implement
Cloning
• Minimize
and
track
aliases
– Clarify
ownership
of
memory
– Data
corrupEon
errors
hard
to
trace
• Zero
out
references
when
access
no
longer
needed
– Reduce
potenEal
for
data
corrupEon
– Improve
performance
of
garbage
collector
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved
37. Common
Best
PracEces
• Understand
design
impact
on
program
memory
– Stack
versus
heap
memory:
efficiency
versus
flexibility
– Store
versus
compute
(see
secEon
4.4)
• Note
language
differences
• Use
of
stack
&
heap
• Compile-‐Eme
versus
Run-‐Eme
layouts
• Methods
of
allocaEon,
deallocaEon,
copying
(cloning)
BUT
REMEMBER…
DESIGN
NOT
enforced
by
compiler!
Copyright@2014
Taylor
&
Francis
Adair
Dingle
All
Rights
Reserved