*** ВНИМАНИЕ: Блог переехал на другой адрес - demin.ws ***

суббота, 25 июля 2009 г.

Кто быстрее: функтор или указатель на функцию

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

functor.cpp:
#include "gtest/gtest.h"

#include <algorithm>

typedef double Type;

Type* array;
const int N = 100000000;

inline bool less(Type a1, Type a2) {
return a1 < a2;
}

class Less {
public:
inline bool operator()(Type a1, Type a2) {
return a1 < a2;
}
};

// Использование встроенной функции сравнения.
TEST(Callback, BuiltIn) {
std::sort(array, array + N);
}

// Использование свободной функции сравнения по указателю.
TEST(Callback, Function) {
std::sort(array, array + N, less);
}

// Использование функтора.
TEST(Callback, Functor) {
std::sort(array, array + N, Less());
}

int main(int argc, char* argv[]) {
// Создаем отсортированный массив.
array = new Type[N];
Type* p = array;
for (int i = 0; i < N; ++i) *p++ = i;

testing::InitGoogleTest(&argc, argv);
// Принудительно печатаем время работы тестов.
testing::GTEST_FLAG(print_time) = true;
return RUN_ALL_TESTS();
}
Компилируем и запускаем (Visual Studio 2008):
cl /O2 /arch:SSE2 /EHsc /I. functor.cpp gtest\gtest-all.cc && functor
Результат:
[==========] Running 3 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 3 tests from Callback
[ RUN ] Callback.BuiltIn
[ OK ] Callback.BuiltIn (9547 ms)
[ RUN ] Callback.Function
[ OK ] Callback.Function (24391 ms)
[ RUN ] Callback.Functor
[ OK ] Callback.Functor (9578 ms)
[----------] 3 tests from Callback (43547 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test case ran. (43547 ms total)
[ PASSED ] 3 tests.
Видно, что скорость функтора (9578 мс) практически равна встроенной функции (9547 мс) сравнения. А вот вызов свободной функции конкретно отстает (24391 мс), приблизительно в 2.5 раза.

Такое поведение можно объяснить тем, что в данном случае при вызове обычной функции компилятор не может оптимизировать такой вызов встраиванием (inlining). Вне зависимости от того, что функция объявлена выстраиваемой, так как ее вызов производится по указателю, компилятор не может сделать предположений о значении этого указателя на стадии исполнения, а значит провести оптимизацию.

При использовании же функтора компилятору доступна информация о семантике вызываемого кода, поэтому все типы оптимизации возможны. Отсюда и скорость, близкая к встроенной функции сравнения.
Как вариант, при замене типа с double на int и при условии, что опция /arch:SSE2 включена, тест с функтором работал даже быстрее встроенной функции.
Вывод

Использование функторов предпочтительнее, чем свободных функций. С точки зрения проектирования и так все понятно (функтор, или функциональный объект, можно удобно тестировать, наследовать и т.д), но, как видно, и в плане производительности функтор тоже впереди.
Все необходимые файлы для компиляции примеров из этого поста можно посмотреть и скачать на сайте проекта Easy Сoding в соответствующем разделе.


Данный пост является экспериментом по использованию Google Code в качестве хостинга для размещения исходных текстов примеров и самих статей.

Головная страница проекта - http://code.google.com/p/easy-coding/.

Каждый пост будет по необходимости размещаться в отдельном каталоге и отдельной страницей Wiki.

пятница, 17 июля 2009 г.

Дизассемблер как метод уменьшения размера обновлений

Интересующихся попытками сделать жизнь лучше, рекомендую к прочтению интересный документ от инженеров Google на тему минимизации размеров обновлений для браузера Chrome.

Google очень радеет за безопасность своих продуктов. Например, для браузера Chrome - это значит регулярные обновления. А кому охота каждый день качать по десять мегов, даже если там весьма критическое обновление. Поэтому разработчики Chrome делают все, чтобы сократить размер пересылаемых по сети данных.

Применение bsdiff частичное решает проблему, и уже не надо тупо слать архив целиком.

Но теперь к самому интересному. Что происходит, когда найден досадный баг и сделано исправление, например:

Было:
...
if (packet_size < 1024) ...
...
а стало:
...
if (packet_size > 10 && packet_size < 1024) ...
...
Исправили всего одну строчку. Но после компиляции и линковки результирующий файл будет радикально другой: съедет таблица символов, где-то изменится выравнивание, возможно линкер из-за этого изменит размещение сегментов и т.д. Море факторов, из-за которых самое минимальное изменение исходного текста приведет к глобальному изменению результирующего исполняемого файла.

Что делать? А что, если в обновлении посылать модуль не в двоичном виде, а в виде исходного текста? Тогда изменение (diff) будет совсем небольшое.

Конечно, посылать нормальный исходный текст и компилировать его на стороне клиента пока несколько затруднительно. Не у всех установлена Visual Studio.

Инженеры Google придумали технологию Courgette. Суть ее в том, что посылаются изменения на уровне не оригинального исходного текста, а на уровне ассемблера. Для формирования обновления скомпилированный исполняемый файл дизассемблируется. Тоже самое делается для предыдущей версии файла. Затем производится поиск изменений в текстовом виде (например, тем же стандартным diff'ом). И этот текстовый файл и является, собственно, обновлением. Затем он пересылается пользователю. На стороне пользователя текущий исполняемый файл тоже дизассемблируется, изменяется на основе принятого файла изменений (diff'а) и снова ассемблируется.

Старая схема с bsdiff'ом работала так:
  сервер:
изменения = bsdiff(оригинал, обновление)
передать изменения

клиент:
принять изменения
обновление = bspatch(оригинал, изменения)
Теперь это работает так:
  сервер:
ассемблер_оригинал = дизассемблировать(оригинал)
ассемблер_обновление = дизассемблировать(обновление)
ассемблер_обновление_кор = коррекция(ассемблер_обновление, ассемблер_оригинал)
ассемблер_изменения = bsdiff(ассемблер_оригинал, ассемблер_обновление_кор)
передать ассемблер_изменения

клиент:
принять ассемблер_изменения
ассемблер_оригинал = дизассемблировать(оригинал)
ассемблер_обновление_кор = bspatch(ассемблер_оригинал, ассемблер_изменения)
обновление = ассемблировать(ассемблер_обновление_кор)
Для успешной реализации дизассемблер должен располагать таблицей символов со стадии нормальной линковки исполняемого файла. Также на специальном шаге коррекция производится оптимизация ассемблерного кода для минимизации последующих различий с предыдущей версией путем группировки неизмененных частей.

А вот и результаты на примере единичного обновления Chrome'а:

МетодРазмер обновления
Полное обновление10,385,920
Обновление bsdiff'ом704,512
Courgette метод78,848

Неплохо, да?

Вот такой вот хитрый план у разработчиков Chrome. Обещают рассказать особенности реализации, как только все будет готово.

Данный пост является вольным переводом статьи, ссылка на которую дана в начале. Уж больно меня зацепила эта тема.

среда, 15 июля 2009 г.

Руководство для начинающих по Google Test

Выложил на Google Code свой перевод руководства для начинающих по Google Test.

Как всегда буду благодарен за замечания об ошибках, опечатках, и да вообще по стилистике в целом. Порой перевод технических терминов становится проблемой, так как с одной стороны для многих понятнее, когда используется просто английский термин (например, fixture), но с другой стороны хочется сделать жизнь как-то красивее и попытаться на своем родном писать без тотального засилия английских слов (их и так порядочно).

На отдельной странице будет общий список ресурсов о Google Test на русском языке. Очень пришелся кстати мой недавний пост о новшествах версии 1.3.0.

В планах перевод остальных документов.

В процессе работы над текстом перевода, у меня родилось вот такое небольшое лирическое отступление.

Я последнее время увлекся различными механизмами верстки при помощитекстовых языков разметки. И я не про HTML. Я говорю о таких вещах, как простые Wiki-диалекты, и заканчивая целым миром верстки под названием TeX. Периодически покуриваю на ночь документацию с http://www.ccas.ru/voron/latex.html. Начал я с совершенно дивного коротенького справочника, где сухо и сжато перечислены базовые элементы языка верстки LaTeX c примерами.

Например, моя любимая задача линейного программирования:



будет записываться так:
\[
\max_{n\in R}\sum_{j=1}^n
c_jx_j
;\;
\sum_{j=1}^n a_{ij}x_j\leqslant b_i, (i=1,\;2,\;\ldots,\;m)
\]
Если вы хотите в онлайне проиграться с набором формул в LaTeX, то рекомендую заглянуть сюда. Там, правда, не поддерживаются русские буквы в теле LaTeX команд, но для экспериментов это не проблема.

Когда я несколько лет назад верстал свою диссертацию в Ворде, я постоянно утыкался в корявости WYSIWYG подхода. Когда документ разрастается - сложная структура глав, много формул, картинок, внутренние и внешние ссылки, то уже крайне важной становится независимость содержания от, собственно, разметки. В WYSIWYG подходе на все случаи жизни уже не напасешься менюшек и кнопочек. Возникают корявости.

Безусловно, Ворд развивается семимильными шагами от версии к версии, оттачивая механизм стилей, без которого верстать что-то длинее десяти страниц вообще неудобно, но как же ему далеко от гибкости, которую придумали и реализовали в TeX десятки лет назад. К тому же чисто текстовое представление документа позволяет хранить его под контролем версий и удобно отслеживать изменения.

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

Вобщем, набивая перевод документации по Google Test, я просто отдыхал душой и попискивал от удовольствия, так как все происходило в Wiki-разметке в разделе документации проекта на Google Code.

Мечта - очень хотелось бы, чтобы когда-нидудь Blogspot реализовал бы альтернативный ввод постов с использованием Wiki-разметки, а не HTML. Хотя бы также, как это работает на Google Code. Ну а если еще и контроль версий будет, тогда вообще бесконечное счастье наступит.

Так если разобраться, единственная причина, почему нельзя вести блог прямо на Google Code в разделе Wiki - там нет трансляции постов в виде временной ленты. Если бы можно было в теле постов Блогспота на лету импортировать тест из документов Wiki c Google Code, то можно было бы удобно писать посты с применением Wiki-разметки и контролем версий, а отображать их в блоге.

Я, например, редко пишу в блог посты на 2-3 строчки. Обычно это крополивая работа по набивке, верстке, подгонке картинок, общего вида и т.д. И каждый пост это по сути законченный документ, к которому иногда возвращаешься, что-то исправляешь, добавляешь и т.д. И тут все прелести удобной итеративной работы с текстом выходят на первый план.


Посты по теме:

среда, 8 июля 2009 г.

Google C++ coding stantard прямо в Visual Studio

Многие читали стандарт кодирования на С++ от Google.

Для себя я его давно использую, а на работе удалось продавить его фрагменты в наш внутренний стандарт.

В качестве приятного бонуса Google раздает задорную утилитку cpplint, для быстрой проверки исходника на С++ на соответствие правилам и для генерации отчета, понимаемого средой разработки (например, Visual Studio). Написана она на Питоне, так что для ее использования его надо установить.

Я прикрутил cpplint себе в Студию, чтобы можно было проверять исходники прямо в редакторе.

cpplint имеет несколько десятков checker'ов, их можно опционально отключать. Я отключил только три:
  • Проверку на использование #include без указания относительного пути, например #include "one.h" вместо #include "path/to/lib/one.h". Правило очень разумное, так как исключает перехлест заголовочных файлов с одинаковыми именами в разных подсистемах, но у меня уж больно много, где надо менять.
  • Проверку на формирование имени защитного #define'а в начале заголовочного файла. У меня свое правило именования, и оно меня устраивает.
  • Проверку на неиспользование потоков в STL. Я потоки использую, поэтому отключил.
Итак, получился скрипт cpplint.cmd:
C:\Python25\python.exe %~d0%~p0cpplint.py ^
--filter=-build/include,-build/header_guard,-readability/streams ^
--output=vs7 %1 %2 %3 %4 %5 %6 %7 %8 %9
Можно его из командной строки вызывать, но из Студии интереснее.

Итак, Menu->Tools->External Tool..., жмем Add и далее как на картинке (пути подправить по вкусу):



Теперь, прямо в редакторе жмем ALT-T,C,ENTER и снизу окне результатов получаем отчет. Кликая на его строки можно скакать по исходнику.

Лично я считаю, что порядок в исходниках напрямую связан с порядком в голове его автора.

Google Chrome OS

Удивительное дело, как Google умудряется без предварительного шапкозакидательства в стиле Microsoft делать удивительно грандиозные вещи: Chrome, Android, Native Client, а теперь еще и Google Chrome OS.

Обычно я не комментирую новости, но тут не мог стерпеть.

вторник, 7 июля 2009 г.

Static assert

А какой у вас используется assert времени компиляции, если не использовать boost/static_assert.hpp?

У меня вот такой:
template <bool> struct STATIC_ASSERTION_FAILURE;
template <> struct STATIC_ASSERTION_FAILURE<true> {};
#define STATIC_CHECK(x) sizeof(STATIC_ASSERTION_FAILURE< (bool)(x) >)
Работает приемлемо сносно:
int main() {
STATIC_CHECK(sizeof(int) < sizeof(char));
return 0;
}

Фундаментальные алгоритмы

После того, как я жестко облажался с самопальной сортировкой, причем облажался дважды: первый раз, когда думал, что порву по скорости STL'ный std::sort(), а второй - когда не понял, что все эти "ухищрения", как я думал, по ускорению QuickSort'а, реализованные в STL'е Visual Studio, есть ни что иное, как просто давно придуманный IntroSort.

В общем, я взял в руки:

Роберт Седжвик

Фундаментальные алгоритмы на C++. Части 1-4. Анализ. Структуры данных. Сортировка.



и начал освежать (а местами просто узнавать с нуля) когда-то читанное в студенчестве.

Втянулся.

Заказал вторую книгу:

Роберт Седжвик

Фундаментальные алгоритмы на C++. Часть 5. Алгоритмы на графах



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

Вобщем, если вы все еще можете не думая сказать, например, чем лучше хеш-таблица по сравнению со сбалансированным (например, красно-черным) деревом, почему именно Splay-дерево удобно для реализации кеша, написать по памяти пирамидальную сортировку или привести пример NP-задачи, которая не является полной, то может фундамент все еще крепок.