Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Здравствуйте, Lazin, Вы писали:
EP>>>Но на C++, по крайней мере, есть выбор. Я могу принимать итераторы через шаблоны, а могу через type-erasure, а-ля any_iterator. И оптимизировать под то, что важнее. L>>Это С++, если ф-я инлайнится, то ее тело все равно должно быть в результирующем коде, так как ее может использовать другой модуль.
EP>Что ты имеешь ввиду? Функция вида: EP>
EP>void foo(any_iterator x);
EP>
компилируется один раз, это не шаблон. Её даже можно вынести в динамическую библиотеку.
EP>>>Часто скорость важнее. Я даже не помню когда последний раз видел -Os — везде -O2/-O3. L>>Иногда -Os (и -Oa, за компанию) бывает даже быстрее чем -O3, у меня так было в одном из проектов
EP>Иногда такое бывает из-за того, что march/mtune выставлено на GCD (например вместо native), а не из-за размера кода.
L>>>>К тому же, польза от инлайна на x64 и на x86 с соглашением о вызове __fastcall будет не такой уж и большой, параметры в ф-ю будут переданы через регистры, так как их всего два. EP>>>Кого два? Инлайнятся функции с разным количеством параметров. L>>В случае предиката сортировки, передается всего два параметра.
EP>Так мы же про общий случай, сортировка — это просто иллюстрация.
qsort какой то неудачный пример — функция стандартная, и ей "сто лет в обед", и есть масса других вариантов библиотек сортировки.
В контр-пример можно привести, что printf в большинстве случаев работает быстрее std::cout (можно глянуть на тесты boost::format — там это замечательно видно).
На C можно тоже исходники чистыми хедерниками писать.
И втюхивать в начало каждой функции, как inline, так и static inline, ну и (непотребный конечно вариант, но все таки хоть какая то замена шаблонам из C++) #define.
На выходе получишь те же самые свернутые в одну функцию полезные действия.
И опять же под линуксом Firefox (всеми горячо любимым gcc) официально собирается с -Os, и он правда в такой варианте сборки работает быстрее, чем с -O2/-O3.
L>>Они легко могут передаваться через регистры процессора, не затрагивая память.
EP>Через память, точнее кэш, будет передаваться RSP. Но проблема даже не в этом. EP>А в том, что теряется возможность сделать оптимизацию заинлайненного кода (зачастую многоуровневую). И плюс дополнительные инструкции. EP>Более того, если попытаться сделать аналог std::sort для C, тот там будет ещё пара косвенных вызовов из-за: EP> произвольного типа последовательности EP> произвольного типа элемента (а не только POD, который можно swap-byte-ить)
EP>>>Конечно же правильный выбор алгоритмов это отличительная особенность C программистов. "Ну и что, что язык медленный — мы вас меньшей сложностью заборим!" L>>Я не троллю, просто типичный обитатель С++ тредов как правило любит рассказывать о том как все круто инлайнится и от этого все быстро работает, а в этом вашем Си quicksort указатель на функцию вообоще дергает, это common sense.
EP>Так действительно всё круто инлайнится, сам неоднократно убеждался И сортировка это лишь конкретный пример.
L>>Но при этом, наш гипотетический гуру программирования ни разу в жизни не пытался оптимизировать сортировку, выбором алгоритма под конкретные данные
EP>Я понятия не имею о каком "гипотетическом гуру" ты говоришь. EP>Это выглядит как: "вот я знаю сиплюсплюскинка — так он вообще сортировки выбирать не умеет. а вот сишники — другое дело, их медленный qsort буквально подталкивает к правильному выбору алгоритма"
L>>Стандартную библиотеку С++ писали лучшие умы человечества, поэтому мы будем использовать ее и только ее везде, чтобы было быстро
EP>Не скажу за всю стандартную библиотеку, но STL действительно писали лучшие умы. По крайней мере это самая лучшая стандартная алгоритмическая библиотека, с самым лучшим интерфейсом и набором алгоритмов. EP>Тех же сортировок там три штуки: интроспективная (которая сочетает в себе три типа сортировки), пирамидальная, сортировка слиянием (причём адаптивная, а не в лоб). EP>Плюс алгоритмы связанные с сортировкой: разбиение множества (три штуки — разбиение Энтони Хоара, Нико Ломуто и адаптивное стабильное), слияние (две штуки: out-of-place и адаптивное in-place), поворот (out-of-place и in-place(привет Евклиду)), выбор k-го элемента (причём с полезной перестановкой остальных элементов), бинарный поиск (пять штук) и т.д.
EP>>>Happy-path намного более вероятный — и оптимизировать нужно именно его. Кстати, с кодами ошибок будет засорение всего happy-path runtime проверками. L>>Если в коде куча конструкций вроде if (status != SUCCESS) ... и при этом вероятность happy path выше в разы, то branch predictor будет очень точно угадывать ветвления и обработка ошибок практически не будет оказывать влияния на время выполнения.
EP>Естественно он будет угадывать, особенно после первого прогона. EP>Фишка в том, что в случае исключений ему даже угадывать ничего не придётся — cmp+je/whatever не будут разбросаны по всему happy path.
Какая то подтасовка фактов тут описана.
Проверка на неудачно совершенную операцию в случае с C++ exception все равно есть.
И если операция совершилась неудачно, только в этом случае происходит throw.
Видимо имеется ввиду, когда произошла куча вызовов функций в глубину, и на каждом возврате из вызова функции происходит проверка результата?
Но ведь это замечательно рефакторится (и без всяких goto) разбиением общей функции с ветвлениями на две:
— первая функция последовательные действия с проверкой результата от одиночного действия и в случае неудачи возврат во вторую функцию.
— вторая функция проверяет результат первой и делает то что надо.
static inline
int level1_get_some(int h)
{
int rc;
rc = get_u(h);
if (0 != rc) return rc;
rc = get_v(h);
if (0 != rc) return rc;
rc = get_i(h);
if (0 != rc) return rc;
...
return 0;
}
int level2_get_some(int h)
{
switch(level1_get_some(h))
{
case 0: return 0;
case ....: some_fail_action(...); break;
}
return ...;
}
Заверни rc = action(...); if (0 != rc) в какой нибудь макрос #define do_action(action) ... — получишь компактность. Прикрути static inline и свернется вся эта портянка в одиночный вызов без хитрых лишних переходов cmp+je через каждый call.
В общем все это делается, было бы желание...
Раньше были спецы, про которых говорили: "пишут не на C++, а на C с классами".
Теперь пора пускать в обиход выражение: "пишут на C, как на C++ без классов"
И это компилируется в компактный простой ассемблерный листинг с jmp/jne/je.. скачками в пределах файла.
В случае же использования С++ try/catch сплошные call XXXXXXX
в дебри либ и фреймворков, которые вызываются по ходу выполнения потока, постоянно прерывая его.
AF>Раньше были спецы, про которых говорили: "пишут не на C++, а на C с классами". AF>Теперь пора пускать в обиход выражение: "пишут на C, как на C++ без классов"
Вся прога на С++, с критичными участками на C++ без его классов и ништяков, которые
не бесплатны, и забирают память и процессор.
Здравствуйте, andrew.f, Вы писали:
EP>>Так мы же про общий случай, сортировка — это просто иллюстрация. AF>qsort какой то неудачный пример — функция стандартная, и ей "сто лет в обед",
AF>и есть масса других вариантов библиотек сортировки.
Покажи хотя бы пример интерфейса сортировки на C, которая будет принимать любой предикат, любой тип последовательности, с любыми элементами.
AF>В контр-пример можно привести, что printf в большинстве случаев работает быстрее std::cout (можно глянуть на тесты boost::format — там это замечательно видно).
Пример не равноценный — std::cout медленный потому что много чего делает, и имеет customization points.
А qsort медленный — потому что интерфейс такой
AF>На C можно тоже исходники чистыми хедерниками писать. AF>И втюхивать в начало каждой функции, как inline, так и static inline, ну и (непотребный конечно вариант, но все таки хоть какая то замена шаблонам из C++) #define. AF>На выходе получишь те же самые свернутые в одну функцию полезные действия.
Покажи интерфейс сортировки, или например rbtree.
AF>И опять же под линуксом Firefox (всеми горячо любимым gcc) официально собирается с -Os, и он правда в такой варианте сборки работает быстрее, чем с -O2/-O3.
Здравствуйте, andrew.f, Вы писали:
EP>>Фишка в том, что в случае исключений ему даже угадывать ничего не придётся — cmp+je/whatever не будут разбросаны по всему happy path. AF>Какая то подтасовка фактов тут описана.
Где?
AF>Проверка на неудачно совершенную операцию в случае с C++ exception все равно есть.
Очевидно, что если такая проверка только в самом низу call-tree, то в случае исключений будет один if. В то время как на C error_code придётся протаскивать наверх через всю call-tree-branch, по всему объёму дерева
AF>И если операция совершилась неудачно, только в этом случае происходит throw. AF>Видимо имеется ввиду, когда произошла куча вызовов функций в глубину, и на каждом возврате из вызова функции происходит проверка результата?
Да.
AF>Но ведь это замечательно рефакторится (и без всяких goto) разбиением общей функции с ветвлениями на две: AF>- первая функция последовательные действия с проверкой результата от одиночного действия и в случае неудачи возврат во вторую функцию. AF>- вторая функция проверяет результат первой и делает то что надо.
Во-первых у тебя остались те же jump'ы — switch/case, причём даже менее эффективные чем goto.
Во-вторых от того что ты спрятал runtime проверки в отдельную функцию — они быстрее не стали. Они всё также остались разбросанными по всему happy-path.
Вот смотри, в случае исключений будет:
if(error)
throw something();
в самом низу call-tree, у тебя же будут проверки (runtime ветвления) по всему call-tree
Здравствуйте, smeeld, Вы писали:
S>И это компилируется в компактный простой ассемблерный листинг с jmp/jne/je.. скачками в пределах файла.
Этот вариант даже медленнее чем вариант с goto.
S>В случае же использования С++ try/catch сплошные call XXXXXXX S> в дебри либ и фреймворков, которые вызываются по ходу выполнения потока, постоянно прерывая его.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
AF>В контр-пример можно привести, что printf в большинстве случаев работает быстрее std::cout (можно глянуть на тесты boost::format — там это замечательно видно).
что за бред?!
с каких таких пор эта сишная всезаглатывающая кишка стала быстрее плюсовых потоков?!
#include <cstdio>
#include <iostream>
#include <chrono>
enum io_type { test_printf, test_stdio };
template<io_type>
struct test;
template<>
struct test<test_printf> {
template<typename T>
static std::chrono::milliseconds
run(const T *arr, std::size_t size) {
auto start = std::chrono::system_clock::now();
for ( ; size; --size ) {
printf("%d", *arr++);
}
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now()-start);
}
};
template<>
struct test<test_stdio> {
template<typename T>
static std::chrono::milliseconds
run(const T *arr, std::size_t size) {
auto start = std::chrono::system_clock::now();
for ( ; size; --size ) {
std::cout << *arr++;
}
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now()-start);
}
};
int main() {
enum { size = 1024*1024*4 };
int *ia = new int[size];
auto t1 = test<test_printf>::run(ia, size);
std::cerr << "printf time =" << t1.count() << std::endl;
auto t2 = test<test_stdio >::run(ia, size);
std::cerr << "stdio time =" << t2.count() << std::endl;
}
запускаем:
./iospeed 1>/dev/null
получаем:
printf time =323
stdio time =183
да и как вообще может быть такое, чтоб printf() который вообще ничего не знает о типе, выполнялся быстрее типизированного кода?
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Во-первых у тебя остались те же jump'ы — switch/case, причём даже менее эффективные чем goto. EP>Во-вторых от того что ты спрятал runtime проверки в отдельную функцию — они быстрее не стали. Они всё также остались разбросанными по всему happy-path. EP>Вот смотри, в случае исключений будет: EP>
EP>if(error)
EP> throw something();
EP>
в самом низу call-tree, у тебя же будут проверки (runtime ветвления) по всему call-tree
if-ы и switch для редко выполняющихся условий, это практически zero overhead. Мало того, switch для кодов ошибок (когда коды ошибок выбраны правильно), компилится в switch table, который работает намного быстрее чем throw catch. Т.е. код, выбирающий обработчик для кода ошибки у компилятора получится более быстрым, нежели код для try catch.. catch.. catch, причем намного. Я уже молчу про локальность этого кода
L>if-ы и switch для редко выполняющихся условий, это практически zero overhead. Мало того, switch для кодов ошибок (когда коды ошибок выбраны правильно), компилится в switch table, который работает намного быстрее чем throw catch. Т.е. код, выбирающий обработчик для кода ошибки у компилятора получится более быстрым, нежели код для try catch.. catch.. catch, причем намного. Я уже молчу про локальность этого кода
т.е. ты хочешь сказать, что обработка исключительных ситуаций это штатное выполнение программы?
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Здравствуйте, Lazin, Вы писали:
L>if-ы и switch для редко выполняющихся условий, это практически zero overhead.
Этот "практически zero overhead" будет разбросан по всему коду (который к тому же забивает prediction table).
L>Мало того, switch для кодов ошибок (когда коды ошибок выбраны правильно), компилится в switch table, который работает намного быстрее чем throw catch.
switch table будет медленнее оригинальных goto. Я вообще не вижу смысл в переводе безусловного goto на switch в контексте обсуждения производительности
L>Т.е. код, выбирающий обработчик для кода ошибки у компилятора получится более быстрым, нежели код для try catch.. catch.. catch, причем намного. Я уже молчу про локальность этого кода
Возможно выбор действия при ошибке будет и быстрее, но по всему happy-path размазывается penalty в виде if'ов.
Здравствуйте, niXman, Вы писали:
L>>if-ы и switch для редко выполняющихся условий, это практически zero overhead. Мало того, switch для кодов ошибок (когда коды ошибок выбраны правильно), компилится в switch table, который работает намного быстрее чем throw catch. Т.е. код, выбирающий обработчик для кода ошибки у компилятора получится более быстрым, нежели код для try catch.. catch.. catch, причем намного. Я уже молчу про локальность этого кода
X>т.е. ты хочешь сказать, что обработка исключительных ситуаций это штатное выполнение программы?
Я ничего такого не хотел сказать. Посмотри на что я отвечал и что я написал в ответ. Мой поинт в том, что runtime проверки кодов ошибок могут вообще не сказываться на времени работы кода, все. Зачем обобщать?
Здравствуйте, Evgeny.Panasyuk, Вы писали:
L>>Мало того, switch для кодов ошибок (когда коды ошибок выбраны правильно), компилится в switch table, который работает намного быстрее чем throw catch. EP>switch table будет медленнее оригинальных goto. Я вообще не вижу смысл в переводе безусловного goto на switch в контексте обсуждения производительности
Я не предлагаю заменять goto на switch. Просто сравниваю два способа обработки ошибок — throw catch и код ошибки + switch для последующей обработки.
L>>Т.е. код, выбирающий обработчик для кода ошибки у компилятора получится более быстрым, нежели код для try catch.. catch.. catch, причем намного. Я уже молчу про локальность этого кода EP>Возможно выбор действия при ошибке будет и быстрее, но по всему happy-path размазывается penalty в виде if'ов.
Несомненно размазывается. Я не утверждаю, что коды ошибок всегда быстрее, я лишь утверждаю что можно написать так, что никакого penalty не будет, я даже небольшой пример написал
#include <iostream>
#include <boost/timer.hpp>
using namespace std;
int main()
{
const int num_samples = 200000000;
float* A = new float[num_samples];
float* B = new float[num_samples];
float* C = new float[num_samples];
boost::timer timer;
for (int i = 0; i < num_samples; i++) {
A[i] = 0.f;
B[i] = 1.f;
C[i] = 0;
}
double result = timer.elapsed();
std::cout << "rewrite of " << (3*num_samples*sizeof(float)/(1024*1024)) << " Mb takes " << result << std::endl;
timer.restart();
for (int i = 0; i < num_samples; i++) {
C[i] += A[i] + B[i];
}
result = timer.elapsed();
std::cout << "without branch: " << result << std::endl;
timer.restart();
for (int i = 0; i < num_samples; i++) {
if (i == -1)
break;
if (i == -2)
break;
if (i == -3)
break;
if (i == -4)
break;
if (i == -5)
break;
if (i == -6)
break;
if (i == -7)
break;
if (i == -8)
break;
if (i == -9)
break;
C[i] += A[i] + B[i];
}
result = timer.elapsed();
std::cout << "with branch: " << result << std::endl;
}
На моей машине, оба цикла отрабатывают за одно и то же время, по причине того, что branch predictor успешно предсказывает все ветвления в коде, по причине того, что i никогда не принимает отрицательные значения.
Здравствуйте, Lazin, Вы писали:
L>На моей машине, оба цикла отрабатывают за одно и то же время, по причине того, что branch predictor успешно предсказывает все ветвления в коде, по причине того, что i никогда не принимает отрицательные значения.
Хотя возможно, причина в том, что оптимизатор сводит это все к одному branch-у, я не смотрел disassembly.
Здравствуйте, Lazin, Вы писали:
L>>>Мало того, switch для кодов ошибок (когда коды ошибок выбраны правильно), компилится в switch table, который работает намного быстрее чем throw catch. EP>>switch table будет медленнее оригинальных goto. Я вообще не вижу смысл в переводе безусловного goto на switch в контексте обсуждения производительности L>Я не предлагаю заменять goto на switch. Просто сравниваю два способа обработки ошибок — throw catch и код ошибки + switch для последующей обработки.
В реальности есть не один код ошибки, а много — и вместо switch нужен goto.
L>На моей машине, оба цикла отрабатывают за одно и то же время, по причине того, что branch predictor успешно предсказывает все ветвления в коде, по причине того, что i никогда не принимает отрицательные значения.
Во-первых у тебя memory-bounded вычисления — большую часть времени будет занимать пересылка данных в/из памяти. Чтобы обойти этот эффект, попробуй сделать num_samples = 64k (чтобы попасть хотя бы в L3), и добавить внешний цикл "repeat".
Во-вторых попробуй добавить volatile на i, чтобы отключить оптимизатор.
И в-третьих тут один цикл с несколькими проверками — тут branch predictor должен неплохо сработать. В реальном же коде, эти проверки будут размазаны тонким слоем по всему call graph, и не всегда будут вызываться в циклах. Плюс они забивают predictor table.
Здравствуйте, niXman, Вы писали:
X>Здравствуйте, Evgeny.Panasyuk, Вы писали:
AF>>В контр-пример можно привести, что printf в большинстве случаев работает быстрее std::cout (можно глянуть на тесты boost::format — там это замечательно видно). X>что за бред?! X>с каких таких пор эта сишная всезаглатывающая кишка стала быстрее плюсовых потоков?! X>
X>да и как вообще может быть такое, чтоб printf() который вообще ничего не знает о типе, выполнялся быстрее типизированного кода?
Это такой забавный троллинг? Или Вы серьезно не умеете оптимизировать под разные типы системных вызовов?
Вот, я ни в коем случае не хаю C++ — я на нем реально пишу очень и очень много. Но подходы к написанию C-кода и C++-кода в реалиях очень сильно разные, в отличии от тестовых задач, написанных в лоб в одном и том же стиле для обоих языков.
В случае printf vs std::cout разница точно такая же между select vs poll (epoll/kevent более продвинутые варианты, и они в данном случае для сравнения не годятся). Либо тоже самое, как делают некоторые студенты считаться данные из файла функцией read не блоками, а по-байтно склеивая все это в единый массив.
Конечно, в предложенном варианте std::cout будет быстрее.
Но в реальном коде вызовы выглядят не:
for (i = 0; i < n; i++) std::cout << a[i];
A вот так:
std::cout << "Привет " << name << "! Как у тебя дела? Что делал " << day << "? Какие планы на " << next_day ....
Код на C:
printf("Привет %s! Как у тебя дела? Что делал %s? Какие планы на %s? " .... "", name, day, next_day ...);
И в этом случае printf быстрее, что может стать "узким горлышком" для std::cout, когда их очень много. Правда теряешь на compile-time проверках типов (хотя современные компиляторы умеют проверять содержимое формат-строки), и опять же это не такая сильная проблема — код отлаживается один раз, запускается множество раз.
Даже boost::asio замечательно написанный, но сильно перегруженный постоянными перекладываниями из одного контейнера в другой дескрипторов сокетов.
Даже в его случае, его можно выкинуть, написать все на чистом API и получить лишние 10-20% производительности в реальном приложение, а не на "сферическом коне в вакууме", когда тесты гоняются на внутреннем интерфейсе машине, или в лучшем случае в локальной сети между двумя серверами.
Здравствуйте, andrew.f, Вы писали:
AF>Это такой забавный троллинг?
это ты так прикидываешься?
AF>Или Вы серьезно не умеете оптимизировать под разные типы системных вызовов?
где в моем примере использование системных вызовов? или ты таки считаешь, что printf() это системный вызов?
AF>И в этом случае printf быстрее, что может стать "узким горлышком" для std::cout, когда их очень много.
снова прикидываешься?
AF>Даже boost::asio замечательно написанный, но сильно перегруженный постоянными перекладываниями из одного контейнера в другой дескрипторов сокетов. AF>Даже в его случае, его можно выкинуть, написать все на чистом API и получить лишние 10-20% производительности в реальном приложение, а не на "сферическом коне в вакууме", когда тесты гоняются на внутреннем интерфейсе машине, или в лучшем случае в локальной сети между двумя серверами.
ни о чем
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Здравствуйте, smeeld, Вы писали:
S>>И это компилируется в компактный простой ассемблерный листинг с jmp/jne/je.. скачками в пределах файла.
EP>Этот вариант даже медленнее чем вариант с goto.
Смотря что ты оптимизировал. Это лишь вариант ручного инлайнига сильно разветвленного кода, в отличии от типичной мешанины, когда когда обработка ошибок в одном месте с основным потоком кода.
В реалиях C++ код выглядит примерно также, у него точно также после каждого вызова есть проверка результата выполнения, и в случае ошибки — возврат на уровень выше вызовом throw. Тот самый прямой happy-path.
S>>В случае же использования С++ try/catch сплошные call XXXXXXX S>> в дебри либ и фреймворков, которые вызываются по ходу выполнения потока, постоянно прерывая его.
EP>Подробнее.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Здравствуйте, andrew.f, Вы писали:
EP>>>Так мы же про общий случай, сортировка — это просто иллюстрация. AF>>qsort какой то неудачный пример — функция стандартная, и ей "сто лет в обед",
EP>Да какая разница? Вот например GLib: EP>
AF>>и есть масса других вариантов библиотек сортировки.
EP>Покажи хотя бы пример интерфейса сортировки на C, которая будет принимать любой предикат, любой тип последовательности, с любыми элементами.
Ты же сам прекрасно знаешь, что в этом случае код на макросах пишут.
Альтернативный вариант — в качестве объекта абстрации выбирается функция, но все объявления функций делаются через static inline.
Функция предикат, тоже пишется со static inline. И о чудо — при передаче в качестве параметра адреса static inline функции в другую static inline функцию — все это замечательно сворачиваeтся без лишних call XXXX.
Можешь сам попробовать — написать простой пример с подобными вызовами и посмотреть ассемблеровскую портянку, пропустив через gcc -S ....
Сорри, но из готовых алгоритмов, что у меня есть — код проприетарный, то есть закрытый.
Поищу открытые проекты с подобными наработками — поделюсь.
AF>>В контр-пример можно привести, что printf в большинстве случаев работает быстрее std::cout (можно глянуть на тесты boost::format — там это замечательно видно).
EP>Пример не равноценный — std::cout медленный потому что много чего делает, и имеет customization points. EP>А qsort медленный — потому что интерфейс такой
Вот в том то и дело, что сравниваются абсолютно неадекватные вещи, и при этом говорится один язык лучше второго, потому что вот в этом конкретном случае реализовано лучше.
Идеального варианта, подходящего под все случае жизни никто до сих пор не придумал.
EP>Покажи интерфейс сортировки, или например rbtree.
Я про это выше написал. Поищу вариант открытой библиотеки — скину.
AF>>И опять же под линуксом Firefox (всеми горячо любимым gcc) официально собирается с -Os, и он правда в такой варианте сборки работает быстрее, чем с -O2/-O3.
EP>Есть тесты?
Тесты на mozilla.org.
Самый простой вариант — качаем официальную сборку Firefox for Linux с mozilla.org и набираем в адресной строке about:buildconfig. Смотрим параметры сборки. Для 64-битных систем до сих пор 32 битные сборки, по банальной причине — памяти кушает больше, а на производительности никак не сказывается.
В дистрибутивах тоже самое — Firefox собран с -Os и без поддержки exceptions и rtti, но зато не (Chrome, LLVM тоже без исключений и rtti собираются, если что).
Например, у меня Debian:
Здравствуйте, andrew.f, Вы писали:
AF>Даже boost::asio замечательно написанный, но сильно перегруженный постоянными перекладываниями из одного контейнера в другой дескрипторов сокетов. AF>Даже в его случае, его можно выкинуть, написать все на чистом API и получить лишние 10-20% производительности в реальном приложение, а не на "сферическом коне в вакууме", когда тесты гоняются на внутреннем интерфейсе машине, или в лучшем случае в локальной сети между двумя серверами.
boost::asio и на тестах через внутренний интерфейс сливает релизации на чистом C API.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Во-первых у тебя memory-bounded вычисления — большую часть времени будет занимать пересылка данных в/из памяти. Чтобы обойти этот эффект, попробуй сделать num_samples = 64k (чтобы попасть хотя бы в L3), и добавить внешний цикл "repeat". EP>Во-вторых попробуй добавить volatile на i, чтобы отключить оптимизатор. EP>И в-третьих тут один цикл с несколькими проверками — тут branch predictor должен неплохо сработать. В реальном же коде, эти проверки будут размазаны тонким слоем по всему call graph, и не всегда будут вызываться в циклах. Плюс они забивают predictor table.
Посмотрел дизасм. Оказывается оптимизатор убирает все мои if-ы и векторизует цикл. Если ограничить размер массива и сделать внешний цикл, а в if-е сравнивать что-нибудь еще, что оптимизатор не может выбросить, векторизация не выполняется вообще. Если добавить volatile — все становится очень плохо, так как компилятор генерирует кучу загрузок и не может в векторизацию, независимо от налиция или отсутствия ветвлений.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Во-первых у тебя memory-bounded вычисления — большую часть времени будет занимать пересылка данных в/из памяти. Чтобы обойти этот эффект, попробуй сделать num_samples = 64k (чтобы попасть хотя бы в L3), и добавить внешний цикл "repeat". EP>Во-вторых попробуй добавить volatile на i, чтобы отключить оптимизатор. EP>И в-третьих тут один цикл с несколькими проверками — тут branch predictor должен неплохо сработать. В реальном же коде, эти проверки будут размазаны тонким слоем по всему call graph, и не всегда будут вызываться в циклах. Плюс они забивают predictor table.
Если выключить оптицизацию, код для циклов генерируется одинаковый, только во втором есть branch. Время работы тоже примерно одинаковое, первый цикл, без branch-а, может быть на 5-10% быстрее, но не больше. Это при том, что тело цикла это две загрузки и одно сохранение, в общем — все очень быстро и влезает в L2 кэш.