views:

251

answers:

6

I'm wondering with functions like the following, whether to use a temporary variable (p):

void parse_foo(const char*& p_in_out,
               foo& out) {
    const char* p = p_in_out;

    /* Parse, p gets incremented etc. */

    p_in_out = p;
}

or can I just use the original argument and expect it to be optimized similarly to the above anyway? It seems like there should be such an optimization, but I've seen the above done in a few places such as Mozilla's code, with vague comments about "avoiding aliasing".

+1  A: 

The variant with a temporary variable could be faster since it doesn't imply that every change to the pointer is reflected back to the argument and the compiler has better chances on generating faster code. However the right way to test this is to compile and look at the disassembly.

Meanwhile this has noting to do with avoiding aliasing. In fact, the variant with a temporary variant does employ aliasing - now you have two pointers into the same array and that's exactly what aliasing is.

sharptooth
I don't understand your reasoning for faster code.
Martin York
Well, it depends on the compiler dumbness. With a temporary it can allocate it on a register and only write to the argument once. Without a temporary it might decide to update the argument on each change.
sharptooth
But it could just as easily put the parameter in a register while the function executes.
Martin York
Actually there's more to it than dumbness -- it depends on whether the compiler can assume that nothing aliases the pointer or not.
Crashworks
+1  A: 

I would use a temporary if there is a possibility that the function is transactional.

i.e. the function succeeds or fails completely (no middle ground).
In this case I would use a temp to maintain state while the function executes and only assign back to the in_out parameter when the function completes successfully.

If the function exits prematurely (ie via exception) then we have two situations:

  • With a temporary (the external pointer is unchanged)
  • Using the parameter directly the external state is modified to reflect position.

I don't see any optimization advantages to either method.

Martin York
Good answer, this and sellibitze' answer are right on the money. It does depend on whether "fail completely" means abandon the input altogether (then just throwing an exception suffices), but where I need to recognize one of several alternatives in the input, properly transactional functions would definitely be the way to go. Thanks! (Sorry I can't upvote this yet.)
mjmt
A: 

One thought that comes immediately in mind: exception safety. If you throw an exception during parsing, the use of a temporary variable is what you should do to provide strong exception safety: Either the function call succeeded completely or it didn't do anything (from a user's perspective).

sellibitze
If that is what you require. If for example the function parses a stream you may expect the pointer to represent the point in the stream the exception happened.
Martin York
True. What I tried to bring across is that *if* the strong guarantee is what you want, the temporary pointer variable is the way to go. This "error position" could be part of the exception object, for example.
sellibitze
A: 
Crashworks
There is no "restrict" in C++, which is what the tag points to. That's a C99 thing, and I don't think it's going into C++0x.
David Thornley
Nonsense. Both GCC and MSVC support it in their current implementations.
Crashworks
+2  A: 

All good answers, but if you're worried about performance optimization, the actual parsing is going to take nearly all of the time, so pointer aliasing will probably be "in the noise".

Mike Dunlavey
Unless the parsing involves reading from and writing to the pointer a lot. Then its load-hit-stores would be in the inner loop.
Crashworks
@Crash: Hi again. You're right. If I'm tuning code like that, if I still think it's not fast enough, and that inner loop is the "bottleneck", then I will care about it, absolutely.
Mike Dunlavey
... when I write a parser, it's mostly spending time like this: `while(WHITE(*p)) p++;`, either that or looking up symbols in a table. It's usually not a performance issue unless the input is *massive*.
Mike Dunlavey
In this case, the LHS would be in the `p++` expression (because p itself is being written to by reference). Definitely it's something you'd need to measure before changing; it might be a big deal, might not. But it can be a fortyfold difference in how long that `p++` op takes, so in a tight loop like that, it would be significant.
Crashworks
I mention this just because I actually did get a huge speedup out of a parser once by doing this very thing.
Crashworks
@Crash: Boy, that's bare-metal coding!
Mike Dunlavey
@Crashworks: You're right, GCC even at -O3 is doing a store smack in the middle of a loop, strange that it's not deferred. @Mike: The thing here though is *p since p is a reference really means **p, and the difference doesn't seem to be optimized away completely (at least by GCC which is a dealbreaker). But yeah I know this isn't a big deal, I really just wanted to avoid writing my code one way or the other without understanding why. Thanks very much for the help.
mjmt
mjmt -- it's not odd that it's not deferred; it's what the compiler has to do unless you make it promises about pointer aliasing. see my cruelly downvoted answer at bottom.
Crashworks
A: 

How does the compiler know that p_in_out isn't aliased somehow? It really can't optimize away writing the data back through the reference.

struct foo {
    setX(int); setY(int); 
    const char* current_pos;
} x;
parse_foo(x.current_pos, x);


I look at this and ask why you didn't just return the pointer Then you don't have a reference to a pointer and you don't have to worry about modify the original.

const char* parse_foo(const char* p, foo& out) {
    //use p;
    return p;
}

It also means you can call the function with an rvalue:

p = parse_foo(p+2, out);
jmucchiello