Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > Исключения / Speedtest


Автор: SABROG 25.9.2008, 13:55
Изучая тему исключений решил проверить так ли страшны исключения как их малюют. Написал две тестовые програмки:

//exception.cpp
Код

#include <iostream>
#include <time.h>

bool func();

using namespace std;

int main(int argc, char *argv[])
{
    int numtest;
    if (2 > argc)
    {
        numtest = 100000000;
    }
    else
    {
        numtest = atoi(argv[1]);
    }
    volatile clock_t c1;
    volatile clock_t c2;


    volatile int value = 0;
    volatile double avg = 0;
    c1 = clock();
    for (volatile int i=0; i < numtest; i++)
    {
        try
        {
            func();
        }
        catch(bool)
        {
            value++;
        }
    }
    c2 = clock();
    avg+=double(c2-c1)/CLOCKS_PER_SEC;
    printf("%f\n", avg);
    return 0;
}

bool func()
{
    throw true;
    return true;
}




//standard.cpp
Код

#include <iostream>
#include <time.h>

bool func();

using namespace std;

int main(int argc, char *argv[])
{
    int numtest;
    if (2 > argc)
    {
        numtest = 100000000;
    }
    else
    {
        numtest = atoi(argv[1]);
    }
    volatile clock_t c1;
    volatile clock_t c2;


    volatile int value = 0;
    volatile double avg = 0;
    c1 = clock();
    for (volatile int i=0; i < numtest; i++)
    {
        if (func())
        {
            value++;
        }
    }
    c2 = clock();
    avg+=double(c2-c1)/CLOCKS_PER_SEC;
    printf("%f\n", avg);
    return 0;
}

bool func()
{
    return true;
}




Общая задача для двух программ - узнать об ошибке в функции и увеличить переменную. Т.е. пытаемся определить насколько исключения будут медленными, если одну и ту же функцию вызывать несколько миллионов раз. По умолчанию стоит 100 миллионов. Решил также сравнить как на циклы влияет оптимизация и компиляторы gcc и msvc между собой.

Код

                    -=[GCC]=-
g++ -o0 -o exception exception.cpp:
gccexception: 282 890 байт

(default 100000000 iterations)
>gccexception && gccexception && gccexception
102.001000
106.641000
109.376000

g++ -o3 -o exception exception.cpp:
gccexception: 282 890 байт

(default 100000000 iterations)
>gccexception && gccexception && gccexception
102.376000
105.501000
102.141000
-------------------------------------
g++ -o0 -o standard standard.cpp:
gccstandard: 279 306 байт

(default 100000000 iterations)
>gccstandard && gccstandard && gccstandard
0.968000
0.921000
0.937000

g++ -o3 -o standard standard.cpp:
gccstandard: 279 306 байт

(default 100000000 iterations)
>gccstandard && gccstandard && gccstandard
0.953000
0.953000
0.968000
-=-=-=-=-=-=-=-=-=-
                    -=[MSVC 2005]=-
cl /Od /EHsc exception.cpp /link /out:msvcexception.exe
msvcexception: 77 824 байт

(default 1000000 iterations) - 1 миллион против 100 миллионов на gcc.
>msvcexception && msvcexception && msvcexception
4.734000
5.062000
4.734000

cl /Ox /EHsc exception.cpp /link /out:msvcexception.exe

(default 1000000 iterations) - 1 миллион против 100 миллионов на gcc.
>msvcexception && msvcexception && msvcexception
gccexception: 77 824 байт
5.203000
4.812000
4.875000
-------------------------------------
cl /Od /EHsc standard.cpp /link /out:msvstandard.exe
msvcstandard: 69 632 байт

>msvcstandard && msvcstandard && msvcstandard
0.921000
0.906000
0.984000

cl /Ox /EHsc standard.cpp /link /out:msvstandard.exe
msvcstandard: 69 632 байт
>msvcstandard && msvcstandard && msvcstandard
0.562000
0.546000
0.562000
-=-=-=-=-=-=-=-=-=-


Из результатов видно, что оптимизация в gcc вообще никак не влияет на скорость. Зато в msvc оптимизация работает, но только для стандартных способов проверки. Т.е. оптимизации исключений не проводятся ни в gcc, ни в msvc.

При стандартном способе проверки (без exception'ов) оптимизация дает хороший прирост в скорости для msvc, по сравнению с gcc.

Однако с исключениями в msvc творится что-то странное, они ну очень тормозные по сравнению с gcc. Мне пришлось отказаться от 100 миллионов циклов в пользу 1 миллиона. Но если умножить лучший результат на 100, то мне бы пришлось ожидать 481 секунду. Терпения не хватило. Я не исключаю того, что возможно для msvc есть какие-то ключи для компилятора связаные с выбором реализации исключений. 

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

Вполне возможно, что я мог где-то накосячить в исходниках и все результаты можно пустить коту под хвост, поэтому буду рад конструктивной критике. Может быть я вообще не правильно использую исключения smile

Автор: Lazin 25.9.2008, 14:07
Цитата(SABROG @  25.9.2008,  13:55 Найти цитируемый пост)
Может быть я вообще не правильно использую исключения

ты их неправильно используешь smile 
Цитата(SABROG @  25.9.2008,  13:55 Найти цитируемый пост)

Код

bool func()
{
    throw true;
    return true;
}


дело в том, что обычно, программа редко занимается обработкой исключений, но во все функции способные бросить исключение (а в некоторых случаях во все функции), передается дополнительная информация, вот это и нужно измерять, а ты просто используешь исключения вместо return, а это не обычный способ их использования smile

Добавлено через 3 минуты и 44 секунды
то-есть когда используются исключения, в стек пихается дополнительная инфа, сам понимаешь что это дешевая операция, а вот обработка исключения это аварийная ситуация smile , здесь важно что-бы управление было передано куда надо и были вызваны деструкторы в правильном порядке, а не что-бы это произошло быстро

Автор: vinter 25.9.2008, 14:17
Цитата(Lazin @  25.9.2008,  15:07 Найти цитируемый пост)
а вот обработка исключения это аварийная ситуация

не всегда. И даже не в большинстве случаев.

Цитата(SABROG @  25.9.2008,  14:55 Найти цитируемый пост)
 volatile clock_t c1;    volatile clock_t c2;    volatile int value = 0;    volatile double avg = 0;

это как называется? "Какой плохой компилятор хватит оптимизировать?" smile

Автор: SABROG 25.9.2008, 14:17
Это понятно. Моей целью было замерить ту самую скорость из-за которой не советуют использовать исключения вместо кодов ошибок (напр. Bjarne Straustrup).

Добавлено через 3 минуты и 23 секунды
Цитата(vinter @ 25.9.2008,  14:17)
Цитата(Lazin @  25.9.2008,  15:07 Найти цитируемый пост)
а вот обработка исключения это аварийная ситуация

не всегда. И даже не в большинстве случаев.

Цитата(SABROG @  25.9.2008,  14:55 Найти цитируемый пост)
 volatile clock_t c1;    volatile clock_t c2;    volatile int value = 0;    volatile double avg = 0;

это как называется? "Какой плохой компилятор хватит оптимизировать?" smile

Я хотел оптимизации именно циклов (развертка циклов и т.п.).

Автор: vinter 25.9.2008, 14:41
SABROG, не занимайся ерундой, хочешь использовать - используй. А сидеть и пытаться вычислить наносекунду разницы это реально извращение.

Цитата(SABROG @  25.9.2008,  15:17 Найти цитируемый пост)
Я хотел оптимизации именно циклов 

volatile зачем юзаешь?

Автор: SABROG 25.9.2008, 15:15
Цитата(vinter @  25.9.2008,  14:41 Найти цитируемый пост)
SABROG, не занимайся ерундой, хочешь использовать - используй. А сидеть и пытаться вычислить наносекунду разницы это реально извращение.

Понимаешь в чем дело, мне нравятся исключения за то, что это хороший механизм не только для отладки, но и за более секьюрной идентификации ошибки вместо многоэтажных if'ов или case'ов. К тому же код выглядит более причесанным и легкочитаемым. И действительно хочется использовать исключения вместо кодов возвратов где только можно. Но везде пишут о том, какое это опасное оружие не в тех руках и при неправильном применении. Вот и хочется мне самому понять как все-таки стоит проектировать приложение с исключениями и в каких методых его стоит использовать. А если уж промахнусь, то кого винить за тормоза.

Цитата(vinter @  25.9.2008,  14:41 Найти цитируемый пост)

volatile зачем юзаешь? 


Занимаюсь копи-пастингом. Скопировал исходник из предыдущих тестов со строками ну и решил оставить, т.к. оптимизация или неоптимизация этих переменных ну никак не повлияет на скорость вызова функции и проверки последующего значения. Меня сейчас интересует не общая скорость выполнения функции, а дельта между использованием исключений и не использованием. А оптимизацию я включал и выключал, чтобы проверить оптимизирует ли как-нибудь компилятор сами исключения и циклы.

Может конечно и ерундой занимаюсь, но ведь придут и другие люди с тем же вопросом.

Автор: Lazin 25.9.2008, 15:32
1-й вариант - функция не бросает исключения

Код

#include <iostream>
#include <time.h>
bool func();
using namespace std;
bool global = false;
int main(int argc, char *argv[])
{
    int numtest;
    //if (2 > argc)
    //{
        numtest = 100000000;
    //}
    //else
    //{
    //    numtest = atoi(argv[1]);
    //}
    volatile clock_t c1;
    volatile clock_t c2;
    volatile int value = 0;
    volatile double avg = 0;
    c1 = clock();
    for (volatile int i=0; i < numtest; i++)
    {
        global = (i % 1000) == 0;
        try
        {
            func();
        }
        catch(bool)
        {
            value++;
        }
    }
    c2 = clock();
    avg+=double(c2-c1)/CLOCKS_PER_SEC;
    printf("%f\n", avg);
    return 0;
}
bool func()
{
    if(global)
        return false;
        //throw true;
    return true;
}

работает примерно 1.5 сек

второй вариант - ф-я бросает исключение при каждом 100000-м вызове
Код

#include <iostream>
#include <time.h>
bool func();
using namespace std;
bool global = false;
int main(int argc, char *argv[])
{
    int numtest;
    //if (2 > argc)
    //{
        numtest = 100000000;
    //}
    //else
    //{
    //    numtest = atoi(argv[1]);
    //}
    volatile clock_t c1;
    volatile clock_t c2;
    volatile int value = 0;
    volatile double avg = 0;
    c1 = clock();
    for (volatile int i=0; i < numtest; i++)
    {
        global = (i % 100000) == 0;
        try
        {
            func();
        }
        catch(bool)
        {
            value++;
        }
    }
    c2 = clock();
    avg+=double(c2-c1)/CLOCKS_PER_SEC;
    printf("%f\n", avg);
    return 0;
}
bool func()
{
    if(global)
        throw true;
    return true;
}

работает за 1.6 сек.

Добавлено через 1 минуту и 46 секунд
то есть реально исключение обрабатывается только 100 раз, и на скорость сама его обработка не влияет, 5%-я разница это и есть стоимость использования исключений

Автор: J0ker 25.9.2008, 18:44
проясняю ситуацию
в современной программе практически НЕВОЗМОЖНО избавиться от оверхеда, накладываемого исключениями
почему?
потому, что вы вызываете функции сторонних библиотек и те в свою очередь делают то-же самое.
т.о. вы можете объявить throw() весьма ограниченное число собственных функций, которые либо не вызывают других функций вообще (включая неявные) либо вызывают заведомо функции не бросающие исключения. Иначе вы рискуете остаться без вызова деструкторов локальных объектов, т.к. данная функция не будет добавлена в таблицу размотки исключения. Смею напомнить, что стандартный new может выбросить исключение.
Т.о. даже если вы НЕ ловите исключения (или ловите их в саом конце - в main) вы НЕ МОЖЕТЕ промежуточные функции объявит throw().

точнее тут
http://forum.vingrad.ru/forum/topic-229145/kw-exception.html

Автор: SABROG 25.9.2008, 23:02
Joker, вижу вроде написал что-то умное, но написал на каком-то, только тебе, понятном языке.

В принципе тема себя исчерпала. Я вижу, что люди почему-то встают на сторону исключений, хотя способ которым я их использовал является стандартным способом проверки ошибки. Факт остается фактом, как не используй исключения, а они медленней стандартных методов проверки. Другое дело, что в одном случае они медленней в 100 раз, а в другом на 5%, а это уже о чем то говорит.

Автор: UnrealMan 26.9.2008, 01:54
Механизм исключений по определению предназначен для обработки исключительных (т.е. редко возникающих) ситуаций. Если одна и та же ошибка то и дело возникает в каком-то цикле, то это уже не исключительная ситуация, и способы её обработки нужны соответствующие.

Автор: J0ker 26.9.2008, 02:30
Цитата(SABROG @  25.9.2008,  23:02 Найти цитируемый пост)
Joker, вижу вроде написал что-то умное, но написал на каком-то, только тебе, понятном языке.

ну если выразиться по простому - любая функция объявляющая локальные объекты требующие разрушения и (функция-же) содержащая после этого вызов(ы) не throw() функций - (как минимум) всегда содержит в прологе регистрацию в таблице размотки. Т.о. этот оверхед по времени и по объему кода неизбежен. Оверхед catch-а и по времени и по объему ничтожный. Плюс, конечно-же, оверхед по объему от наличия самого стека размотки.
Сам-же процесс обработки исключения не во всех компиляторах оптимизирован, и поэтому тесты дают гигантский разброс. Отсуствие хорошей оптимизации связано с тем, что механизм исключений это НЕ РУТИННЫЙ механизм завершения блока, и должен использоваться для индикации действительно исключительных ситуаций.
Вобщем я дуаю что оценка в 5% - вполне достоверна...

Автор: Lazin 26.9.2008, 05:32
Цитата(J0ker @  26.9.2008,  02:30 Найти цитируемый пост)
Вобщем я дуаю что оценка в 5% - вполне достоверна...

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

Автор: J0ker 26.9.2008, 17:35
Цитата(Lazin @ 26.9.2008,  05:32)
Цитата(J0ker @  26.9.2008,  02:30 Найти цитируемый пост)
Вобщем я дуаю что оценка в 5% - вполне достоверна...

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

просто надо учесть, что регистрация вызывается практически для каждой функции на каждом входе

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)