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

пятница, 6 февраля 2009 г.

Универсальный мьютекс на C++ для Windows и UNIX

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

Например, в программе есть некоторая строковая переменная, хранящая описание текущего состояния. Это состояние может, например, выводиться в нижней полоске рабочего окна. Теперь представим, что в программе есть два параллельно работающих потока. Первый занимается получением данных из сети, а второй — обработкой базы данных. Допустим, настал некоторый момент времени, когда первый поток принял данные из сети и хочет об этом отрапортовать в строке состояния, записав туда "Принято 16384 байт". Приблизительно в этот же момент второй поток завершил периодическую проверку базы данных и также желает об этом сообщить пользователю строкой "База данных проверена". Операция копирования строки не является атомарной, то есть нет никакой гарантии, что во время ее выполнения процессор не переключится на какой-то другой поток, и операция копирования не будет прервана посреди работы. Итак, когда поток номер 1 успел записать в строку состояния слово "Принято", может так случиться, что процессор активирует поток номер 2, который также начнет запись своей строки и добавит к уже записанному "Принято" строку "База данных про", но будет прерван первым потоком и т.д. В итоге в переменная может содержать кашу типа "ПрияноБаза данных 1про6в3ерена84 байт". Вывод такой — результат полностью непредсказуем.

Для решения подобного вроде проблем в мире параллельного программирования существует такое понятие, как блокировка. Суть ее в том, что когда один процесс захватывает блокировку, то все остальные процессы, пытающиеся ее захватить после, будут блокированы до тех пор, пока первый процесс ее не отпустит. Это похоже на дверь в комнату: представим, что наша переменная globalStatus находится в комнате с одной дверью и ключом внутри. Если дверь открыта (блокировка свободна), то в комнате никого нет (никто не работает с переменной). Когда процесс заходит в комнату, он запирает дверь изнутри (захватывает блокировку). После этого процесс может спокойно работать с переменной как угодно долго, так как гарантированно никто другой не войдет в комнату, так как она заперта изнутри, и не будет мешать ему работать с переменной.

Это была идея простейшей блокировки, которую часто называют мьютекс (mutex). Сейчас мы рассмотрим реализацию такой блокировки на С++, которая будет работать в Windows и UNIX. Как я писал в статье про параллельные потоки, в мире UNIX стандартом де-факто является библиотека pthread (POSIX Threads). Имеено ее мы и будем использовать для UNIX-версии. Для Windows будет отдельная реализация.

Класс Mutex получился весьма простой, в виде единственного файла mutex.h. Пространство имен (namespace) называется ext для простоты. Переименуйте его, если это требуется для вашего проекта.

Файл mutex.h:
#ifndef _EXT_MUTEX_H
#define _EXT_MUTEX_H

#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#define NOGDI
#include <windows.h>
#else
#include <stdlib.h>
#include <pthread.h>
#endif

namespace ext {

#ifdef WIN32
typedef CRITICAL_SECTION MutexType;
#else
typedef pthread_mutex_t MutexType;
#endif

// Интерфейс класса Mutex.
// Класс задумывался как маленький и быстрый, поэтому все
// определено прямо в заголовочном файле, и все функции
// объявлены принудительно inline. Это должно уберечь
// от ошибок и предупреждений о двойных символах при
// включении mutex.h в несколько модулей.
class Mutex {
public:
inline Mutex();
// Деструктор объявлен как не виртуальный из-за тех же
// соображений эффективности. Если вы планируете
// наследоваться от этого класса, то лучше сделать
// деструктор виртуальным, так как наследование от класса
// с не виртуальным деструктором потенциально опасно
// с точки зрения утечек памяти и является одним из
// больших "no-no" в С++.
inline ~Mutex();

// Функция захвата блокировки (вход в комнату и запирание
// двери ключом изнутри).
inline void Lock();

// Функция освобождения блокировки (отпирание двери и
// выход из комнаты)
inline void Unlock();
private:
MutexType __mutex;

// Защита от случайного копирования объекта данного класса.
// Экземпляр этого класса с трудом может быть нормально
// скопирован, так как он жестко привязан к системному
// ресурсу.
Mutex(const Mutex&);
void operator=(const Mutex&);
};

#ifdef WIN32

// Реализация через Windows API

Mutex::Mutex() { InitializeCriticalSection(&__mutex); }
Mutex::~Mutex() { DeleteCriticalSection(&__mutex); }
void Mutex::Lock() { EnterCriticalSection(&__mutex); }
void Mutex::Unlock() { LeaveCriticalSection(&__mutex); }

#else // WIN32

// UNIX версия через pthread

Mutex::Mutex() { pthread_mutex_init(&__mutex, NULL); }
Mutex::~Mutex() { pthread_mutex_destroy(&__mutex); }
void Mutex::Lock() { pthread_mutex_lock(&__mutex); }
void Mutex::Unlock() { pthread_mutex_unlock(&__mutex); }

#endif // WIN32

} // ext

#endif
Касаемо техники "защиты" объекта в С++ от случайного копирования я уже писал ранее.
Я не стал проверять коды возвратов данных функций для упрощения класса. Могу сказать, что если хоть одна из них завершиться с ошибкой, то это значит, что-то конкретно не так в вашей системе, и приложение по любому не будет работать нормально еще по миллиарду причин.
Пощупаем класс в работе. И конечно, используя unit-тестирование.
Традиционно, для компиляции тестов нам нужна Google Test Framework. Как я уже писал, вы можете скачать мою модификацию этой библиотеки, которая сокращена до двух необходимых файлов gtest/gtest.h и gtest-all.cc.
Для теста нам также потребуются файлы thread.cpp и thread.h из статьи про параллельные потоки в С++.

Файл тестов mutex_unittest.cpp:
#include "gtest/gtest.h"

#include "mutex.h"
#include "thread.h"

// Макрос для осуществления задержки в миллисекундах
#ifdef WIN32
#include <windows.h>
#define msleep(x) Sleep(x)
#else
#include <unistd.h>
#define msleep(x) usleep((x)*1000)
#endif

// Определим параллельный поток, который будет
// "конкурировать" с основным потоком за блокировку.
// Данный поток будет пытаться захватить блокировку,
// изменить значение флага и освободить затем
// блокировку.
class A: public ext::Thread {
public:
// Передаем в конструкторе ссылку на флаг и
// ссылку на блокировку.
A(volatile int& flag, ext::Mutex& mutex) :
__flag(flag), __mutex(mutex)
{}

virtual void Execute() {
// Захват блокировки (1)
__mutex.Lock();
// Изменяет флаг на 1
__flag = 1;
// Освобождаем блокировку
__mutex.Unlock();
}

private:
volatile int& __flag;
ext::Mutex& __mutex;
};

TEST(MutexTest, Generic) {
// Начальное значение флага - 0.
volatile int flag = 0;

// Создаем объект-блокировку
ext::Mutex mutex;
// Захватываем блокировку.
mutex.Lock();

// Создаем параллельный поток выполнения.
A a(flag, mutex);
// Запускаем его.
a.Start();
// Ждем для проформы десятую секунды, чтобы дать
// время параллельному потоку создаться и успеть
// дойти до строки (1), то есть до захвата блокировки.
msleep(100);

// Значение флага должно быть все еще 0, так как
// параллельный поток должен быть блокирован на
// строке (1), так как мы захватили блокировку еще
// до его создания.
EXPECT_EQ(0, flag);

// Освобождаем блокировку, тем самым давая
// параллельному потоку выполняться дальше и
// изменить значение флага на 1.
mutex.Unlock();

// Ждем завершения параллельного потока.
a.Join();
// Так как параллельный поток завершился, то
// флаг теперь точно должен быть равен 1.
EXPECT_EQ(1, flag);
}
Для запуска тестов нам нужен стандартный файл запуска runner.cpp:
#include "gtest/gtest.h"

int main(int argc, char* argv[]) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Компилируем все вместе.

В Visual Studio:
cl /EHsc /I. /Femutex_unittest_vs2008.exe /DWIN32 runner.cpp mutex_unittest.cpp thread.cpp gtest\gtest-all.cc
Или если вы используете gcc:
g++ -I. -o mutex_unittest_cygwin.exe runner.cpp mutex_unittest.cpp thread.cpp gtest/gtest-all.cc
Запускаем mutex_unittest_vs2008.exe или mutex_unittest_cygwin.exe:
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from MutexText
[ RUN ] MutexText.Generic
[ OK ] MutexText.Generic
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran.
[ PASSED ] 1 test.
Вроде работает как надо.

Теперь внесем в исходный текст класса "случайную" ошибку, заменив строку:
void Mutex::Lock()         { EnterCriticalSection(&__mutex); }
на
void Mutex::Lock()         { /* EnterCriticalSection(&__mutex); */ }
Этой "ошибкой" мы просто отключили создание блокировки. Перекомпилируем все заново и запустим:
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from MutexText
[ RUN ] MutexText.Generic
mutex_unittest.cpp:41: Failure
Value of: flag
Actual: 1
Expected: 0
[ FAILED ] MutexText.Generic
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran.
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] MutexText.Generic

1 FAILED TEST
Видно, что флаг был изменен в параллельном потоке вне зависимости от блокировки (и понятно почему, мы ж ее "сломали").

Итак, можно вернуть исправленную строку в исходное состояние. Класс работает, и тесты на это подтвердили.
При использовании класса Mutex у себя в проекте не забудьте включить файл mutex_unittest.cpp в ваш набор unit-тестов.
В завершении могу сказать, что данный класс успешно работает и проверен мной лично на Windows (32- и 64-бит), Linux 2.6 (32- и 64-бит Intel и SPARC), AIX 5.3 и 6, SunOS 5.2 64-bit SPARC, HP-UX и HP-UX IA64.


Другие посты по теме:

четверг, 5 февраля 2009 г.

Обновление для миллисекундного таймера

Универсальный класс PreciseTimer, дающий возможность работать с миллисекундными интервалами времени в Windows и UNIX получил новую функцию:
static void sleepMs(int ms);
Эта функция реализует задержку в указанное число миллисекунд. Функция является статической, то есть ей можно пользоваться без создания экземпляра касса:
...
ext::PreciseTimer::sleepMs(100);
...
Необходимо учитывать, что в UNIX системах данная функция может быть прервана пришедшим системным сигналом, например, сигналом о полученных новых данных в буфер сокета. В этом случае задержка может быть меньне, чем ожидается.

Обновленный исходный текст класса и тестов находится по старому адресу.

среда, 4 февраля 2009 г.

Дармаван Салихан, "BIOS. Дизассемблирование, модификация, программирование"

Не буду оригинален в посвящении некоторых постов книгам.

Дармаван Салихан

BIOS. Дизассемблирование, модификация, программирование (BIOS Disassembly Ninjutsu Uncovered)



С изматывающими подробностями разжевываются по крупицам вопросы, касаемые биоса. Начиная от организации памяти в писюках 90-х и биосов тех лет вплоть современных матерей со всем последними шинами, протоколами, системам загрузки, инициализации железа, загрузки микрокода в процессор и периферию, ориентации на модное направление встраиваемых систем и т.д. Изучается вопрос написания своих биосов с нуля (как на ассемблере, так и на си) и модификации существующих. Подробно разобран пример, как всем известный вирус CIH стирал биосы на некоторых матерях своего времени.

Изложение материала сопровождается изобилием примеров программ и результатов дизассемблирования под винды и линукс.

Единственный вопрос, который, как мне показалось, остался за бортом — это альтернативы классическому подходу в простроении биосов на основе ядра линукса, а именно системы Coreboot, ранее известной как LinuxBIOS.

Резюме: крайне занимательное чтиво для занимающихся встраиваемыми системами, и вообще находящихся на стыке железа и софта.

И еще. Нельзя забывать о том, что книги надо не только иметь, но и читать.

вторник, 3 февраля 2009 г.

Рецензирование кода

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

В процессе внесения мноего патча в Google Test Framework какой-то кореец просто извел меня своими придирками не только в фундаментальному качеству представленных мной unit-тестов, доказывающих правильность моих изменений, но и вплоть до знаков пунктуации в комментариях. Лишь раза с десятого, он сказал "поехали!", и махнул рукой, как говорится. Патч то был размером строк в 50-60. По первости я просто бесился, что какой-то хрен указывает мне на мнимые "недостатки" моего "гениального кода". Но когда в результате я оглянулся на то, что было изначально и на результат — я простил ему все.

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

понедельник, 2 февраля 2009 г.

Функциональный деструктор в С++

В С++ крайне распространен прием использования класса std::vector для хранения указателей на размещенные в куче объекты.

Например, вы размещаете в куче десяток объектов типа Книга (Book) и сохраняете указатели на них в векторе:
class Book {
public:
Book(int index);
...
};
...
std::vector<Book *> books;
for (int i = 0; i < 10; ++i)
books.push_back(new Book(i));
Естественно, после использования память надо освободить. Обычно стандартный прием для этого таков:
for (std::vector<Book *>::iterator i = books.begin(); i != books.end(); ++i)
delete *i
В целом, с таким подходом все в порядке, разве что слегка веет от него излишней алгоритмической загруженностью. Он вынужден, навязан особенностями языка C++. Индексная переменная i здесь абсолютно неважна для цикла, она является чисто служебной. Все это, конечно, не так страшно, как использование оператора goto или статических переменных, но все равно хочется гармонии. И способ есть. Данный код можно переписать так:
#include <algorithm>
...
class deleter {
public:
template <typename T>
void operator()(const T* p) const {
delete p;
}
};
...
std::for_each(a.begin(), a.end(), deleter());
Данный код определяет класс-функтор, у которого перегруженный оператор void operator()(...) является шаблонным. Затем стандартный алгоритм std::for_each() вызывает этот оператор для каждого члена вектора.
Конечно, вы можете сказать, что мол битва за идею принуждает нас таскать за собой класс deleter, но тут аргумент простой — данный подход ближе к декларативному подходу в программировании, нежели к прямому алгоритмическому. В декларативном подходе вы стараетесь как можно больше логики перенести из ее явного программирования базовыми конструкциями типа условий, циклов и т.д. к ее выражению через определения (декларации) сущностей и их взаимосвязей. Декларативные конструкции проще дробить на независимые куски, а значит проще тестировать. Например, вы можете протестировать алгоритм std::for_each в изоляции, тем самым гарантируя его корректную работу сразу во всей программе, а вот протестировать явный цикл в изоляции вряд ли получится, так как цикл "жестко вплетен" в прочую логику программы. Максимум удастся проверить данный конкретный цикл как-то вручную, и если их программе много, проверять придется каждый из них.
Соглашусь, однако, что конкретно этот пример весьма тривиален и является в большинстве делом вкуса, нежели вопросом реального выигрыша простоте и тестируемости кода. Но сам прием весьма показателен в плане замены простейших алгоритмов высокоуровневыми сущностями. И еще, в защиту такого приему могу сказать, что например, вы можете переопределить алгоритм std::for_each на свой, который сможет на конкретно вашей платформе выполняться гораздо быстрее, или, например, ловить исключения работы с кучей и журналировать проблемы освобождения памяти. В случае же прямого использования цикла for вам придется переписать сам цикл. Хорошо, когда такое место одно в программе, а если их тысячи?

воскресенье, 1 февраля 2009 г.

Статический аргумент функции putenv

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

Прототип функции такой:
int putenv(char* string);
Казалось бы, все предельно просто и понятно — вызываешь функцию, она делает свое дело, если надо проверятся код ошибки, и все пучком.

Например:
...
putenv("TESTVAR=testvalue");
...
или так:
...
char var[1024];
...
strcpy(var, "TESTVAR=");
strcat(var, value);
putenv(var);
...
Вроде похожие примеры, хотя между ними есть огромное "но". Первый пример будет работать нормально, а вот второй пример может приводить к неверному значению установленной переменной или даже к падению программы с нарушением защиты памяти на некоторых платформах. Почему?
Отгадка кроется в том, что на некоторых операционных системах и версиях системной библиотеки libc значение параметра будет использоваться напрямую даже после возврата из функции. Другими словами, функция putenv() не копирует переданный ей параметр куда-либо. Если вы передадите в эту функцию буфер, размещенный в стеке (автоматическая переменная), то это прямой путь к серьезным ошибкам памяти, так в момент реального использования установленной переменной автоматический буфер может уже не существовать (функция, его создавшая, уже завершила работу и подчистила за собой стек). Отсюда вывод — указатель, передаваемый в функцию putenv(), всегда должен указывать на статические данные. Первый пример работает правильно, так как в С и C++ строковые константы размещены в статическом сегменте и постоянны во время всей жизни программы.
Ситуация усугубляется еще и тем, что разные версии системной библиотеки ведут себя по разному. Нет четкого правила: копировать значение буфера или нет. Я лично наступил на эти грабли, когда начал писать программу под виндами на Visual Studio, все работало. А вот при запуске на юниксах или под виндами, но использовании компилятора gcc из Cygwin, все падало. Конечно, чтение документации все сразу прояснило, но вот осадочек остался. Просто использование буфера напрямую, без создания внутренней копии, как-то противоречит здравому смыслу, но... как сделало, так сделано.

Я набросал мини-тесты на всю эту тему.
Традиционно, для компиляции тестов нам нужна Google Test Framework. Как я уже писал, вы можете скачать мою модификацию этой библиотеки, которая сокращена до двух необходимых файлов gtest/gtest.h и gtest-all.cc.
Файл тестов putenv_unittest.cpp:
#include <gtest/gtest.h>
#include <cstdlib>

// Главная проверочная функция. Вызывает putenv() с указанным буфером
// (автоматическим или статическим), если надо перезатирает буфер, чтобы
// проверить тот ли это буфер, и после сверяет установленное значение
// с ожидаемым эталоном.
void checkPutEnv(char* buf, bool rewrite) {
// Эталон
const char* etalon = "TESTVAR=testvalue";
// Копируем эталон в буфер
std::strcpy(buf, etalon);
// Устанавливаем переменную
putenv(buf);
// Если того требуют условия теста, то перезатираем буфер
// фальшивым значением.
if (rewrite)
std::strcpy(buf, "TESTVAR=novalue");
// Копируем значение системной переменной.
std::string actual = getenv("TESTVAR");
// Сверяем результаты
EXPECT_EQ(std::string("testvalue"), actual);
}

// Тест с использованием статического буфера без перезатирания.
// Это должно работать стопудово на все платформах, так как
// используется статический буфер, и мы его не перезатираем.
TEST(PutEnv, UsingStaticVariable) {
static char buf[1024];
checkPutEnv(buf, false);
}

// Тест с использованием статического буфера с перезатиранием.
// Данный тест будет рабоать только на системах, которые копируют
// аргумент putenv()'а во внутренний буфер. То есть перезатирание
// нами буфера не приведет к изменению значения установленной
// переменной.
TEST(PutEnv, UsingStaticVariableRewriteBuffer) {
static char buf[1024];
checkPutEnv(buf, true);
}
Файл головной программы запуска тестов:
runner.cc:
#include "gtest/gtest.h"
int main(int argc, char* argv[]) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Сначала пробуем в Visual Studio.

Компилируем:
cl /EHsc /I. /Feputenv_unittest_vs2008.exe runner.cpp putenv_unittest.cpp gtest\gtest-all.cc
Запускаем putenv_unittest_vs2008.exe:
[==========] Running 2 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 2 tests from PutEnv
[ RUN ] PutEnv.UsingStaticVariable
[ OK ] PutEnv.UsingStaticVariable
[ RUN ] PutEnv.UsingStaticVariableRewriteBuffer
[ OK ] PutEnv.UsingStaticVariableRewriteBuffer
[----------] Global test environment tear-down
[==========] 2 tests from 1 test case ran.
[ PASSED ] 2 tests.
Видно, что все тесты работают, а это значит, что микрософтовский putenv() копирует значение аргумента во внутренний буфер, и значит можно передавать в него указатель на нестатический блок памяти.

Теперь пробуем тоже под виндами, но через Cygwin.
Компилируем:
g++ -I. -o putenv_unittest_cygwin.exe putenv_unittest.cpp runner.cpp gtest/gtest-all.cc
Запускаем putenv_unittest_cygwin.exe:
[==========] Running 2 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 2 tests from PutEnv
[ RUN ] PutEnv.UsingStaticVariable
[ OK ] PutEnv.UsingStaticVariable
[ RUN ] PutEnv.UsingStaticVariableRewriteBuffer
putenv_unittest.cpp:15: Failure
Value of: actual
Actual: "novalue"
Expected: std::string("testvalue")
Which is: "testvalue"

[ FAILED ] PutEnv.UsingStaticVariableRewriteBuffer
[----------] Global test environment tear-down
[==========] 2 tests from 1 test case ran.
[ PASSED ] 1 test.
[ FAILED ] 1 test, listed below:
[ FAILED ] PutEnv.UsingStaticVariableRewriteBuffer

1 FAILED TEST
Видно, что тест PutEnv.UsingStaticVariableRewriteBuffer, ожидая получить значение переменной testvalue, получил novalue. Ясно, мы принудительно перезаписали оригинальное значение переменной прямо в буфере после вызова putenv(). Вывод: Cygwin'овский putenv() не копирует значение аргумента куда-либо и, значение буфера используется напрямую, поэтому тут надо передавать указатель обязательно на статический блок памяти, или хотя бы такой блок, которые не исчезнет на момент обращения к переменной.

Резюме.

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


Другие посты по теме:

Кто быстрее: vector<bool> или vector<int>

Я много раз слышал, что стандартный класс std::vector, специализированный для хранения типа bool, то есть std::vector<bool>, который по задумке создателей должен работать заметно быстрее своего смыслового аналога std::vector<int>, на самом деле нет так и хорош. Но тут, как говориться, бабушка на двое сказала, так как с одной стороны операция с базовым типом процессора int обычно является почти самой быстрой атомарной операцией, а другой стороны тип bool может быть упакован в тот же "быстрый" int пачкой по 32 или 64 штуки за раз, и можно оперировать сразу группой значений. В общем, целое поле для оптимизации.

Я люблю все проверять лично, так что привожу результаты своей проверки.

Итак, объект — программа нахождения простых чисел "Решето Эратосфена". Классический алгоритм для проверки на вшивость всяких оптимизаторов. На оригинальность и оптимальность кода не претендую.

Файл era.cpp:
#include <iostream>
#include <vector>
#include <cmath>

int main(int argc, char* argv[]) {
// Получаем предельное значение эксперимента из командной
// строки. По умолчанию - 100. Это основной, влияющий
// на время работы алгоритма, параметр.
long n = argc > 1 ? std::atoi(argv[1]) : 100;
// Корень квадратный из максимума, округленный до большего
// целого.
long sqrt_n = static_cast<long>(std::sqrt(static_cast<double>(n))) + 1;

// Массив-вектор для хранения значений. Это центр внимания нашего
// эксперимента. Макрос TYPE задает тип элементов вектора и должен
// быть задан в опциях при компиляции: -DTYPE=int или
// -DTYPE=bool соответственно.
std::vector<TYPE> S(n, true);

// Собственно, решето Эратосфена.
for (int i = 2; i < sqrt_n; ++i)
if (S[i])
for (int j = i*i; j < n; j += i)
S[j] = false;

// Подсчет количество найденных простых чисел. Делаем это для
// самопроверки.
int count = 0;
for (int i = 2; i < n; ++i)
if (S[i])
count++;

// Печатаем найденное количество.
std::cout << count << std::endl;

return 0;
}
Эксперимент я проводил на ноутбуке с процессором Core 2 1ГГц. Для конкретно этой машины я выбрал предел поиска в 10000000. При этом значении времена работы программы с одной стороны небольшие (удобно для повторения замеров), но другой стороны — весьма показательные.

Теперь компилятор. В забеге принимали участие:
  1. GNU g++ 3.4.4 (cygwin)
  2. Borland/Codegear bcc32.exe 5.93 (Codegear Studio 2007)
  3. Microsoft cl.exe 14.00 (Visual Studio 2005)
  4. Microsoft cl.exe 15.00 (Visual Studio 2008)
Операционная система Windows XP SP3.

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

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

Файл build.cmd:
bcc32 -DTYPE=bool -eera-bcc32-bool.exe era.cpp
bcc32 -DTYPE=int -eera-bcc32-int.exe era.cpp
bcc32 -O2 -DTYPE=bool -eera-bcc32-bool-opt.exe era.cpp
bcc32 -O2 -DTYPE=int -eera-bcc32-int-opt.exe era.cpp

g++ -DTYPE=bool -o era-g++-bool.exe era.cpp
g++ -DTYPE=int -o era-g++-int.exe era.cpp
g++ -O3 -funroll-all-loops -fomit-frame-pointer -mtune=nocona -DTYPE=bool -o era-g++-bool-opt.exe era.cpp
g++ -O3 -funroll-all-loops -fomit-frame-pointer -mtune=nocona -DTYPE=int -o era-g++-int-opt.exe era.cpp

call cl2008.cmd
cl /EHsc /DTYPE=bool /Feera-cl2008-bool.exe era.cpp
cl /EHsc /DTYPE=int /Feera-cl2008-int.exe era.cpp
cl /EHsc /arch:SSE2 /O2 -DTYPE=bool /Feera-cl2008-bool-opt.exe era.cpp
cl /EHsc /arch:SSE2 /O2 -DTYPE=int /Feera-cl2008-int-opt.exe era.cpp

call cl2005.cmd
cl /EHsc /DTYPE=bool /Feera-cl2005-bool.exe era.cpp
cl /EHsc /DTYPE=int /Feera-cl2005-int.exe era.cpp
cl /EHsc /arch:SSE2 /O2 -DTYPE=bool /Feera-cl2005-bool-opt.exe era.cpp
cl /EHsc /arch:SSE2 /O2 -DTYPE=int /Feera-cl2005-int-opt.exe era.cpp
При скрипты cl2005.cmd и cl2008.cmd я уже писал.
После компиляции должны получиться 16 исполняемых файлов с сообразными именами.

Далее, запуск. Для этого можно использовать следующий скрипт.

Файл run.cmd:
ntimer -1 era-cl2005-bool.exe 10000000
ntimer -1 era-cl2005-int.exe 10000000
ntimer -1 era-cl2005-bool-opt.exe 10000000
ntimer -1 era-cl2005-int-opt.exe 10000000

ntimer -1 era-cl2008-bool.exe 10000000
ntimer -1 era-cl2008-int.exe 10000000
ntimer -1 era-cl2008-bool-opt.exe 10000000
ntimer -1 era-cl2008-int-opt.exe 10000000

ntimer -1 era-bcc32-bool.exe 10000000
ntimer -1 era-bcc32-int.exe 10000000
ntimer -1 era-bcc32-bool-opt.exe 10000000
ntimer -1 era-bcc32-int-opt.exe 10000000

ntimer -1 era-g++-bool.exe 10000000
ntimer -1 era-g++-int.exe 10000000
ntimer -1 era-g++-bool-opt.exe 10000000
ntimer -1 era-g++-int-opt.exe 10000000
Для измерения времени работы я использовал программу ntimer. Ее нужно скачать, распаковать и положить ntimer.exe в текущий каталог. Будучи запущенной с ключом "-1" эта программа печатает времена в одну строку. Нас интересует самое первое печатаемой ей время.
Барабанная дробь! Запускаем...

Таблица с временами работы (по порядку):
КомпиляторВерсияТип элементаОптимизацияВремя (сек.)
Visual Studio 200514.00boolВыкл23.75
Visual Studio 200514.00intВыкл1.75
Visual Studio 200514.00boolВкл1.171
Visual Studio 200514.00intВкл1.312
Visual Studio 200815.00boolВыкл23.062
Visual Studio 200815.00intВыкл1.703
Visual Studio 200814.00boolВкл2.390
Visual Studio 200814.00intВкл1.312
Borland/Codegear 20075.93boolВыкл8.375
Borland/Codegear 20075.93intВыкл1.296
Borland/Codegear 20075.93boolВкл8.156
Borland/Codegear 20075.93intВкл1.328
gcc (cygwin)3.4.4boolВыкл4.640
gcc (cygwin)3.4.4intВыкл3.109
gcc (cygwin)3.4.4boolВкл0.984
gcc (cygwin)3.4.4intВкл1.343

А теперь в отсортированном виде по возрастанию времени:

КомпиляторВерсияТип элементаОптимизацияВремя (сек.)
gcc (cygwin)3.4.4boolВкл0.984
Visual Studio 200514.00boolВкл1.171
Borland/Codegear 20075.93intВыкл1.296
Visual Studio 200514.00intВкл1.312
Visual Studio 200814.00intВкл1.312
Borland/Codegear 20075.93intВкл1.328
gcc (cygwin)3.4.4intВкл1.343
Visual Studio 200815.00intВыкл1.703
Visual Studio 200514.00intВыкл1.75
Visual Studio 200814.00boolВкл2.390
gcc (cygwin)3.4.4intВыкл3.109
gcc (cygwin)3.4.4boolВыкл4.640
Borland/Codegear 20075.93boolВкл8.156
Borland/Codegear 20075.93boolВыкл8.375
Visual Studio 200815.00boolВыкл23.062
Visual Studio 200514.00boolВыкл23.75

Итак, на первом месте gcc в режиме bool с оптимизацией. На втором месте Visual Studio снова в режиме bool и оптимизацией. Интересно выступил борландовый компилятор, получив третье место, причем без оптимизации. Так как априори борландовый bcc32.exe считается весьма посредственным компилятором в плане качества кода и оптимизатора, то полученное им третье место весьма и весьма странно.
Конечно, пытливый читатель сразу заметит, что я как-то очень лихо проскочил один очень важный вопрос, а именно — версию STL. Не могу поручиться, что каждый из этих компиляторов поставляется с абсолютно неизменной и, как принято считать, "стандартной" версией этой библиотеки. Каждая фирма что-то меняет всегда под себя.
В итоге, я так и не получил однозначного ответа на изначальный вопрос — пользоваться ли std::vector вместо std::vector или нет. Слишком много побочных факторов. Поэтому я бы посоветовал, если вы встали перед такой же дилеммой в вашем проекте, провести эксперимент на месте с вашим конкретным компилятором, вашей версией STL, на вашей конкретной платформе и т.д., то есть с учетом всех ваших факторов. Можно использовать приведенные мной программы и скрипты. Если у вас будут интересные и неоднозначные результаты, пишите.