简体   繁体   中英

C++ optimization of reference-to-pointer argument

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".

所有好的答案,但是如果您担心性能优化,那么实际的解析将花费几乎所有时间,因此指针别名可能会“陷入困境”。

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.

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

ie 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.

Yes, you should assign it to a local that you mark restrict ( __restrict in MSVC).

The reason for this is that if the compiler cannot be absolutely sure that nothing else in the scope points at p_in_out , it cannot store the contents under the pointer in a local register. It must read the data back every time you write to any other char * in the same scope . This is not an issue of whether it is a "smart" compiler or not; it is a consequence of correctness requirements.

By writing char* __restrict p you promise the compiler that no other pointer in the same scope points to the same address as p . Without this guarantee, the value of *p can change any time any other pointer is written to, or it may change the contents of some other pointer every time *p is written to. Thus, the compiler has to write out every assignment to *p back to memory immediately, and it has to read them back after every time another pointer is written through.

So, guaranteeing the compiler that this cannot happen — that it can load *p exactly once and assume no other pointer affects it — can be an improvement in performance. Exactly how much depends on the particular compiler and situation: on processors subject to a load-hit-store penalty, it's massive; on most x86 CPUs, it's modest.

The reason to prefer a pointer to a reference here is simply that a pointer can be marked restrict and a reference cannot. That's just the way C++ is.

You can try it both ways and measure the results to see which is really faster. And if you're curious, I've written in depth on restrict and the load-hit-store elsewhere .

addendum : after writing the above I realize that the people at Moz were more worried about the reference itself being aliased -- that is, that something else might point at the same address where const char *p is stored, rather than the char to which p points. But my answer is the same: under the hood, const char *&p means const char **p , and that's subject to the same aliasing issues as any other pointer.

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM