Портирование проекта C ++ с VS 6.0 на VS 2010 привело к замедлению кода - PullRequest
1 голос
/ 03 марта 2012

Я перенес один проект из Visual C ++ 6.0 в VS 2010 и обнаружил, что критическая часть кода (механизм сценариев) теперь работает примерно в три раза медленнее, чем была раньше. После некоторых исследований мне удалось извлечь фрагмент кода, который, кажется, вызывает замедление. Я максимально уменьшил это, так что будет легче воспроизвести проблему. Проблема воспроизводится при назначении сложного класса (Variant), который содержит другой класс (String), и объединении нескольких других полей простых типов.

Играя на примере, я обнаружил еще одну «магию»: 1. Если я прокомментирую один из неиспользуемых (!) Членов класса, скорость возрастет, и код, наконец, будет работать быстрее, чем те, которые соответствуют VS 6.2 2. То же самое верно, если я удаляю «обертку» 3. То же самое верно и в случае изменения значения поля с 1 на 0

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

Пример кода ниже: На моем процессоре Intel 2,53 ГГц этот тест, скомпилированный под VS 6.2, выполняется 1,0 секунды. Составлено под VS 2010 - 40 секунд Скомпилировано под VS 2010 с прокомментированными «волшебными» строками - 0,3 секунды.

Проблема воспроизводится при любом переключении оптимизации, но «Оптимизация всей программы» (/ GL) должна быть отключена. В противном случае этот слишком умный оптимизатор будет знать, что наш тест на самом деле ничего не делает, и тест будет выполняться 0 секунд.

#include        <windows.h>
#include        <stdio.h>
#include        <stdlib.h>

class String
{
public:
    char    *ptr;
    int     size;

    String() : ptr(NULL), size( 0 ) {};
    ~String() {if ( ptr != NULL ) free( ptr );};
    String& operator=( const String& str2 );
};

String& String::operator=( const String& string2 )
{
    if ( string2.ptr != NULL )
    {
        // This part is never called in our test:
        ptr = (char *)realloc( ptr, string2.size + 1 );
        size = string2.size;
        memcpy( ptr, string2.ptr, size + 1 );
    }
    else if ( ptr != NULL )
    {
        // This part is never called in our test:
        free( ptr );
        ptr = NULL;
        size = 0;
    }

    return *this;
}


struct Date
{
    unsigned short          year;
    unsigned char           month;
    unsigned char           day;
    unsigned char           hour;
    unsigned char           minute;
    unsigned char           second;
    unsigned char           dayOfWeek;
};


class Variant
{
public:
    int             dataType;
    String          valStr; // If we comment this string, the speed is OK!

    // if we drop the 'union' wrapper, the speed is OK!
    union
    {
        __int64     valInteger;

        // if we comment any of these fields, unused in out test, the speed is OK!
        double      valReal;
        bool        valBool;
        Date        valDate;
        void        *valObject;
    };

    Variant() : dataType( 0 ) {};
};


void TestSpeed()
{
    __int64             index;
    Variant             tempVal, tempVal2;

    tempVal.dataType = 3;
    tempVal.valInteger = 1; // If we comment this string, the speed is OK!

    for ( index = 0; index < 200000000; index++ )
    {
        tempVal2 = tempVal;
    }
}

int main(int argc, char* argv[])
{
    int         ticks;
    char        str[64];

    ticks = GetTickCount();

    TestSpeed();

    sprintf( str, "%.*f", 1, (double)( GetTickCount() - ticks ) / 1000 );

    MessageBox( NULL, str, "", 0 );

    return 0;
}

1 Ответ

0 голосов
/ 03 марта 2012

Это было довольно интересно.Сначала я не смог воспроизвести замедление в сборке релиза, только в отладочной сборке.Затем я выключил оптимизацию SSE2 и получил те же ~ 40 с времени выполнения.

Проблема, похоже, заключается в назначении компилятором сгенерированного копирования для Variant.Без SSE2 он фактически выполняет копирование с плавающей запятой с инструкциями fld / fstp, поскольку объединение содержит двойное число.И с некоторыми конкретными значениями это, очевидно, очень дорогая операция.64-разрядное целочисленное значение 1 отображается на 4.940656458412e-324#DEN, которое является денормализованным числом, и я считаю, что это вызывает проблемы.Когда вы оставляете tempVal.valInteger неинициализированным, оно может содержать значение, которое работает быстрее.

Я провел небольшой тест, чтобы подтвердить это:

union {
    uint64_t i;
    volatile double d1;
};
i = 0xcccccccccccccccc; //with this value the test takes 0.07 seconds
//i = 1; //change to 1 and now the test takes 36 seconds
volatile double d2;

for(int i=0; i<200000000; ++i)
    d2 = d1;

Итак, вы можете определить свою собственную копиюназначение для Variant, который просто делает простой memcpy для объединения.

Variant& operator=(const Variant& rhs)
{
    dataType = rhs.dataType;
    union UnionType
    {
        __int64     valInteger;
        double      valReal;
        bool        valBool;
        Date        valDate;
        void        *valObject;
    };
    memcpy(&valInteger, &rhs.valInteger, sizeof(UnionType));
    valStr = rhs.valStr;
    return *this;
}
...