代码之家  ›  专栏  ›  技术社区  ›  mjmt

指针指针引用的C++优化

  •  2
  • mjmt  · 技术社区  · 15 年前

    我想知道下面的函数是否使用临时变量(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;
    }
    

    或者我可以仅仅使用原始参数,并期望它被优化为与上述类似的结果吗?看起来应该有这样的优化,但是我在一些地方看到过,比如Mozilla的代码,其中有一些模糊的评论是关于“避免别名”。

    6 回复  |  直到 15 年前
        1
  •  2
  •   Mike Dunlavey    15 年前

    所有的答案都很好,但是如果你担心性能优化,实际的解析几乎要花掉所有的时间,所以指针别名可能会“在噪音中”。

        2
  •  1
  •   sharptooth    15 年前

    带有临时变量的变量可能更快,因为它并不意味着 每一个 指针的更改将反映回参数,编译器有更好的机会生成更快的代码。然而,正确的测试方法是编译并查看反汇编。

    同时,这与避免别名无关。事实上,变体 具有 一个临时变量确实使用了别名—现在您有两个指针指向同一个数组,这正是别名的含义。

        3
  •  1
  •   Loki Astari    15 年前

    如果函数可能是事务性的,我将使用临时函数。

    即功能完全成功或失败(没有中间立场)。
    在这种情况下,我将在函数执行时使用一个temp来维护状态,并且只在函数成功完成时才返回给in_out参数。

    如果函数过早退出(即通过异常),则有两种情况:

    • 使用临时指针(外部指针不变)
    • 直接使用该参数可以修改外部状态以反映位置。

    我认为这两种方法都没有任何优化优势。

        4
  •  1
  •   Crashworks    15 年前

    是的,你应该把它分配给你标记的本地人 restrict ( __restrict 在MSVC)。

    原因是如果 编译器不能绝对确定作用域中的任何其他内容都指向 p_in_out ,它无法将指针下的内容存储在本地寄存器中。它必须把数据读回 每次你给别人写信 char * 在同一范围内 . 这不是一个关于它是否是“智能”编译器的问题,而是正确性要求的结果。

    通过写作 char* __restrict p 你向编译器保证 同一作用域中没有其他指针指向与p相同的地址 . 如果没有这个保证 *p 可以在任何其他指针被写入时更改,也可以每次更改其他指针的内容 *P 是写给的。因此,编译器必须写出 *P 立即返回内存,每次写入另一个指针后,它都必须将它们读回。

    所以,保证编译器 这不可能发生 -它可以加载 *P 精确地一次,并且假设没有其他指针影响它-可以是性能的改进。具体多少取决于特定的编译器和情况:对于受到加载命中存储惩罚的处理器,它是巨大的;对于大多数x86 CPU,它是适度的。

    在这里选择指向引用的指针的原因仅仅是可以标记一个指针。 限制 引用不能。这就是C++的方式。

    你可以尝试这两种方法,并测量结果看哪个更快。如果你好奇, I've written in depth on restrict and the load-hit-store elsewhere .

    补遗 :在写了上面的文章之后,我意识到moz的人更担心引用本身被化名——也就是说,其他的东西可能指向同一个地址 const char *p 存储,而不是p所指向的字符。但我的回答是一样的:在引擎盖下, const char *&p 方法 const char **p ,这与任何其他指针都有相同的别名问题。

        5
  •  1
  •   jmucchiello    15 年前

    编译器怎么知道p_in_out不存在别名?它确实无法优化通过引用来回溯数据。

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

    我看着这个,问你为什么不直接返回指针,然后你就没有指针的引用,你也不必担心修改原始指针。

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

    它还意味着可以用rValk调用函数:

    p = parse_foo(p+2, out); 
    
        6
  •  0
  •   sellibitze    15 年前

    一个立即想到的想法是:异常安全。如果在解析期间抛出异常,那么应该使用临时变量来提供强异常安全性:要么函数调用完全成功,要么它什么也没做(从用户的角度)。