lunes, 24 de noviembre de 2014

The Importance of Cache

Hi!

I recently posted a blog entry on data orientation, where one of the consequences of following this design mindset was a more optimal cache utilization.

Intrigued by the claims about how the memory is becoming the main bottleneck in current systems, I wanted to test it by myself, so I came up with a very simple C++ program.

#include <iostream>
#include <chrono>
const unsigned long int SIZE= 64*1024;

int main()
{
char array[SIZE];
char array2[SIZE];

auto start = std::chrono::system_clock::now();
for(int i = 0; i < 1000; ++i)
{
int aux = array[i];
}
auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now() - start);
std::cout << "Duration: " << duration.count() << std::endl;

auto start2 = std::chrono::system_clock::now();
for(int i = 0; i < 1000; ++i)
{
int aux = array2[i*64];
}
auto duration2 = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now() - start2);
std::cout << "Duration 2: " << duration2.count() << std::endl;

double res = (double)(duration2.count()) / (double)(duration.count());
std::cout << "First is " << res  << " faster" << std::endl;
}

I simply define and read two arrays of 64 KBs each, storing their values in a local variable. The difference is that in the first chunk of code I access memory sequentially, taking advantage of the cache, whereas in the second chunk I access in strides of 64 bytes, which in turn causes cache misses and more frequent access to main memory. 

The results are astonishing: the first chunk (the cache-friendly code) executes an average of 7.3 times faster. So yes! Memory is an important bottleneck in today's systems. 

See you!
FM

jueves, 13 de noviembre de 2014

Extern "C": Communicating with C interface through C++

Hi all!

Today, I bring you a short explanation, which is not directly tied to games programming, but just programming in general. However, I think it's something useful as you may find what I'll explain here in libraries that you may use in your games. 

The explanation is about the keyword extern for specifying C linkage. Note that the keyword "extern" has another meaning when it precedes variables or function names, but we're dealing here with a different use, in particular, with the following one:

extern "C"
{
   //Headers (.h) or functions declarations
}

With the above statement, we're telling the C++ compiler the following: "Listen guy, I have several functions (or headers, which in turn comprise several functions) that were compiled with a C compiler, but I want to be able to call these functions from my C++ code". 

And why would you need this? What's the difference that makes C and C++ unable to talk to each other natively? The problem is name mangling due to function overloading. C++ allows overloading functions, so the C++ compiler needs to use both the function name and arguments information in order to provide a unique identifier for the function. Since C does not allow overloading, it does not need to use name mangling. If you used name mangling, the C++ linker would look for function names that are mangled, but it wouldn't find them because the C compiler didn't mangle the names. So basically, when you use extern "C", you're deactivating the default C++ name mangling for the functions declared inside the block. 

A short example comes now. Suppose you have the following C header/implementation:

//mymath.h
#ifndef MYMATH_H
#define MYMATH_H

int mySum(int x, int y);

#endif

//mymath.c
#include "mymath.h"
int mySum(int x, int y)
{
   return (x + y);
}

Now, we compile this with a C compiler (suppose gcc):

gcc -c mymath.c

The above line generates an object file called mymath.o.

Now, let's make a simple C++ program that uses the above function like this:

//maincpp.cpp
#include <iostream>
#include "myMath.h"

int main()
{
  std::cout << "Sum in C yields: " << mySum(4, 2) << std::endl;
  return 0;
}

And now, let's compile with a C++ compiler:

g++ -c maincpp.cpp

This generates an object file called maincpp.o.

Now, let's try to build the executable:

g++ -o main maincpp.o mymath.o

The output of this line is:

Undefined symbols for architecture x86_64:
    "mySum(int, int)", referenced from:
         _main in maincpp.o
ld: symbol(s) not found for architecture x86_64

So basically, the linker is telling us that it is unable to find the function with name mySum(int, int). The way to solve it is by informing the C++ compiler that mySum is actually a C function, so that it generates the appropriate name for it that the linker can find. 

//maincpp.cpp
#include <iostream>
extern "C"
{
   #include "mymath.h"
}

int main()
{
  std::cout << "Sum in C yields: " << mySum(4, 2) << std::endl;
  return 0;
}

Now, after executing:

g++ -o main maincpp.o mymath.o

we get the expected result:

Sum in C yields: 6

Finally, it's very common (and convenient) to find the extern "C" statement wrapping header files as shown next:

#ifdef __cplusplus
extern "C" 
{
#endif

//Functions declarations

#ifdef __cplusplus
}
#endif

The symbol __cplusplus is defined only if the compiler is a C++ compiler. Therefore, suppose that we do the following in mymath.h:

//mymath.h
#ifndef MYMATH_H
#define MYMATH_H

#ifdef __cplusplus
extern "C" 
{
#endif

int mySum(int x, int y);

#ifdef __cplusplus
}
#endif

#endif

We can still compile mymath.c because __cpluplus is 0 and "extern" will not make it to the object file (C does not understand extern "C"). And what's more important, this allows any C++ client code to be oblivious about whether it should specify or not C linkage. So now, maincpp.cpp would be like this:

//maincpp.cpp
#include <iostream>
#include "mymath.h"

int main()
{
  std::cout << "Sum in C yields: " << mySum(4, 2) << std::endl;
  return 0;
}

And that's all! Hope you understand everything and find it useful!

See you!