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

后端 未结 11 1190
醉话见心
醉话见心 2021-02-06 13:39

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)
{         


        
11条回答
  •  孤独总比滥情好
    2021-02-06 14:22

    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.

提交回复
热议问题