Блог пользователя I_love_Tanya_Romanova

Автор I_love_Tanya_Romanova, 13 лет назад, По-русски

Сегодня столкнулся с непонятным для меня проседанием по времени на GCC решения, которое нормально заходит под MSC++.

Есть код, в котором что-то примерно такое:

struct point{double x,y;};

vector<pair<point,long > > v;

bool cmp(pair<point, int> a,pair<point, int> b)
{
     if (a.first.x>b.first.x)return true;
     if (a.first.x<b.first.x)return false;
     if (a.first.y>b.first.y)return true;
     return false;
}

int main()
{
sort(v.begin(),v.end(),cmp);
}

Тестирование в "запуске" показало, что при отправке решения под GCC время исполнения превышает время исполнения под студией в 9-12 раз.

Если исправить типы pair<point,long > / pair<point, int> (чтобы пары по описанию точно совпадали в векторе и в сортировке, не важно, будет в обеих случаях int или long), то это ускоряет программу процентов на 30, но все равно время выполнения больше студийного в 6-8 раз.

Кто-то может объяснить мне причины такой ощутимой разницы?

UPD. Если переписать просто на вектор точек (заменить пары на сами точки), то все равно разница сохраняется.

  • Проголосовать: нравится
  • +13
  • Проголосовать: не нравится

»
13 лет назад, скрыть # |
 
Проголосовать: нравится +8 Проголосовать: не нравится

Изменится ли что-то, если сделать

inline bool cmp(const pair<point, long>& a, const pair<point, long>& b)

?

»
13 лет назад, скрыть # |
Rev. 3  
Проголосовать: нравится +8 Проголосовать: не нравится

или так еще попробуй

struct C {
    inline bool operator ()(const T& a, const T& b) const {
        //
    }
};

sort(v.begin(), v.end(), C());

А ну и причина в том, что происходит копирование объектов при передаче в функцию. а также вызов функции не инлайнится, поэтому долго получается.

  • »
    »
    13 лет назад, скрыть # ^ |
     
    Проголосовать: нравится 0 Проголосовать: не нравится

    Кстати по поводу inline, есть пример, в котором добавление этого слова реально даёт выигрыш при включенном O2?

    • »
      »
      »
      13 лет назад, скрыть # ^ |
       
      Проголосовать: нравится 0 Проголосовать: не нравится

      в рекурсиях нехило часто помогает

      • »
        »
        »
        »
        13 лет назад, скрыть # ^ |
         
        Проголосовать: нравится +3 Проголосовать: не нравится

        Я не спорю с этим утверждением, но если это действительно так, то не мог бы кто-нибудь объяснить почему?

        • »
          »
          »
          »
          »
          13 лет назад, скрыть # ^ |
           
          Проголосовать: нравится 0 Проголосовать: не нравится

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

    • »
      »
      »
      13 лет назад, скрыть # ^ |
      Rev. 2  
      Проголосовать: нравится +18 Проголосовать: не нравится

      У меня есть такой пример. Некоторое время назад решали Московский четвертьфинал 2009, и там была задача I, решающаяся dfs-ом огромной глубины (хотя, я чувствую, существует нормальное решение). Отправили, получили ML (он был 128 МБ), дописали inline к dfs, отправили, получили AC. Потом посмотрели: решение работало примерно в 2 раза быстрее и кушало примерно на 30 метров меньше памяти.

»
13 лет назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Если просто заинлайнить — никаких изменений.

Если изменить способ передачи в функцию, то очень заметное ускорение. Но все равно GCC далеко позади.

Для 500000 точек время выполнения было 0.45/3.5, стало 0.25/1.

»
13 лет назад, скрыть # |
Rev. 2  
Проголосовать: нравится +13 Проголосовать: не нравится

Возможно, Visual Studio с оптимизациями по умолчанию догадывается, что копирование point (1) не имеет побочных эффектов и (2) не требуется. И поэтому заменил объявление

bool cmp (pair <point, int> a, pair <point, int> b)

на

bool cmp (pair <point, int> const & a, pair <point, int> const & b)

У меня с mingw-g++ 4.7.2 это локально дало ускорение в несколько раз.

А по-хорошему, учитывая (1) и (2), можно заинлайнить всю функцию. Но у меня g++ наотрез отказывается это делать, даже с

inline bool cmp (pair <point, int> const & a, pair <point, int> const & b) __attribute__ ((always_inline));

профайлер показывает, что функция есть. Может, кто-то подскажет, как это делается правильно?

UPD: исходник тут.

  • »
    »
    13 лет назад, скрыть # ^ |
     
    Проголосовать: нравится 0 Проголосовать: не нравится

    Вот исходник после всех названных выше оптимизаций.

    Теперь я не могу понять, чем же мой код принципиально отличается. Потому как в примере выше — время выполнения примерно одинаковое.

    А мой код под студией все равно работает в 3.5 раза быстрее.

  • »
    »
    13 лет назад, скрыть # ^ |
     
    Проголосовать: нравится 0 Проголосовать: не нравится

    Почему не inline bool cmp(const pair <point, int>& a, const pair <point, int>& b)? И почему это эквивалентное объявление дает ускорение небольшое ?:/

    • »
      »
      »
      13 лет назад, скрыть # ^ |
      Rev. 2  
      Проголосовать: нравится +3 Проголосовать: не нравится

      На всякий случай замечу, что const T действительно должно быть эквивалентно T const. Запись T const (исправил) понятнее: если слева от const есть тип, то величину этого типа запрещается менять. Например, int const * const * x означает, что нельзя менять ни int, ни int *, а int * * менять можно. Ну а если слева от const ничего нет, ему приходится взять следующий тип справа.

      У меня const с любой стороны даёт одинаковую скорость. Возможно, случилась погрешность при измерении времени работы.

»
13 лет назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Кажется, вот еще одно ускорение : stable_sort вместо sort. Видимо, дело в специфике данных.

  • »
    »
    13 лет назад, скрыть # ^ |
     
    Проголосовать: нравится 0 Проголосовать: не нравится

    Можно как-то более конкретно — какая здесь специфика, и в чем отличия между двумя семействами компиляторов в данных условиях? Был бы рад понять это на идейном уровне, ведь не хотелось бы наступать на эти грабли опять, как только условия немножко изменятся:)

    • »
      »
      »
      13 лет назад, скрыть # ^ |
       
      Проголосовать: нравится 0 Проголосовать: не нравится

      Ну, утверждать не берусь, смотреть лень, но, видимо, в MSC++ всегда используется merge_sort, а в g++ sort() начинается с quick_sort, в то время как stable_sort также реализован на merge_sort (про g++ уверен на 95%). В случае, если изначально данные не совершенно случайные — есть частичная отсортированность, много равных элементов, стоимость swap-а очень высока — stable_sort работает быстрее, на случайных же данных quick_sort все-таки быстрее. Можно еще делать вот так: static bool cmp(pnt * a, pnt * b), тогда, если я правильно понимаю, стоимость свопа падает (и, кстати, это действительно дает еще небольшое ускорение, теперь тормозят new), но все равно stable_sort быстрее (почему?...). Вот еще ссылочка http://attractivechaos.wordpress.com/2008/08/28/comparison-of-internal-sorting-algorithms/

      • »
        »
        »
        »
        13 лет назад, скрыть # ^ |
         
        Проголосовать: нравится +3 Проголосовать: не нравится

        Спасибо:) Именно на ответ такого рода я и ждал. В свободное время надо будет поэкспериментировать — покормить ее сортированными/шафленными массивами и остальное в этом духе.

        И статья интересная)

        Остальным тоже спасибо, хоть я и спрашивал не "как ускорить то, что работает медленно", а "почему оно работает медленно", но тоже узнал много полезного.

      • »
        »
        »
        »
        13 лет назад, скрыть # ^ |
         
        Проголосовать: нравится 0 Проголосовать: не нравится

        Ну, утверждать не берусь, смотреть лень, но, видимо, в MSC++ всегда используется merge_sort Это не так. Везде в современных реализациях STL в std::sort используются вариации introsort.

»
13 лет назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

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

»
13 лет назад, скрыть # |
 
Проголосовать: нравится +14 Проголосовать: не нравится

забавно, если перед вызовом sort поставить

srand(time(0));
random_shuffle(v.begin(), v.end());

то время выполнения становится одинаковым, то есть на GCC быстрее