PowerPoint Presentation
Parallel Computing
with GPUs: Memory
Dr Paul Richmond
http://paulrichmond.shef.ac.uk/teaching/COM4521/
Last Week Summary
We learnt about the motivation for using GPUs
The prevalence of GPUs in HPC
Begin looking at the C language
Compiled and built some programs in the lab
Demonstrated basic string (char array) manipulation
Compilation and linking
Now to consider * and & operators
Points from the feedback from
String concatenation and termination
Extern keyword
Transistors != performance (parallelism rules!)
Unable to complete a specific exercise (incorrect results)
Setting up my own machine
Familiarity with VS interface (solutions, debugging and breakpoints)
REGISTRATION
Did you manage to complete all of the lab
exercises?
Have you reviewed the exercise solutions? The difficulty of the Lab class this week was?
This Lecture
Pointers
Advanced use of pointers
Dynamically managed memory
Structures
Binary files
Pointers
A pointer is a variable that contains the address of a variable
Pointers and arrays are closely related
We have already seen some of the syntax with * and & operators
The * operator can be used to define a pointer variable
The operator & gives the address of a variable
Can not be applied to expressions or constants
#include
void main()
{
int a;
int *p;
a = 8;
p = &a;
}
Pointer example
printf(“a = %d, p = %d\n”, a, p);
printf(“a = %d, p = 0x%08X\n”, a, p);
char b;
char *p;
b = 8;
p = &b;
printf(“sizeof(b) = %d, sizeof(p) = %d\n”, sizeof(b), sizeof(p));
printf(“b = %d, p = 0x%08X\n”, b, p);
What is the size of p?
a = 8, p = 2750532
a = 8, p = 0x0045FCE0
Same example using a char
int a;
int *p;
Pointer example
printf(“a = %d, p = %d\n”, a, p);
printf(“a = %d, p = 0x%08X\n”, a, p);
char b;
char *p;
b = 8;
p = &b;
printf(“sizeof(b) = %d, sizeof(p) = %d\n”, sizeof(b), sizeof(p));
printf(“b = %d, p = 0x%08X\n”, b, p);
What is the size of p?
sizeof(b) = 1, sizeof(p) = 4
b = 8, p = 0x003BF9A7
a = 8, p = 2750532
a = 8, p = 0x0045FCE0
Same example using a char
Pointers
Pointer size does not change regardless of what it points to
The size of a pointer on a 32 bit machine is always 4 bytes
The size of a pointer on a 64 bit machine is always 8 bytes
The operator * is the indirection operator and can be used to
dereference a pointer
I.e. it accesses the value that a pointer points to…
The macro NULL can be assigned to a pointer to give it a value 0
This is useful in checking if a pointer has been assigned
int x = 1; int y = 0;
int *p;
p = &x; // p now points to x (value is address of x)
y = *p; // y is now equal to the value of what p points to (i.e. x)
x++; // x is now 2 (y is still 1)
(*p)++; // x is now 3 (y is still 1)
p = NULL// p is now 0
Pointers and arguments
C passes function arguments by value
They can therefore only be modified locally
void swap (int x, int y){
int temp;
temp = x;
x = y;
y = temp;
}
This is ineffective
Local copies of x and y are exchanged and then discarded
Pointers and arguments
C passes function arguments by value
They can therefore only be modified locally
void swap (int *x, int *y){
int temp;
temp = *x;
*x = *y;
*y = temp;
}
This swaps the values which x and y point to
Called by using the & operator
swap(&x, &y);
Pointers and Arrays
In the last lecture we saw pointer being used for arrays
char *name is equivalent to char name []
When we declare an array at compile time the variable is a pointer to
the starting address of the array
E.g. int a[10];
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
a &a[4]
int a[10] = {1,2,3,4,5,6,7,8,9,10};
int *p;
p = &a[4];
printf(“*p=%d, p[0]=%d\n”, *p, p[0]);
What is the output?
Pointers and Arrays
In the last lecture we saw pointer being used for arrays
char *name is equivalent to char name []
When we declare an array at compile time the variable is a pointer to
the starting address of the array
E.g. int a[10];
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
a &a[4]
int a[10] = {1,2,3,4,5,6,7,8,9,10};
int *p;
p = &a[4];
printf(“*p=%d, p[0]=%d\n”, *p, p[0]);
*p=5, p[0]=5
Pointer and Arrays
There is however an important distinction between char *name
and char name []
Consider the following
The pointer may be modified
The array can only refer to the same storage
char a[] = “hello world 1”;
char *b = “hello world 2”;
char *temp;
temp = b;
b = a;
a = temp; //ERROR
Pointer arithmetic
Pointer can be manipulated like any other value
p++: advances the pointer the next element
Pointer arithmetic must not go beyond the bounds of an array
Incrementing a pointer increments the memory location depending
on the pointer type
An single integer pointer will increment 4 bytes to the next integer
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
a a+4
int a[10] = {10,9,8,7,6,5,4,3,2,1};
int *p = a;
p+=4;
printf(“*p=%d, p[0]=%d\n”, *p, p[0]);
What is the output?
Pointer arithmetic
Pointer can be manipulated like any other value
p++: advances the pointer the next element
Pointer arithmetic must not go beyond the bounds of an array
Incrementing a pointer increments the memory location depending
on the pointer type
An single integer pointer will increment 4 bytes to the next integer
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
a a+4
int a[10] = {10,9,8,7,6,5,4,3,2,1};
int *p = a;
p+=4;
printf(“*p=%d, p[0]=%d\n”, *p, p[0]);
*p=6, p[0]=6
This Lecture
Pointers
Advanced use of pointers
Dynamically managed memory
Structures
Binary files
General Purpose Pointer
A General purpose pointer can be defined using void type
A void type can not be dereferenced
Arithmetic on a void pointer will increment/decrement by 1 byte
void *p;
char c;
int i;
float f;
p = &c; // ptr has address of character data
p = &i; // ptr has address of integer data
p = &f; // ptr has address of float data
Endianness
X86 uses little endian format
Memory is stored from least significant byte stored at the lowest memory
unsigned int a = 0xDEADBEEF;
char* p;
p = (char*)&a;
printf(“0x%08X, 0x%08X, 0x%08X, 0x%08X\n”, p, p+1, p+2, p+3);
printf(“0x%02X, 0x%02X, 0x%02X, 0x%02X\n”, *p, *(p+1), *(p+2), *(p+3));
0x00400000, 0x00400001, 0x00400002, 0x00400003
0xEF, 0xBE, 0xAD, 0xDE
p+3 = 0x00400003
p+2 = 0x00400002
p+1 = 0x00400001
p = 0x00400000
…
AD BE EFDE
Endianness
x78 x56 x34 x12
4 bytes (32 bit
integer)
1 byte
0
x
0
0
4
0
0
0
0
int a[] = {0x12345678, 0x0000000, 0xDEADBEEF, 0xFFFFFFFF};
x00 x00 x00 x00 xEF xBE xAD xDE xFF xFF xFF xFF
0
x
0
0
4
0
0
0
4
0
x
0
0
4
0
0
0
8
0
x
0
0
4
0
0
0
C
Endianess is very stange without an example
ssenaidnE si yrev egnats tuohtiw na elpmaxe
Pointers to pointers
Consider the following
int a[10][20]
int *b[10]
a is a two-dimensional array
200 int sized locations are reserved in memory
b is single dimensional array of pointers
10 pointers to integers are reserved
B[?] must be initialised or allocated (later in this lecture)
The pointers in b may be initialised to arrays of different length
char names[][10] = {“Paul”, “Bob”, “Emma”, “Jim”, “Kathryn”};
char *p_names[] = {“Paul”, “Bob”, “Emma”, “Jim”, “Kathryn”};
Which of the above is better?
Function Pointers
It is possible to define pointers to functions
Functions are however not variables
int (*f_p)(int, int);
f_p is a pointer to a function taking two integer arguments and returning an integer.
If f is a function then &f is a pointer to a function
Just in the same way that if a is an integer then &a is a pointer to an integer
int add(int a, int b);
int sub(int a, int b);
void main()
{
int (*f_p)(int, int);
f_p = &add;
return;
}
Using function pointers
Treat the function pointer like it is the function you want to call.
There is no need to dereference (*f_p) but you may if you wish
f_p = &add;
printf(“add = %d\n”, f_p(10, 4));
f_p = ⊂
printf(“sub = %d\n”, f_p(10, 4));
add = 14
sub = 6
Care is needed with parenthesis
What is f?
What is g?
int *f();
int (*g)();
Using function pointers
Treat the function pointer like it is the function you want to call.
There is no need to dereference (*f_p) but you may if you wish
f_p = &add;
printf(“add = %d\n”, f_p(10, 4));
f_p = ⊂
printf(“sub = %d\n”, f_p(10, 4));
add = 14
sub = 6
Care is needed with parenthesis
What is f? function returning pointer to int
What is g? pointer to a function returning int
int *f();
int (*g)();
const pointers
Remember the definition of const?
Not unintentionally modifiable
What then is the meaning of the following?
char * const ptr;
const char * ptr;
char const * const ptr;
const pointers
Remember the definition of const?
Not unintentionally modifiable
Read from right to left
What then is the meaning of the following?
char * const ptr;
char const * ptr;
The pointer is constant but the data pointed to is not
i.e. declare ptr as const pointer to char
The pointed to data is constant but the pointer is not
i.e. declare ptr as pointer to const char
char const * const ptr;
The pointer is constant and the data it points to is also constant
i.e. declare ptr as const pointer to const char
const char * ptr;=
https://cdecl.org/ – C Gibberish to English
https://cdecl.org/
This Lecture
Pointers
Advanced use of pointers
Dynamically managed memory
Structures
Binary files
Reminder: Heap vs. Stack
Stack
Memory is managed for you
When a function declares a variable it is pushed onto the stack
When a function exists all variables on the stack are popped
Stack variables are therefore local
The stack has size limits
Heap
You must manage memory
No size restrictions (except available memory)
Accessible by any function
Dynamically allocated memory
What if we can’t specify an array size at compile time (static allocation)
The size might not be known until runtime
We can use the malloc system function to get a block of memory on the
heap.
malloc keeps a list of free blocks of memory on the heap
malloc returns the first free block which is big enough “first fit”
If a block is too big it is split
Part is returned to the user and the remainder added to the free list
If no suitable block is found malloc will request a larger block from the OS
Increases the size of the heap
Adds the new memory to the free list (flagged as in use)
Free list
Free
In use
Owned by OS
malloc
void *malloc(size_t size)
Returns a pointer to void which must therefore be cast
#include
#include
void main()
{
int *a;
a = (int*) malloc(sizeof(int) * 10);
}
Use sizeof function to ensure correct number of bytes per element
a can now be used as an array (as in the previous examples)
Result of malloc can be implicitly cast
Memory leaks
Consider the following
b is on the stack and is free’d on return
a points to an area of memory which is allocated
a then points to b, there is no pointer to the area of memory that was
allocated
void main()
{
int b[10] = {1,2,3,4,5,6,7,8,9,10};
int *a;
a = (int*) malloc(sizeof(int) * 10);
a = b;
return;
}
This is known as a memory leak
Where we allocate memory we must also free it
free
The free function will add a previous used area of memory to the
free list
If it is adjacent to another free block these will be coalesced into a larger
block
void free (void *);
Free
In use
Owned by OS
Free list
int *a = (int*) malloc(sizeof(int) * 10); //allocate
free(a);//free
a
free
The free function will add a previous used area of memory to the
free list
If it is adjacent to another free block these will be coalesced into a larger
block
void free (void *);
Free
In use
Owned by OS
Free list
int *a = (int*) malloc(sizeof(int) * 10); //allocate
free(a);//free
free
The free function will add a previous used area of memory to the
free list
If it is adjacent to another free block these will be coalesced into a larger
block
void free (void *);
Free
In use
Owned by OS
Free list
int *a = (int*) malloc(sizeof(int) * 10); //allocate
free(a);//free
free
The free function will add a previous used area of memory to the
free list
If it is adjacent to another free block these will be coalesced into a larger
block
void free (void *);
Free
In use
Owned by OS
Free list
int *a = (int*) malloc(sizeof(int) * 10); //allocate
free(a);//free
free
The free function will add a previous used area of memory to the
free list
If it is adjacent to another free block these will be coalesced into a larger
block
void free (void *);
Free
In use
Owned by OS
Free list
int *a = (int*) malloc(sizeof(int) * 10); //allocate
free(a);//free
Memory operations
Set a block of memory to char value
void *memset(void *str, int c, size_t n)
Can be used to set any memory to a value (e.g. 0)
Useful as allocated memory has undefined values
int *a;
int size = sizeof(int) * 10;
a = (int*) malloc(size);
memset(a, 0, size);
Coping memory
void *memcpy(void *dest, const void *src, size_t n)
Copies n bytes of memory from src to dst
int *a;
int b[] = {1,2,3,4,5,6,7,8,9,10};
int size = sizeof(int) * 10;
a = (int*) malloc(size);
memcpy(a, b, size);
This Lecture
Pointers
Advanced use of pointers
Dynamically managed memory
Structures
Binary files
Structures
A structure is a collection of one or more variables
Variables may be of different types
Groups variables as a single unit under a single name
A structure is not the same as a class (at least in C)
No functions
No private members
No inheritance
Structures are defined using the struct keyword
Values can be assigned with an initialisation list or through structure member
operator ‘.’
struct vec{
int x;
int y;
};
struct vec v_1 = {123, 456};
struct vec v_2;
v_2.x = 123;
v_2.y = 456;
Features of structures
As with everything, structures are passed by value
struct vec make_vec(int x, int y){
struct vec v = {x, y};
return v;
}
Pointers to structures use a different member operator
‘->’ accesses member of a pointer to a struct
Alternatively dereference and use the standard operator ‘.’
struct vec v = {123, 456};
struct vec *p_vec = &v;//CORRECT
p_vec->x = 789;//CORRECT
p_vec.x = 789; //INCORRECT
Declarations and definition can be combined
struct vec{
int x;
int y;
} v1 = {123, 456};
Structure assignment
Structures can be assigned
Arithmetic operators not possible (e.g. vec_2 += vec_1)
struct vec vec_1 = {12, 34};
struct vec vec_2 = {56, 78};
vec_2 = vec_1;
BUT No deep copies of pointer data
E.g. if a person struct is declared with two char pointer members
(forename and surname)
struct person paul, imposter;
paul.forename = (char *) malloc(5);
paul.surname = (char *) malloc(9);
strcpy(paul.forename, “Paul”);
strcpy(paul.surname, “Richmond”);
imposter = paul; // shallow copy
strcpy(imposter.forename, “John”);
printf(“Forename=%s, Surname=%s\n”, paul.forename, paul.surname);
What is the Output?
Structure assignment
Structures can be assigned
Arithmetic operators not possible (e.g. vec_2 += vec_1)
struct vec vec_1 = {12, 34};
struct vec vec_2 = {56, 78};
vec_2 = vec_1;
BUT No deep copies of pointer data
E.g. if a person struct is declared with two char pointer members
(forename and surname)
struct person paul, imposter;
paul.forename = (char *) malloc(5);
paul.surname = (char *) malloc(9);
strcpy(paul.forename, “Paul”);
strcpy(paul.surname, “Richmond”);
imposter = paul; // shallow copy
strcpy(imposter.forename, “John”);
printf(“Forename=%s, Surname=%s\n”, paul.forename, paul.surname);
Forename=John, Surname=Richmond
Structure allocations
Structures can be allocated and assigned to a pointer
sizeof will return the combined size of all structure members
Better to pass big structures as pointers
struct vec *p_vec;
p_vec = (struct vec *) malloc(sizeof(struct vec));
//…
free(p_vec);
Structures passed as arguments have member variables values copied
If member is a pointer then pointer value copied not the thing that points to it
(shown on last slide)
Passing large structures by value can be quite inefficient
Type definitions
The keyword typedef can be used to create ‘alias’ for data types
Once defined a typedef can be used as a standard type
//declarations
typedef long long int int64;
typedef int int32;
typedef short int16;
typedef float vec3f [3];
//definitions
int32 a = 123;
vec3f vector = {1.0f, -1.0f, 0.0f};
typedef is useful in simplifying the syntax of struct definitions
struct vec{
int x;
int y;
};
typedef struct vec vec;
vec p1 = {123, 456};
This Lecture
Pointers
Advanced use of pointers
Dynamically managed memory
Structures
Binary files
Binary File Writing
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
size_t: size of single object
nmemb: number of objects
Returns the number of objects written (if not equal to nmemb then error)
void write_points(FILE* f, point *points){
fwrite(points, sizeof(point), sizeof(points) / sizeof(point), f);
}
void main(){
point points[] = { 1, 2, 3, 4 };
FILE *f = NULL;
f = fopen(“points.bin”, “wb”); //write and binary flags
write_points(f, points);
fclose(f);
}
Binary file reading
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
void read_points(FILE *f, point *points, unsigned int num_points){
fread(points, sizeof(point), num_points, f);
}
void main(){
point points[2];
FILE *f = NULL;
f = fopen(“points.bin”, “rb”); //read and binary flags
read_points(f, points, 2);
fclose(f);
}
Summary
Pointers and arrays are closely related
Using & and * we can manipulate memory
Pointers can point to variable definitions or functions
To dynamically allocate memory we can use malloc
Any memory allocated has to be free’d
Structures and typedefs allow us to create helpful storage units
Files can be written to with raw binary data