Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the differences between using array offsets vs pointer incrementation?

Tags:

c++

Given 2 functions, which should be faster, if there is any difference at all? Assume that the input data is very large

void iterate1(const char* pIn, int Size)
{
   for ( int offset = 0; offset < Size; ++offset )
   {
      doSomething( pIn[offset] );
   }
}

vs

void iterate2(const char* pIn, int Size)
{
   const char* pEnd = pIn+Size;
   while(pIn != pEnd)
   {
      doSomething( *pIn++ );
   }
}

Are there other issues to be considered with either approach?

like image 473
Snazzer Avatar asked Nov 02 '09 19:11

Snazzer


People also ask

Which is better to use pointer or array?

The main difference between Array and Pointers is the fixed size of the memory block. When Arrays are created the fixed size of the memory block is allocated. But with Pointers the memory is dynamically allocated.

What is array offset?

In computer science, an offset within an array or other data structure object is an integer indicating the distance (displacement) between the beginning of the object and a given element or point, presumably within the same object.

Are pointer references more efficient than array indexes?

Pointer arithmetic is actually about 30% faster than using array indexes.

Which is faster pointer or array?

Why ? pointers because it is direct memory access followed by dereferencing array - add current index to base address then dereferencing.


3 Answers

Chances are, your compiler's optimizer will create a loop induction variable for the first case to turn it into the second. I'd expect no difference after optimizations so I tend to prefer the first style because I find it clearer to read.

like image 82
Boojum Avatar answered Oct 20 '22 07:10

Boojum


Boojum is correct - IF your compiler has a good optimizer and you have it enabled. If that's not the case, or your use of arrays isn't sequential and liable to optimization, using array offsets can be far, far slower.

Here's an example. Back about 1988, we were implementing a window with a simple teletype interface on a Mac II. This consisted of 24 lines of 80 characters. When you got a new line in from the ticker, you scrolled up the top 23 lines and displayed the new one on the bottom. When there was something on the teletype, which wasn't all the time, it came in at 300 baud, which with the serial protocol overhead was about 30 characters per second. So we're not talking something that should have taxed a 16 MHz 68020 at all!

But the guy who wrote this did it like:

char screen[24][80];

and used 2-D array offsets to scroll the characters like this:

int i, j;
for (i = 0; i < 23; i++)
  for (j = 0; j < 80; j++)
    screen[i][j] = screen[i+1][j];

Six windows like this brought the machine to its knees!

Why? Because compilers were stupid in those days, so in machine language, every instance of the inner loop assignment, screen[i][j] = screen[i+1][j], looked kind of like this (Ax and Dx are CPU registers);

Fetch the base address of screen from memory into the A1 register
Fetch i from stack memory into the D1 register
Multiply D1 by a constant 80
Fetch j from stack memory and add it to D1
Add D1 to A1
Fetch the base address of screen from memory into the A2 register
Fetch i from stack memory into the D1 register
Add 1 to D1
Multiply D1 by a constant 80
Fetch j from stack memory and add it to D1
Add D1 to A2
Fetch the value from the memory address pointed to by A2 into D1
Store the value in D1 into the memory address pointed to by A1

So we're talking 13 machine language instructions for each of the 23x80=1840 inner loop iterations, for a total of 23920 instructions, including 3680 CPU-intensive integer multiplies.

We made a few changes to the C source code, so then it looked like this:

int i, j;
register char *a, *b;
for (i = 0; i < 22; i++)
{
  a = screen[i];
  b = screen[i+1];
  for (j = 0; j < 80; j++)
    *a++ = *b++;
}

There are still two machine-language multiplies, but they're in the outer loop, so there are only 46 integer multiplies instead of 3680. And the inner loop *a++ = *b++ statement only consisted of two machine-language operations.

Fetch the value from the memory address pointed to by A2 into D1, and post-increment A2
Store the value in D1 into the memory address pointed to by A1, and post-increment A1.

Given there are 1840 inner loop iterations, that's a total of 3680 CPU-cheap instructions - 6.5 times fewer - and NO integer multiplies. After this, instead of dying at six teletype windows, we never were able to pull up enough to bog the machine down - we ran out of teletype data sources first. And there are ways to optimize this much, much further, as well.

Now, modern compilers will do that kind of optimization for you - IF you ask them to do it, and IF your code is structured in a way that permits it.

But there are still circumstances where compilers can't do that for you - for instance, if you're doing non-sequential operations in the array.

So I've found it's served me well to use pointers instead of array references whenever possible. The performance is certainly never worse, and frequently much, much better.

like image 28
Bob Murphy Avatar answered Oct 20 '22 09:10

Bob Murphy


With modern compiler there shouldn't be any difference in performance between the two, especially in such simplistic easily recognizable examples. Moreover, even if the compiler does not recognize their equivalence, i.e. translates each code "literally", there still shouldn't be any noticeable performance difference on a typical modern hardware platform. (Of course, there might be more specialized platforms out there where the difference might be noticeable.)

As for other considerations... Conceptually, when you implement an algorithm using the index access you impose a random-access requirement on the underlying data structure. When you use a pointer ("iterator") access, you only impose a sequential-access requirement on the underlying data structure. Random-access is a stronger requirement than sequential-access. For this reason I, for one, in my code prefer to stick to pointer access whenever possible, and use index access only when necessary.

More generally, if an algorithm can be implemented efficiently through sequential access, it is better to do it that way, without involving the unnecessary stronger requirement of random-access. This might prove useful in the future, should a need arise to refactor the code or to change the algorithm.

like image 3
AnT Avatar answered Oct 20 '22 08:10

AnT