2015-04-02 2 views
0

Я работаю над презентацией, которая демонстрирует различные оптимизации на C++ и зацикливается на примере, где const позволяет им.G ++: Перемещение на другую единицу перевода прерывает «оптимизацию const»?

Рассмотрим следующий код:

object.h

class Object { 
    int i1; 
    int i2; 
public: 
    Object(int i1_, int i2_) : i1(i1_), i2(i2_) {} 

    int getI1() const { return i1; } 
    int getI2() const { return i2; } 

    std::pair<int, int> calculate() const { 
     return std::pair<int, int>(i1 + i2, i1 * i2); 
    } 
}; 

constopt.cpp

#include <iostream> 
#include "object.h" 

int main() { 
    Object o(10, 20); 

    std::cout << o.getI1() << " + " << o.getI2() << " = " 
       << o.calculate().first << std::endl 
      << o.getI1() << " * " << o.getI2() << " = " 
       << o.calculate().second << std::endl; 

    return 0; 
} 

Когда calculate() является встраиваемой, все отлично, G ++ непосредственно передает константы (10 и 20) до operator << кеширование getI1() и getI2() вызовов:

mov $0xa,%esi 
mov $0x601080,%edi 
callq 0x400740 <[email protected]> 

Но когда я перехожу calculate() в отдельный блок перевода, он вынуждает i1 и i2, чтобы быть извлечены дважды (до и после o.calculate().first):

mov (%rsp),%esi 
mov 0x4(%rsp),%r14d 
mov $0x601080,%edi 
callq 0x400740 <[email protected]> 

Я не вижу никакой разницы , потому что getI1() не полагается на какие-либо побочные эффекты, которые могут быть созданы calculate() и Object, должен быть const, даже если calculate() находится в отдельной единицы перевода.

Разве что G ++ недостаточно умен или он не может выполнять оптимизацию в таких случаях? Мое предположение состоит в том, что он может кэшировать getI1() и getI2() звонки пришли из этого ответа: How does const after a function optimize the program?

Я использую gcc версию 4.8.1. Я пробовал оба -O и -O2.


Кажется, что const не используется в GCC оптимизатора в этом случае. Вместо этого он выполняет собственное копание (которое не может выполняться в разных единицах перевода) и ищет pure and const functions. Функции могут быть отмечены вручную с помощью __attribute__((const)). Фаза, которая устраняет дополнительные вызовы getI*(), называется FRE (Полное устранение избыточности).

+0

Возможный дубликат http://stackoverflow.com/questions/9767395 –

+1

Составители не оптимизируются на основе константы. Константа только там, чтобы помочь разработчикам. 'const_cast' всегда присутствует, и компилятор не имеет возможности узнать об этом. – sbabbi

+0

@sbabbi Можете ли вы найти ссылку для этого? Я был бы очень удивлен, если это так. – Shade

ответ

1

потому getI1() не зависит от каких-либо побочных эффектов, которые могут быть созданы стоимость()

Это может сделать, делает ли она или не остается не определен в стандарте C++.

Несмотря на то, что вы пишете getI1() и getI2() потоку перед вызовом calculate(), в C++ порядок оценки аргументов функции не указан. Это означает, что компилятор разрешает делать все звонки calculate() и getI1() и getI2() в любом порядке, прежде чем делать какие-либо фактические записи до std::cout. Если он решает сделать один или оба вызова до calculate() до вызовов getI1() или getI2(), то он не может предположить, что значения i1 и i2 не изменились.

Если разделить выражение вверх, то компилятор должен быть в состоянии видеть, что i1 и i2 не может измениться, пока призывы calculate():

// this statement uses i1 and i2 before they can possibly be changed 
std::cout << o.getI1() << " + " << o.getI2() << " = "; 

// this statement might mutate i1 and i2 
std::cout << o.calculate().first << std::endl 
     << o.getI1() << " * " << o.getI2() << " = " 
      << o.calculate().second << std::endl; 

и объект предназначен быть константной даже при высчитывает () находится в отдельной единицы перевода.

Но вы на самом деле не объявили oconst.

Вызов функции в const член не гарантирует, что члены не могут быть изменен, это более неформальный контракт между вызывающим абонентом и функции, а не гарантия.

+0

Я, хотя это отбрасывание 'const' - это UB (который оставляет руки компилятора развязаны), и что изменяемые объекты должны быть' volatile', но я, вероятно, ошибаюсь. > Но вы на самом деле не заявляли o быть const.<Да, это фактически позволило кэшировать значения 'getI1()' в регистрах (я переместил код в 'void print (const Object & o)')! – myaut

+0

Отбрасывание 'const' - это UB, если объект _really_' const' i.e объявлен 'const' в первую очередь. Нет причин использовать 'volatile' для' mutable' членов, они не связаны. –

+0

О, теперь у нас есть 'const' и _really_' const' объекты, oh, C++! Во всяком случае, спасибо за ответ, он как очистил мой разум, так и помог с моей презентацией. – myaut

2

Ну, чтобы точно ответить на ваш вопрос, нужно знать paticulars реализации компилятора. Я этого не делаю, так что это просто гипотеза.

Object::i1 и Object::i2 не объявлены как const. Поэтому компилятор, не видя определения Object::calculate, обязан предположить, что они могут измениться.

Object::calculate самосопряжение само по себе не позволяет ему вносить изменения в Object::i1 и Object::i2. Рассмотрим следующий слегка измененный пример:

class Object { 
    int i1; 
    int i2; 
    int* p1; 
public: 
    Object(int i1_, int i2_) : i1(i1_), i2(i2_), p1(&i1) {} 

    int getI1() const { return i1; } 
    int getI2() const { return i2; } 

    std::pair<int, int> calculate() const; 
}; 

std::pair<int, int> Object::calculate() const { 
    (*p1)++; 
    return std::pair<int, int>(i1 + i2, i1 * i2); 
} 

Кроме того, o не объявлен сопзЬ в первую очередь, и поэтому Object::calculate имеет право делать что-то, как грубо, как const_cast и уйти с ним!

Смежные вопросы