Понятно, что отыскать оригинал игры и запустить его на современном компьютере под Linux я даже не мечтал. Но, как оказалось, эту игру, как и многие другие игры под целый ряд устаревших платформ, можно запустить в эмуляторе, написанном под виртуальную машину Java прямо в окне браузера. Для этого достаточно зайти на сайт http://www.oldgamz.com/. Собственно Dangerous Dave находится здесь: http://www.oldgamz.com/?p=1876.
17 июля 2013
Dangerous Dave In the Haunted Mansion on-line
Понятно, что отыскать оригинал игры и запустить его на современном компьютере под Linux я даже не мечтал. Но, как оказалось, эту игру, как и многие другие игры под целый ряд устаревших платформ, можно запустить в эмуляторе, написанном под виртуальную машину Java прямо в окне браузера. Для этого достаточно зайти на сайт http://www.oldgamz.com/. Собственно Dangerous Dave находится здесь: http://www.oldgamz.com/?p=1876.
01 февраля 2013
On-line компилятор C++11
21 ноября 2012
Mobile Atlas Creator для создания карт TrekBuddy
Немного поизучав существующие решения, я обнаружил удобную кроссплатформенную программу под названием Mobile Atlas Creator. Она предназначена для загрузки карт с целого ряда картографических ресурсов и сохранения их в необходимом формате. Кроме TrekBuddy, программа может генерировать карты для значительного количества различных навигаторов и, что особенно интересно, доступна генерация атласов для печати.
После загрузки архива с программой, его надо распаковать. Для Linux-систем, как у меня, нужно найти файл Mobile_Atlas_Creator.jar и установить для него право на запуск. После этого, его можно запустить, что приведет к отображению главного окна приложения. С левой стороны окна находится ряд панелей для управления процессом загрузки карты, а в центре показана сама карта. Для того, чтобы начать генерацию атласа, нужно в самой верхней левой панели Map source выбрать источник данных. По умолчанию будет установлен OpenStreetMap MapQuest, который является легальным источником данных с OpenStreetMap. Следующая панель Zoom levels определяет уровень увеличения карты. Я устанавливаю 16, это позволяет добиться определенного компромисса между читабельностью и размером карты. После этого, в центральном окне, необходимо выделить фрагмент карты, который будет сохранен (обратите внимание на размеры, чем больше карта, тем больше времени она будет загружаться и, в последствии, занимать памяти). Для изменения масштаба карты в окне можно воспользоваться колесом мыши, а для скроллинга ― перемещением мыши с зажатой правой кнопкой или курсорными клавишами. Затем, в панеле Atlas Content надо нажать кнопку New и указать тип платформы, для которой будет создан атлас (в моем случае, это TrekBuddy tared atlas), потом нажать кнопку Add selection. Больше никаких дополнительных настроек не требуется, нажимаем Create atlas и ждем некоторое время. Если выбранный фрагмент карты достаточно большого размера, скорее всего загрузка закончится с ошибкой и программа спросит, генерировать ли карту, если не все фрагменты загружены. Особенно переживать в этом случае не надо. Дело в том, что приложение запоминает все те фрагменты, которые удалось загрузить и, при повторной попытке, качает только недозагруженные составляющие. Кстати, база загруженных изображений хранится в каталоге приложения mapsources и может быть очищена нажатием кнопки Settings с последующим переходом на закладку Tile store.
Просмотреть, сколько фрагментов осталось загрузить можно при помощи кнопки Show coverage на панели Map source tile store coverage. Не загруженные фрагменты отображаются серым цветом, остальные ― зеленым. После нескольких итераций загрузки, все фрагменты оказываются на месте и программа создает атлас. Под Linux результат будет находиться в подкаталоге atlases домашнего каталога.
В моем случае, полная карта Киева заняла порядка 26 МБ, я нашел соответствующий tar-файл и скопировал его на телефон. Теперь у меня достаточно актуальная и точная карта города.
15 августа 2012
Sublime Text как среда разработки
Первое впечатление оказалось действительно приятным: очень красивый, очень быстрый, кроссплатформенный, расширяемый, имеет достаточное количество плагинов для разработки на Python. Кроме всего этого, редактор предлагает две функции, которые отличают его от аналогов. Это, во-первых, возможность пакетного ввода, когда можно расставить несколько курсоров по тексту и писать одно и то же сразу в нескольких местах. Во-вторых, с правой стороны окна редактирования по умолчанию располагается карта файла ― сильно уменьшенный текст, который облегчает навигацию по большим файлам.
И то, и другое очень удобно, но именно из-за этих возможностей я не буду использовать редактор как среду разработки и, особенно, не стал бы рекомендовать его начинающим разработчикам.
На самом деле, возможность писать одинаковый текст в нескольких местах, конечно, упрощает процесс разработки, особенно в случаях, когда действительно требуется большое количество однообразного кода. Но, с другой стороны, такой подход провоцирует дублирование, и, следовательно, нарушение одного из основополагающих принципов программирования ― Don't Repeat Yourself.
Аналогично, карта файла упрощает поиск требуемого фрагмента в файлах больших размеров. Соответственно, снижается порог ощущения дискомфорта, связанного с тем, что модуль или класс вырос настолько, что его уже сложно понять и пора как-то отрефакторить.
На мой взгляд, весь этот функционал порожден сложностями поддержки значительных объемов legacy code, написанного, например, на PHP и для проектов, разрабатываемых с использованием современных методологий, даже вреден. Так что я, на данный момент, остаюсь с Eclipse + PyDev.
03 апреля 2012
Пример преждевременной оптимизации
Наверное, каждый, кто называет себя программистом, слышал когда-нибудь мысль Дональда Кнута о преждевременной оптимизации. ...Premature optimization is the root of all evil... и все такое. Но почему-то большинство думает, что это к ним не относится. Каждый из нас сам знает, когда оптимизировать, а когда нет.
minValue = min(a);
maxValue = max(a);
avgValue = sum(a) / a.length;
Но мы-то с вами умные, понимаем, что поиск минимального ― один проход по массиву, максимального и суммы ― еще по одному. А на самом деле все можно написать в одном цикле:
minValue = a[0];
maxValue = a[0];
sumValue = 0;
for (int i = 0; i < a.length; i++) {
if (a[i] > maxValue) {
maxValue = a[i];
}
if (a[i] < minValue) {
minValue = a[i];
}
sumValue +=a[i]
}
avgValue = sumValue / a.length;
Вот, гордо думаем мы, теперь все будет работать значительно быстрее. Но, на самом деле, что мы выиграли? Во-первых, теоретически, вычислительная сложность второго варианта будет O(n), а первого... O(n). Во-вторых, практически все будет еще хуже. С одной стороны, т.к. то, что мы делаем является лишь частью проекта, в состав которого входит также измерение температуры, наш код будет выполняться, к примеру, лишь тогда, когда массив значений окажется заполненным. Одно измерение температуры длится несколько секунд, а все расчеты ― милисекунды. Т.е. на фоне измерений, время расчета будет совсем незаметным. С другой стороны, функции стандартной библиотеки могут иметь эффективную реализацию для данной платформы, например, с использованием векторных инструкций (SSE). Тогда первый вариант кода окажется не в 3 раза медленнее второго, а наоборот быстрее.
18 января 2012
Mock-объект для текстового файла в Python
Достаточно часто встречается ситуация, когда какой-нибудь библиотечный файл требует в качестве параметра имя текстового файла или соответствующий файловый объект. Рассмотрим, например, задачу разбора ini-файла стандартной структуры: сначала идет имя секции в квадратных скобках, затем список параметров и их значений. В Python эта задача решается с помощью модуля стандартной библиотеки ConfigParser. Код выглядит приблизительно так:
def getPropertyA(configFile):Данный пример откроет файл с именем configFile и передаст его файловый дескриптор объекту класса ConfigParser. Метод readfp() выполнит разбор файла, а метод get() вернет, в данном случае, свойство с именем a из секции A.
Config = ConfigParser.ConfigParser()
Config.readfp(open(configFile, 'r'))
return Config.get('A', 'a')
test = StringIO.StringIO("[A]\na = 1\nb = 2")Переменная test является mock-объектом, для которого реализованы функции файлового ввода/вывода, т.е. ее можно передать в качестве параметра в readfp(). Остается еще одна проблема: в приведенном выше коде для получения файлового объекта применяется функция open(). Понятно, что с объектом test она работать не будет. Решить эту проблему можно, благодаря тому, что в Python функции являются объектами первого класса. Зададим в функции парсинга дополнительный параметр по умолчанию:
def getPropertyA(configFile, open = open):В данном случае, локальная переменная open получает в качестве значения функцию open() из глобального пространства имен, которая, собственно, предназначена для открытия файла. Если второй параметр не задан, используется значение по умолчанию, и функция корректно работает с именем файла в качестве первого параметра. В случае же, когда мы передаем mock-объект, функцию open() надо переопределить так, чтобы она просто возвращала то, что получила на входе. Тестовый случай будет выглядеть так:
Config = ConfigParser.ConfigParser()
Config.readfp(open(configFile, 'r'))
return Config.get('A', 'a')
self.assertEqual(getPropertyA(test, open = lambda s, t: s), "1")Анонимная функция получает два параметра, для имени файла и режима открытия, а возвращает только первый.
02 января 2012
Как открыть файл .eml под Linux?
Я столкнулся с проблемой открытия файлов с расширением .eml, когда ко мне пришло несколько писем, отправленные из MS Outlook. Оказывается, эта программа, если нажать на кнопку "переслать письмо" (форвард), перед пересылкой упаковывает его в файл .eml. Открыть его под Linux не составляет труда, например, с помощью почтового клиента Mozilla Thunderbird, однако такое решение мне показалось слишком тяжеловесным. На самом деле, когда я немного почитал документацию, оказалось, что .eml является простым текстовым файлом. Причем, письмо в этом файле соответствует rfc822, а в стандартной библиотеке Python есть модуль email, который прекрасно умеет разбирать такие файлы.
В результате вышел небольшой скрипт, который можно сохранить, например, под именем uneml.py. Скрипт принимает в качестве параметров имя или имена файлов .eml, отображает в консоли текстовое содержимое письма, а прикрепленные файлы сохраняет в текущем каталоге.
import sys, email
DELIMITER_SIZE = 60
def printMes(mes):
print '\n' + '-' * DELIMITER_SIZE
print mes
print '-' * DELIMITER_SIZE + '\n'
for arg in sys.argv[1:]:
try:
message = email.message_from_file(open(arg, 'r'))
printMes(arg)
for part in message.walk():
if part.get_content_maintype() == 'multipart':
continue
if part.get('Content-Disposition') is None:
print part.get_payload(decode=1)
continue
filename = part.get_filename()
if not(filename):
filename = "___.txt"
print "%s saving" % filename
fp = open(filename, 'wb')
fp.write(part.get_payload(decode=1))
fp.close
except:
printMes ("%s is not email" % arg)
18 ноября 2011
О том, что не должен знать современный программист
В очень неплохой, правда уже немного старой, книге по Agile разработке Р. Мартина с соавторами "Быстрая разработка программ" встретилось неожиданное примечание: "Когда Кен Бек и Джим Ньюкирк выполняли рефакторинг этой программы, они вообще исключили операцию извлечения квадратного корня. Кен обосновал это тем, что понятие квадратного корня трудно для понимания..." Как-то стало грустно за современных программистов, которым трудно понять квадратный корень.
08 октября 2011
Доступ к глобальным переменным в Python
| Что-нибудь C-подобное | Python |
int a = 5; void f() { a = 7; } int main() { print (a); f(); print (a); return 0; } |
a = 5 def f(): a = 7 print a f() print a |
| Результат: | Результат: |
| 5
7 |
5
5 |
В языках со статической типизацией объявление переменной, как правило, отличается от оператора присваивания. В первой строке нашего примера для C-подобного языка происходит и объявление, и инициализация, а в третьей строке, в теле функции, только присваивание, что различается синтаксически. А для Python, и первая и третья строка синтаксически одинаковы. Если бы интерпретатор Python работал так же, как принято в C-подобных языках, было бы невозможно объявить локальную переменную с тем же именем, которое уже имеет глобальная переменная. Чтобы избежать подобных ситуаций, присваивание в Python всегда работает в самой локальной области видимости. Для нашего примера это аналогично тому, что мы бы написали int a = 7 в теле функции в левом столбике примера.
В результате мы получаем полезное свойство языка: он не позволяет случайно переприсвоить значение переменной из глобальной области видимости. Если все же надо изменить значение в глобальном контексте, можно воспользоваться ключевым словом global и результат получится ожидаемым:
a = 5 def f(): global a a = 7 print a f() print a
На самом деле все немного сложнее, и для того, кто хочет разобраться подробнее, рекомендую почитать про правило LEGB.
Еще немного о Python:
Карринг и композиция в Python
Python: генерация паролей
Несколько примеров на list comprehension в Python
Генераторы Python и ленивые вычисления
Обход дерева каталогов в Python
23 февраля 2011
Оптимизация с учетом кэша
Хочу написать об очевидных вещах. Несмотря на их очевидность, у меня создалось впечатление, что сегодня, в эпоху высокоуровневого программирования, мало кто обращает на них внимание.
Всем известно, что современные вычислительные системы имеют иерархическую организацию памяти. Ее суть можно понять из рисунка.
Существует очень быстрая память, способная работать на одной или близкой частоте с процессором. К сожалению, много такой памяти использовать нельзя как по техническим, так и по экономическим соображениям. Кроме того, есть менее быстрая память, которая существенно дешевле, и, поэтому ее можно использовать в больших объемах. Наконец, есть жесткий диск, который совсем медленный, но который имеет совершенно гигантскую емкость, по сравнению с остальными видами памяти. Для наиболее эффективного использования всего этого многообразия, различные виды памяти соединяют каскадно, один за другим. При обращении процессора к определенной ячейке, сначала производится поиск в наиболее близкой к нему памяти (для большинства современных компьютеров это кэш первого уровня — L1). Если искомое значение там есть, начинается его обработка, если нет — происходит обращение к следующему уровню памяти и т. д. Найденное значение, по дороге к процессору, дублируется на всех уровнях, позволяя при повторном обращении, найти его сразу. Учитывая то, что для многих алгоритмов вероятность повторного обращения к одной и той же переменной в течении малого промежутка времени достаточно высока, такая иерархическая модель позволяет значительно улучшить производительность системы в целом.
Конечно, т. к. каждый более высокий уровень памяти меньше по объему, новые значения вынуждены вытеснять старые, что, естественно снижает эффективность кэширования.
Кэширование данных выполняется при помощи аппаратуры, поэтому является прозрачным для программиста. Но, несмотря на это, программист может использовать некоторые особенности кэша для повышения быстродействия собственных программ. При дальнейшем рассмотрении будем считать, что мы имеем дело с двухуровневой памятью, т. к. многоуровневые структуры всегда можно свести к такому варианту.
Одной из таких особенностей является то, что чтение участка памяти в последовательном порядке быстрее, чем в произвольном. Если в следующем фрагменте кода
for(int i = 0; i < N; ++i) {
int j = rand() % N;
s += a[i] + j;
}
a[i] заменить на a[j], на моей машине он будет выполняться где-то в 4 раза быстрее. Расчет суммы добавлен для того, чтобы не возникло мысли, что компилятор что-нибудь соптимизировал. Разница в быстродействии связана с тем, что данные из памяти в кэш читаются не по одной ячейке, а блоками. Так проще с точки зрения аппаратной реализации. Поэтому, когда мы читаем одну ячейку, очень вероятно, что следующая за ней (или несколько следующих, в зависимости от размера блока в кэше и размера читаемых данных) тоже окажется в кэше. Тогда последующее чтение приведет к обращению только в кэш, что значительно быстрее.
Этот факт приводит к некоторым неожиданным последствиям. Например, базовые знания об алгоритмах говорят нам, что временная сложность обхода матрицы M×N и по строкам, и по столбцам составляет O(M×N). Однако, если в коде
заменить a[i][j] на a[j][i], производительность упадет в 2 раза. Очевидно, что двумерная структура в памяти вытягивается в линейную строка за строкой.
for (int i = 0; i < N; ++i) {
for (int j = 0; j < M; ++j) {
s += a[i][j];
}
}

Поэтому, обход по строкам представляет собой последовательное чтение, а вот при проходе по столбцам, каждая следующая ячейка находится на M дальше, что при больших размерах матрицы гарантирует промах. Таким образом, если планируется обходить матрицу по столбцам, имеет смысл хранить ее в транспонированном виде.
Приведу еще один пример из области объектно-ориентированного программирования. Пусть у нас есть класс
class A {
public:
int x, y, z, t;
};
Объявим достаточно большой массив экземпляров класса:
A a[N];
, а затем пройдем по его полю x:
int s = 0;
for (int i = 0; i < N; ++i) {
s += a[i].x;
}
Т. к. члены класса хранятся последовательно, при чтении первого x в кэш попадет также y, возможно и z, но не второй x.

За счет разреженности x-ов, производительность такого обхода будет в 1,5 раза ниже (для моей машины), чем при хранении данных в виде отдельных массивов:
int x[N], y[N], z[N], t[N];
Это значит, что при большом количестве экземпляров класса для эффективного обхода какого-нибудь из его полей, иногда имеет смысл строить дополнительный массив-индекс.
Несмотря на очевидность примеров, всегда необходимо помнить, что производить какие-либо оптимизации можно лишь тогда, когда профайлером на рабочих примерах показана их необходимость.
26 января 2011
C++0x threads, случайные числа и метод Монте-Карло
В основе моделирования методом Монте-Карло (МК), как известно, лежит генерация большого числа случайных чисел (СЧ) с заданными вероятностными характеристиками. По сути, метод состоит в проведении очень большого числа независимых экспериментов, а т.к. они независимые, значит их можно выполнять параллельно, и распараллеливание алгоритмов, основанных на методе МК, должно быть очень эффективно.
Я решил проверить это на классическом алгоритме для расчета числа π. Алгоритм состоит в следующем. Берется квадрат со стороной единичной длины и в нем рисуется сектор круга единичного радиуса с центром в одном из углов.
Затем в этот квадрат начинают многократно бросать дротик. Отношение количества попаданий в сектор круга к общему количеству бросков будет приблизительно соответствовать отношению площадей четверти круга и квадрата. Чем больше бросков, тем выше точность. Если площадь четверти круга рассчитывается по формуле S = ¼πR², а площадь квадрата — S' = a², где a — длина стороны квадрата, а R — радиус круга, то при a = R = 1, π = 4(S/S').
Броски дротика можно моделировать путем генерации равномерно распределенных случайных чисел (x, y) в интервале [0; 1], тогда их принадлежность сектору круга будет определятся по формуле x² + y² ≥ 1.
Последовательная версия.
#include <iostream>
#include <cstdlib> // тут генератор СЧ
#include <time.h> // системное время для инициализации генератора
using namespace std;
const long N = 1000000000; // количество бросков в мешень
int main()
{
srand(time(0)); // инициализация генератора
long hit = 0; // количество попаданий
double x, y; // координаты попаданий, оказывается, так на 2 с быстрее,
// чем если объявлять непосредственно в цикле
for(long i = 0; i < N; ++i) {
x = double(rand()) / RAND_MAX; // получаем новую пару координат
y = double(rand()) / RAND_MAX; // в промежутке 0..1
if (x * x + y * y < 1) ++hit; // +1, если попали
}
double pi = 4 * (double(hit) / N); // т.к. бросали только в 1/4 круга
cout << "pi = " << pi << endl;
return 0;
}
Эта программа позволяет получить значение π = 3.14157 для 1.000.000.000 бросков и работает у меня на 4-ядерном Athlon 2,7 МГц 1 минуту и 4 секунды. При этом, загрузка процессора составляет около 25%, т.к. используется только 1 ядро. Естественно, возникает желание ее ускорить за счет распараллеливания: ничего не мешает нам бросать по 4 дротика за раз. Ядер-то 4.
Но это оказывается не так просто: генератор СЧ, который, по сути, является генератором псевдо-СЧ, представляет собой функцию, рассчитывающую каждое новое число на основе предыдущего. Предыдущее число принято называть seed, и оно, фактически, является скрытым состоянием функции-генератора. Это означает, что такая функция не потокобезопасна, и в многопоточной среде ею пользоваться нельзя.
Способом решения этой проблемы является использование функций, которые получают seed в качестве параметра. К таким функциям относится rand_r(), однако тут возникает следующая проблема. Эта функция возвращает СЧ типа int, которых на платформе x86 может быть всего 232. Это значит, что в лучшем случае через 4.294.967.295 чисел последовательность чисел начнет повторяться. Для 1.000.000.000 итераций в последовательной реализации это будет означать, что все СЧ будут разными (на каждый бросок 2 итерации, т.е. 2.000.000.000 чисел). Для 4-х потоков нужно 4 генератора, а т.к. алгоритм генерации одинаков, то при близко расположенных seed'ах в параллельных потоках мы будем получать большой процент одинаковых чисел, что приведет к смещению мат. ожидания генератора.
В приведенной ниже реализации (конечно же, с использованием потоков C++0x, кстати, компилировать ее нужно с ключами -std=c++0x и -pthread) я постарался разнести как можно дальше seed'ы каждого потока (time(0) * i), чтобы уменьшить наложение последовательностей одинаковых значений, но значение π колебалось от 3.1234 до 3.1478. Зато время вычисления составило 15 с, что в 4,4 раза быстрее последовательного варианта. Кроме rand_r() с 32-битным seed'ом, есть еще ряд функций с 48-битным (закомментированный вариант). Использование чисел с большей точностью приводит к снижению производительности — до 22 с, всего в 3 раза быстрее последовательной версии, но зато точность остается на уровне π = 3.14149 (ее, вероятно, можно еще улучшить за счет увеличения количества бросков).
Параллельная версия.
Еще о C++0x:
#include <iostream>
#include <cstdlib>
#include <thread> // тут работа с потоками
#include <time.h>
using namespace std;
const long N = 1000000000;
const int P = 4; // количество процессоров
int main()
{
thread* t[P];
mutex m; // надо для синхронизации, когда будем собирать общую сумму
long hit = 0;
for(int i = 0; i < P; ++i) { // запускаем P потоков
t[i] = new thread([&hit, &m, N, i]() { // это лямбда-функция
long hit_ = 0; // локальный счетчик попаданий
double x, y;
unsigned int r = time(0) * i; // i чтобы гарантировать разные seed
// во всех потоках
// --- это вариант с nrand48() ---------------
// unsigned short seed[3];
// unsigned int r = time(0) * i;
// copy(seed, seed + 1, &r);
// -------------------------------------------
for(long i = 0; i < N / P; ++i) { // N / P в P потоках будет ~ N
x = double(r = rand_r(&r)) / RAND_MAX;
y = double(r = rand_r(&r)) / RAND_MAX;
// --- это вариант с nrand48() ---------------
// x = erand48(seed);
// y = erand48(seed);
// -------------------------------------------
if (x * x + y * y < 1) ++hit_;
}
lock_guard<mutex> lg(m); // блокировка до конца блока, чтобы не было
hit += hit_; // конкуренции за hit
});
}
for(int i = 0; i <>join();
}
double pi = 4 * (double(hit) / N);
cout << "pi = " << pi << endl;
return 0;
}
Еще о параллелизме:
30 ноября 2010
C++0x: lambda functions
Наверное, около месяца назад установил на домашней машине gcc версии 4.5. Эта версия в достаточно полном объеме поддерживает стандарт C++0x, который, в частности, позволяет объявлять в программе анонимные функции и, более того, захватывать текущие значения переменных (контекст) в тело функции. В стандарте эта возможность названа lambda functions, а, в принципе, захват контекста при создании функции обычно называют лексическим замыканием. Самый простой пример такого замыкания выглядит следующим образом.
#include <iostream>
int main() {
auto a = 2;
auto twice = [a] (int x) { return a * x; };
std::cout << "2x2=" << twice(2) << std::endl;
a = 3;
std::cout << "2x2=" << twice(2) << std::endl;
return 0;
}
Здесь мы создаем анонимную функцию и присваиваем ее переменной twice. auto означает, что компилятору предлагается самому вывести тип переменной на основании инициализатора. В первом случае, тип a будет int (сделано исключительно ради иллюстрации), а во втором — lambda(int). Синтаксис объявления lambda function следующий: [список переменных для захвата] (список параметров функции) {тело функции}. При необходимости, можно добавить еще и тип возвращаемого значения (подробнее — хотя бы здесь). Вообще говоря, подобный синтаксис настораживает. Он позволяет писать в коде что-нибудь вроде [=](){}, и это будет нормально компилироваться. C++ от этого явно читабельнее не станет:)
Итак, при создании анонимной функции указано, что значение переменной a необходимо сохранить непосредственно в теле функции. Теперь, даже после модификации этой переменной в глобальном контексте, функция все равно будет умножать входной параметр на 2. В результате, программа выведет дважды «2x2=4». (Компилировать надо как-то так g++-4.5 -std=c++0x test-lambda.cpp)
Следующий пример демонстрирует случай, когда без замыкания не обойтись. Пусть мы хотим заполнить матрицу целыми случайными значениями так, чтобы в каждой строке любое из них не превышало номер строки. В данном случае, для заполнения напрашивается функция generate из стандартной библиотеки. В качестве последнего параметра мы можем передать анонимную функцию, которая будет генерировать случайные числа. Но тут возникает вопрос: как передать в функцию номер строки матрицы, если ее будет вызывать generate? Ответ прост, номер можно поместить в функцию до ее передачи, захватив значение из контекста.
#include <iostream>
#include <algorithm>
#include <iterator>
using namespace std;
int main() {
const int N = 10;
int a[N][N];
for (int i = 0; i < N; ++i) {
generate(a[i], a[i] + N, [=] () { return rand() % (i + 1); });
copy(a[i], a[i] + N, ostream_iterator<int> (cout, " "));
cout << endl;
}
return 0;
}
Результат работы будет следующим:
0 0 0 0 0 0 0 0 0 0
0 1 0 1 1 0 0 0 0 0
2 2 0 0 2 2 2 1 1 1
1 2 2 2 1 3 1 0 3 2
4 3 1 4 4 2 3 4 0 0
5 4 1 2 2 3 2 2 4 1
2 6 4 5 3 5 5 4 3 0
4 7 6 1 6 7 2 4 3 6
7 6 6 8 5 3 6 2 8 1
2 0 6 8 9 2 6 6 4 9
27 июля 2010
О роли женщины в искусстве программирования
Легендарные Б. Керниган и Р. Пайк в своей сравнительно свежей книге «Практика программирования» (1999 год, русский перевод — 2001) о приемах отладки пишут следующее: «...Другой эффективный способ — объяснить свой код кому-нибудь еще. Такое объяснение часто помогает самому увидеть свою ошибку ... Это просто замечательный метод, причем в качестве слушателей можно использовать даже непрограммистов...» За этим следует сноска: «По моему личному опыту, лучше объяснять программу не мужчинам, а женщинам: они чувствуют неуверенность в вашем голосе, когда вы объясняете трудное для вас место, и начинают "копаться" в нем, требуя более подробных объяснений.»
05 июля 2010
Почему не работает cron?
Сегодня на работе пришлось не много повозиться с cron'ом. Т.к. ранее мне не приходилось пользоваться утилитой crontab, я потратил некоторое время на один пустяк, о которых хотел бы рассказать. Вдруг, кому-нибудь пригодится.
Итак, почему у меня не работал cron? cron — это программа-демон, предназначенная для периодического запуска пользовательских программ через определенные промежутки времени. Настройки запуска хранятся в специальных файлах, которые cron перечитывает каждую минуту, и, если пришло время, запускается указанная задача. Таким образом, минимальная частота запуска задач составляет 1 раз в минуту. Подробнее — man cron, crontab. Следует отметить, что программа может быть запущена от имени определенного пользователя, т.е. рабочим будет его домашний каталог, поэтому желательно указывать полные пути ко всем файлам.
Я не сразу смог настроить cron, потому что не дочитал страницу манов до конца:) Причина звучит так: «Although cron requires that each entry in a crontab end in a newline character, the neither the crontab command nor the cron daemon will detect this error. Instead, the crontab will appear load normally. However, the command will never run. The best choice is to ensure that your crontab has a blank line at the end.»
Т.е. в конце каждой строки настройки в файле crontab, должен стоять символ перевода строки, даже если в файле всего одна строка. Причем, crontab не сообщает о том, что произошла ошибка, а просто cron потом не работает.
23 марта 2010
Синхронизация в потоках POSIX: семафоры, мютексы, спин-локи и атомарные операции
Для потоков POSIX я знаю 3 наиболее популярных способа синхронизации:
- семафоры, наиболее тяжеловесный, но и наиболее функциональный способ; семафор предоставляет в распоряжение программиста потокобезопасный счетчик с возможностью блокирования текущего потока в случае, когда значение счетчика доходит до 0; при всех операциях с семафором происходит переключение в режим ядра и обратно, что, естественно, плохо сказывается на производительности;
- мьютексы, фактически семафоры на 2 значения — истина или ложь, заблокирован ли вход в критическую секцию или нет; реализация POSIX обещает, что только в режиме ожидания происходит переключение в ядро, а разблокирование выполняется в режиме пользователя; это, по идее должно несколько снизить вычислительные расходы на синхронизацию;
- спин-локи, аналог мьютекса, но и блокирование и разблокирование происходят в процессе пользователя; за это приходится платить активным ожиданием в момент блокирования (это означает, что поток в цикле будет непрерывно опрашивать переменную синхронизации), которое нагружает процессор на 100%; понятно, что использовать спин-локи имеет смысл только для очень коротких критических секций.
Чтобы протестировать производительность с использованием разных методов синхронизации, я написал простую программу: в N потоках (задается параметрами командной строки) параллельно 1.000.000 / N раз происходит увеличение глобальной переменной на 1. В результате выполнения такой программы должен получиться 1.000.000. Естественно, если вообще не использовать синхронизацию, результат будет несколько отличаться в меньшую сторону. Далее я привел 3 варианта программы с использованием разных способов синхронизации, а также 4-й вариант, в котором применена атомарная операция __sync_fetch_and_add (при компиляции надо добавлять ключи -DSEMAPHORE, -DMUTEX и -DSPIN, либо без ключей для атомарной операции). При компиляции такая инструкция приводит к добавлению перед ассемблерной командой прибавления префикса lock, гарантирующего атомарность на физическом уровне.
#include <iostream>
#include <pthread.h>
#include <cstdlib>
#ifdef SEMAPHORE
#include <semaphore.h>
sem_t s;
#define init sem_init(&s, 0, 1)
#define add sem_wait(&s); \
++a; \
sem_post(&s)
#define destroy sem_destroy(&s)
#elif MUTEX
pthread_mutex_t m;
#define init pthread_mutex_init(&m, NULL)
#define add pthread_mutex_lock(&m); \
++a; \
pthread_mutex_unlock(&m)
#define destroy pthread_mutex_destroy(&m)
#elif SPIN
pthread_spinlock_t l;
#define init pthread_spin_init(&l, 0)
#define add pthread_spin_lock(&l); \
++a; \
pthread_spin_unlock(&l)
#define destroy pthread_spin_destroy(&l)
#else
#define init
#define add __sync_fetch_and_add(&a, 1);
#define destroy
#endif
int a = 0;
const int N = 1000000;
int P = 1, M = N / P;
void* f(void*){
for(int i = 0; i < M; ++i) {
add;
}
}
int main(int argc, char *argv[]){
if (argc > 1) {
P = atoi(argv[1]);
M = N / P;
}
pthread_t t[P];
init;
for(int i = 0; i < P; ++i) {
pthread_create(&t[i], NULL, f, NULL);
}
for(int i = 0; i < P; ++i) {
pthread_join(t[i], NULL);
}
std::cout << a << std::endl;
destroy;
return 0;
}
В результате запуска этой программы с различными вариантами синхронизации на машине с 2-хядерным процессором для количества потоков 1, 2, 5, 10, 50 и 100, я получил следующее время выполнения:
Естественно, что атомарные операции оказались самыми быстрыми и, при этом, результат почти не зависит от количества потоков, семафоры несколько медленнее мьютексов, но зависимость от числа потоков незначительная. А вот спинлоки, показавшие хороший результат для 1 и 2-х потоков, оказываются значительно хуже с ростом числа потоков. Очевидно, что 2-х ядерный процессор может обслуживать только 2 потока в состоянии активного ожидания, что и объясняет полученный результат.
12 марта 2010
CUDA + Ubuntu 9.10
На работе появилась платка с поддержкой CUDA. Чтобы заставить корректно компилироваться код на C++ под Ubuntu 9.10 пришлось немного повозиться. Проблема в том, что на сегодня программное обеспечение для CUDA поддерживает только версии 8.10 и 9.04. Это связано с компилятором g++, который должен иметь версию 4.3, а в новой Ubuntu — 4.4.
Поэтому, при компиляции возникают ошибки:
desktop:~/cuda$ nvcc --host-compilation=c++ cap.cu -o cucap
/usr/include/c++/4.4/ext/atomicity.h(46): error: identifier "__sync_fetch_and_add" is undefined
/usr/include/c++/4.4/ext/atomicity.h(50): error: identifier "__sync_fetch_and_add" is undefined
2 errors detected in the compilation of "/tmp/tmpxft_00006255_00000000-4_cap.cpp1.ii".
Решением проблемы стала установка дополнительно g++ версии 4.3, которая, оказывается, умеет мирно сосуществовать с 4.4. Для этого достаточно выполнить команду:
sudo apt-get install g++-4.3
После этого компиляция заработала с добавлением ключа --compiler-bindir=/usr/bin/gcc-4.3:
nvcc --host-compilation=c++ cudaTest.cu --compiler-bindir=/usr/bin/gcc-4.3
Для того, чтобы проверить работоспособность CUDA, я написал небольшую программку, которая читает текст из файла, а, затем, преобразует все буквы в верхний регистр.
#include <iostream>Результат работы:
#include <iterator>
#include <fstream>
using namespace std;
__global__ void setCAPS(unsigned char *a) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if(a[i]>96 && a[i]<123) {
a[i] ^= 32;
}
}
int main() {
ifstream f("SonnetVII.txt", fstream::in|fstream::binary);
f >> noskipws;
f.seekg(0, ios::end);
int fileSize = f.tellg();
f.seekg(0, ios::beg);
unsigned char *a = new unsigned char[fileSize];
copy(istream_iterator<unsigned char> (f), istream_iterator<unsigned char>(), a);
copy(a, a + fileSize, ostream_iterator<unsigned char>(cout,""));
cout << endl;
unsigned char *devMem = NULL;
cudaMalloc((void **)&devMem, fileSize);
cudaMemcpy(devMem, a, fileSize, cudaMemcpyHostToDevice);
dim3 threads = dim3(512, 1);
dim3 blocks = dim3(fileSize / threads.x + 1, 1);
setCAPS<<<blocks, threads>>> (devMem);
cudaMemcpy(a, devMem, fileSize, cudaMemcpyDeviceToHost);
cudaFree(devMem);
copy(a, a + fileSize, ostream_iterator<unsigned char>(cout,""));
cout << endl;
delete[](a);
return 0;
}
Sonnet VIIИнтересно, что по производительности CUDA и CPU-версии одинаковы, но CUDA забирает на себя дополнительные 100 мс. При увеличении размера файла, CPU-версия начинает заметно выигрывать, вероятно, за счет копирования данных с девайса на хост и обратно. А вот когда я модифицировал функцию преобразования регистра вот так (аналогично была модифицирована и CPU-версия):
Lo! in the orient when the gracious light
Lifts up his burning head, each under eye
Doth homage to his new-appearing sight,
Serving with looks his sacred majesty;
And having climb'd the steep-up heavenly hill,
Resembling strong youth in his middle age,
yet mortal looks adore his beauty still,
Attending on his golden pilgrimage;
But when from highmost pitch, with weary car,
Like feeble age, he reeleth from the day,
The eyes, 'fore duteous, now converted are
From his low tract and look another way:
So thou, thyself out-going in thy noon,
Unlook'd on diest, unless thou get a son.
SONNET VII
LO! IN THE ORIENT WHEN THE GRACIOUS LIGHT
LIFTS UP HIS BURNING HEAD, EACH UNDER EYE
DOTH HOMAGE TO HIS NEW-APPEARING SIGHT,
SERVING WITH LOOKS HIS SACRED MAJESTY;
AND HAVING CLIMB'D THE STEEP-UP HEAVENLY HILL,
RESEMBLING STRONG YOUTH IN HIS MIDDLE AGE,
YET MORTAL LOOKS ADORE HIS BEAUTY STILL,
ATTENDING ON HIS GOLDEN PILGRIMAGE;
BUT WHEN FROM HIGHMOST PITCH, WITH WEARY CAR,
LIKE FEEBLE AGE, HE REELETH FROM THE DAY,
THE EYES, 'FORE DUTEOUS, NOW CONVERTED ARE
FROM HIS LOW TRACT AND LOOK ANOTHER WAY:
SO THOU, THYSELF OUT-GOING IN THY NOON,
UNLOOK'D ON DIEST, UNLESS THOU GET A SON.
__global__ void setCAPS(unsigned char *a) {получились следующие результаты:
int i = blockIdx.x * blockDim.x + threadIdx.x;
if(a[i]>96 && a[i]<123) {
for(int k = 0; k < 1000001; ++k) {
a[i] ^= 32;
}
}
}
desktop:~/cuda$ time ./cudacapsТ.е. использование CUDA эффективно в случае, когда большая часть вычислений выполняется на видеокарте без копирования данных в память компьютера.
real 0m0.294s
user 0m0.068s
sys 0m0.224s
desktop:~/cuda$ time ./cpucaps
real 0m1.758s
user 0m1.732s
sys 0m0.000s
16 января 2010
Брукс о сложности программных систем
Еще одна мысль Брукса, теперь о причинах сложности разработки программного обеспечения: «…Люди, связанные с программированием, не одиноки в проблемах сложности. Физика имеет дело с объектами чрезвычайной сложности даже на уровне элементарных частиц. Однако физик работает в твердой уверенности, что можно найти общие принципы, будь то кварки или общая теория поля. Эйнштейн неоднократно утверждал, что природа должна иметь простые объяснения, поскольку Богу не свойственны капризность и произвол.
У разработчика программного обеспечения нет такой утешительной веры. Сложность, с которой он должен совладать, по большей части является произвольной, необоснованно вызванной многочисленными человеческими установлениями и системами, которым должны удовлетворить его интерфейсы. Системы различаются интерфейсами и меняются во времени не в силу необходимости, а лишь потому, что были созданы не Богом, а разными людьми…»
Книга, если что, называется "Мифический человеко-месяц или как создаются программные системы", есть полностью на lib.ru
12 января 2010
Брукс о блок-схемах
Ф. Брукс в известной книге, изданной в 1975 г. пишет: «...Подробная пошаговая блок-схема является досадным анахронизмом, пригодным только для новичков в алгоритмическом мышлении. Введенные Голдштайном и фон Нейманом прямоугольники вместе со своим содержимым служили языком высокого уровня, объединяя непостижимые операторы машинного языка в осмысленные группы. Как давно понял Иверсон, в систематическом языке высокого уровня группировка уже проведена, и каждый прямоугольник содержит оператор. Поэтому сами прямоугольники являются утомительным и отнимающим место упражнением в черчении и вполне могут быть удалены...»
Когда я заканчивал университет (около 10 лет назад), единственно допустимым видом графического представления информации об алгоритмах были блок-схемы (в соответствии с ГОСТ 19.701-90, основанном на стандарте ISO 5807-85). На сколько я знаю, на сегодня ситуация остается прежней. На то время, блок-схемы не позволяли показать все тонкости реализации. Гораздо полезнее было бы использовать UML, но для него пока не существует ГОСТа или ДСТУ, позволяющего включать включать подобные диаграммы в конструкторскую документацию.
У того же Брукса: «...Апостол Петр сказал о новообращенных язычниках и законе Моисея: "Что же вы [желаете] возложить на выи учеников иго, которого не могли понести ни отцы наши, ни мы?" (Деяния апостолов 15:10). То же сказал бы я о программистах-новичках и устаревшей практике блок-схем...»
08 января 2010
Карринг и композиция в Python
При программировании на Python в функциональном стиле часто возникает необходимость в использовании такой классической операции, как карринг. Как оказалось, в стандартной библиотеке языка присутствует модуль functools, в котором реализована эта возможность. В двух словах, карринг — это конструирование новой функции на основе существующей путем частичного определения значений аргументов. Например:
from functools import partialДля приведенного выше примера можно использовать стандартный модуль operator, который содержит функции, аналогичные математическим операторам языка:
def plus(x, y): return x + y
plus2 = functools.partial(plus, 2) # определяем новую функцию как функцию plus,
# одним из аргументов которой будет число 2
plus2(2) # результатом будет 4
from operator import add # импортируем функцию сложения двух значенийЕще больше функциональных возможностей предоставляет модуль functional, к сожалению, не входящий в стандартные средства языка. Кроме всего прочего, этот модуль позволяет определять функцию, как композицию нескольких существующих функций. К примеру, определение g = f(h(x)):
map(functools.partial (add, 2), range(10)) # результат — [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
import functionalЕсли определить функцию, которая проверяет число на нечетность:
g = functional.compose(f, h)
def odd(x): return x % 2, то на ее основе легко можно построить функцию, проверяющую на четность (функция partial есть и в functional):
even = functional.compose (odd, functional.partial (add, 1))Тогда
filter(even, range(10))вернет [0, 2, 4, 6, 8].
Эти возможности особенно полезны для call-back-функций, например при описании реакций на события.
28 декабря 2009
52-й понедельник
Сегодня воспетый Петром Мамоновым 52-й понедельник. К сожалению, на youtube нет соответствующего видео для иллюстрации. Обычно, в году 52 недели, поэтому можно спокойно праздновать понедельник последней недели года. Но иногда случаются досадные недоразумения, например, в 2007 г. последняя неделя была 53. Для того, чтобы не пропустить столь знаменательный день, я написал небольшой скрипт на Python, который можно поставить в автозагрузку. datetime.date.today() позволяет получить текущую дату, а strftime("%w:%W") конвертирует ее в номер дня недели (%w, нумерация начинается с вокресенья) и номер недели в году (%W):
if datetime.date.today().strftime("%w:%W") == "1:52": print "С праздником!!"



