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

вторник, 27 декабря 2011 г.

Steve Jobs: The Exclusive Biography / Биография Стива Джобса от Уолтера Айзексона

Note: This post is in two languages. The English one is the second.


Биография Стива Джобса от Уолтера Айзексона от Уолтера Айзексона

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

Cначала это было похоже на пересказ фильма "Пираты силиконовой долины", но через пару глав я уже не мог оторваться.

Можно говорить, что это конъюнктура и попытка сыграть на ситуации, что Стив только что умер, но, несмотря на все эти предрассудки, я подсел практически сразу.

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

Вывод: настоятельно рекомендую, даже для принципиальных нелюбителей Эппла.


Steve Jobs: The Exclusive Biography by Walter Isaacson

I thought that biographies are for "old people". This book has changed that mindset in me. Being driven by massive ads and recently emerged love with Apple products and philosophy I had started reading.

Initially it was like an expanded version of "Pirates of Silicon Valley" which I, frankly, liked, but as reading progressed I had just fallen in love with this book and weren't able to stop.

The book can be treated as conjuncture or just an attempt to leverage the situation that Steve has just passed away. I was fully biased but after the first chapter I was hooked.

It is fascinating reading. Maybe just because the author is talented, I don't know.

Conclusion: strongly recommend.

суббота, 24 декабря 2011 г.

The world is flat 3.0 / Плоский мир 3.0

Note: This post is in two languages. The English one is the second.


Я снова вернулся к аудиокнигам. 40-50 минут в день по дороге на работу и потом обратно позволяют поглощать книги с приличной скоростью.

Из недавнего.

"The world is flat 3.0: A Brief History of the Twenty-first Century", Thomas L. Friedman

Убийственная книга для осознания, что:

  • сейчас индивидуумы могут спокойно конкурировать с корпорациями
  • важность инженерного, а особенно компьютерного, образования растет семимильными шагами, и это не дань моде, а объективная реальность
  • количество мест на планете, где можно жить и зарабатывать достойные деньги и развиваться уже не сужается к Штатами (разве не забавно слышать такое от коренного американца-консерватора) и некоторыми странам Европы
  • Индия, Китай, Россия и многие другие страны начали сильнейшую конкурецию на рынке труда с тем же США уже без массового отъезда специалистов
  • постоянное самообразование - это едиственный способ не быть вытолкнутым с рынка труда

И многое многое другое.

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

Это очень интересное и познавательное чтиво.


I've come back to audiobooks. 40-50 minutes per day when heading an office and then back allow to consume books with decent speed.

A recent.

"The world is flat 3.0: A Brief History of the Twenty-first Century", Thomas L. Friedman

This is a killer book to understand or even discover that nowadays:

  • individuals can compete with corporations
  • importance of engineering and especially computer education is increasing enormously
  • number of places on the Earth when you can have decent life doing what you love to do is not narrowed to the US and a few EU countries anymore
  • India, China, Russia and many other countries have a very strong position competing with even US now without immigration
  • permanent self education and nurturing your curiosity (CQ + PQ always greater that IQ) is the only way to remain valuable

And many other topics.

Frankly, there were moments in my life when I doubted and thought I would be better off doing, for instance, real estate, natural resources, ads or media rather than software. If you have even a tiny similar concern this book will wipe it out forever, and maybe even show the way.

To conclude: very useful, interesting and fascinating reading.

среда, 21 декабря 2011 г.

Виртуальные private-функции в C++ / Virtual private functions in C++

Note: This post is in two languages. The English one is the second.


Наткнулся на интересный, как мне показалось, код. Там использовалась виртуальная private функция. Прием немного странный, но сейчас не об этом.

Сначала мне показалось, что такой код не должен компилироваться, так как если функция private, она недоступна для использования в дочерних классах. Наблюдался какой-то очередной пробел в моих знаниях по C++.

Я написал программу:

#include <iostream>

class A {
public:
  void bar() { foo(); }
private:
  virtual void foo() = 0;
};

class B: public A {
private:
  virtual void foo() { std::cout << "B::foo()" << std::endl; }
};

int main(int argc, char* argv[]) {
  A* a = new B();
  a->bar();
  delete a;
  return 0;
}

И VS2010 и GCC прекрасно его съели, и программа печатает "B::foo()".

Напрашивание такое объяснение - механизм виртуальных функций (технически переопределение функций через vtable) - это runtime, а public/private - это compile time. Получается, что в compile time все законно, и разделение на private/protected/public не зависит от виртуальности функции, а в runtime класс B просто подставляет другую функцию через vtable уже вне зависимости от private/public.


I have come across an interesting in my point of view bit of code. There was a virtual private function. The approach is odd at the first place and I thought it shouldn't even compile, but surprisingly it did. I felt that this was yet another gap in my C++.

I wrote this code:

#include <iostream>

class A {
public:
  void bar() { foo(); }
private:
  virtual void foo() = 0;
};

class B: public A {
private:
  virtual void foo() { std::cout << "B::foo()" << std::endl; }
};

int main(int argc, char* argv[]) {
  A* a = new B();
  a->bar();
  delete a;
  return 0;
}

VS2010 and GCC compile it perfectly and it prints out "B::foo()".

I have concluded that the virtual function mechanism usually implemented via vtable is runtime, but public/private is compile time, and they don't depend on each other.

воскресенье, 18 декабря 2011 г.

Ведение блога на GitHub / GitHub as a blog engine

Note: This post is an attempt to adopt a technique of creating posts in two languages. Instead of maintaining two separate blogs I want to kill two birds with one stone – still write on my native language for majority of the readers and plus have an opportunity to practice in English with a few English speaking readers. The post has two parts. The second is a translation to English. Any feedback regarding the language is appreciated, even in comments.


Сказать, что Blogspot меня бесит – это ничего не сказать. Единственное для чего он нужен, как мне кажется – это быстрая индексация в Google.

Артемий Лебедев как-то отлично сказал, что презирает всякую хрень типа SEO, так как надо создавать интересный и читаемый контент, а не суетиться по мелочи на его раздаче. Поэтому его ЖЖ использует стандартный шаблон без каких-либо попыток выглядеть “клевым”. Но его контент делает блог одним из лидеров в российском прокате.

Да и кого сейчас волнует дизайн блога? Все равно подавляющее количество читателей видят твой блог через призму Google Reader’а.

Но оставим одиозного Лебедева и вернемся к нашим специализированным техническим блогам. Как написал Том Престон-Вернер, один их основателей Github, что ведя блог, он хочем писать посты, а не подкручивать там и сям шаблоны и прочие техдетали всяких WordPress’ов и Mephisto’в.

Подобная же мысль мучает меня с первого дня использования Блогспота. Только недавно я более менее устаканил процесс написания постов, используя ReST. Сейчас я пишу и храню посты в этой разметке, а перед публикацией прогоняю через пару доморощенным скриптов типа “ReST > HTML > Фильтрованный Для Блогспота HTML”. Хотя проблема хостинга картинок все равно решается вручную заранее.

Уже сотни раз я подумывал о собственной платформе на том же WordPress’е, но природная лень и нежелание тратить ни секунды на администрирование всегда останавливало.

Сейчас у меня очередной приступ ненависти к Блогспоту и, как следствие, исследование альтернатив.

Удивительно, но как-то никогда не задумывался, что блог может на быть статическом движке.

А что, если переехать на GitHub Pages? Там суть в том, что один из твоих репозиториев может стать вебсайтом, который работает на статическом движке Jekyll.

Сразу убиваются несколько зайцев. Работаешь в нормальной разметке и используешь git для выкладывания постов. Пишешь и отлаживаешь пост локально. Затем “git push origin master” и все – пост выложен. Текст можно писать не в идиотском HTML, а в Markdown или Textile. ReST, конечно, круче, но это не смертельно.

Более того GitHub Pages позволяет интегрироваться с полноценным доменом второго уровня.

Далее тема комментариев. Не то, чтобы программировать свой движок для комментирования, а даже просто настроить WordPress – лично у меня нет никакого желания. Поэтому одна их самых простых альтернатив – Disqus.

Анализ посещаемости решается через Google Analytics.

Что остается?

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

Но, возвращаясь к вопросу – а нужны ли все эти гаджеты? Если контент в блоге интересен – он найдет своего читателя. Сарафанное радио в виде твитера или фейсбука приведет тебе читателей, если будет что читать.

А если уж говорить на “раскрутке” – та же публикация удавшихся постов на Харбе приводит в сто раз более читателей, чем суета с дизайном сайта.

Вот такие мысли.


To say that Blogspot (Blogger) infuriates me is to say nothing. The only benefit of it is fast indexing by Google.

Artemy Lebedev said that he despises all this SEO bullshit because it is much more productive to focus on creating great posts rather than thinking how to attract more readers. His LJ uses a default template without even minor tweaks to make it looking “cool”. Despite of this his blog is in Russian Top 10.

Nobody reads blogs directly nowadays. Everybody uses Google Reader or other aggregators making the design of blogs absolutely pointless.

Anyway let’s leave odious Lebedev and come back to our techy blogs. Tom Preston-Werner, one of the Github founders, said that he wanted to write posts but not tweaking different templates or keeping the version of WordPress or Mephisto up to date.

A similar thought haunts me from the day one of using Blogspot. Only recently I have sorted a way of creating post by using ReST. Now I write posts in this markup and convert to HTML before publishing by a few handcrafted scripts. Unfortunately hosting for images is not automated in anyway.

Many times I wanted to migrate to a standalone platform, for example, WordPress. But my laziness and unwillingness to spend even a second on its maintenance always stopped me.

At the moment I have another attack of hate to Blogspot and as a consequence look for alternatives.

Surprisingly I never considered using static blog engines.

What if just simply migrate to GitHub Pages? It converts one of your repositories to a website which can be also processed by Jekyll, a static engine from Tom Preston-Werner.

I could kill a few birds with one stone – to use a proper markup language, Markdown or Textile, instead of bloody HTML and to manage publishing by git. Of course ReST is better but I can cope with it.

Also GitHub Pages can be integrated with your own domain if needed.

Comments and discussions. Disqus seems to be an easiest way to sort it without any hassle.

Google Analytics perfectly works with any engine where you can insert their JavaScript hook into pages.

What is still missing?

Sometimes people still visit blogs directly, for instance, to find previous posts or explore “social” details. In this case all these bells and whistles in a form of JavaScript gadgets are very useful and save a lot of time. Blogspot allows adding them in a few clicks.

But coming back to the question at the beginning – do I really need them? If the content is interesting the audience will inevitably find you. A word of mouth in a form of Twitter or Facebook will attract people. But if the content is crap all those bells and whistles (and SEO) will not help.

суббота, 10 декабря 2011 г.

Задача расположения восьми ферзей на Erlang'e

Знаю, что баян, но для меня было весьма показательно.

Например, вот вариант, который я написал где-то за полчаса:

-module(queens_classic).
-export([solve/0]).

solve() ->
    solve(lists:seq(1, 8), lists:seq(1, 15 + 15), 1, []).

print_board([]) -> io:format("~n", []);
print_board([H|T]) ->
    Line = [string:copies(". ", H - 1), "@ ", string:copies(". ", 8 - H)],
    io:format("~s~n", [Line]),
    print_board(T).

solve(_, _, Cols, Result) when Cols > 8 ->
    io:format("~p~n", [Result]),
    print_board(Result);

solve(Rows, Diags, Col, Result) ->
    lists:foreach(fun(Row) ->
                     D1 = Row + Col,
                     D2 = Row - Col + 8 + 15,
                     T = lists:member(Row, Rows) andalso
                         lists:member(D1, Diags) andalso
                         lists:member(D2, Diags),
                     if T ->
                         Rows1 = Rows -- [Row],
                         Diags1 = Diags -- [D1, D2],
                         solve(Rows1, Diags1, Col + 1, [Row | Result]);
                        true -> void
                     end
                  end, Rows).

Выглядит ужасно, и стиль однозначно понятно какой: C/Python на стероидах (циклы, if'ы).

А вот над этим вариантом я провозился несколько часов:

-module(queens).
-export([solve/0]).

solve() ->
    solve(1, []).

print_board([]) -> io:format("~n", []);
print_board([{_X, Y}|T]) ->
    Line = [string:copies(". ", Y - 1), "@ ", string:copies(". ", 8 - Y)],
    io:format("~s~n", [Line]),
    print_board(T).

solve(X, Taken) when X > 8 ->
    io:format("~p~n", [Taken]),
    print_board(Taken);

solve(X, Taken) ->
    L = [{X, Y} || Y <- lists:seq(1, 8), not under_attack({X, Y}, Taken)],
    row(L, Taken).

row([], _) -> [];
row([{X, Y}|L], Taken) ->
    solve(X + 1, [{X, Y} | Taken]),
    row(L, Taken).

under_attack(_, []) -> false;
under_attack({X, Y}, [{Xt, Yt}|L]) ->
    Y == Yt orelse abs(Y - Yt) == abs(X - Xt) orelse
    under_attack({X, Y}, L).

Вся работа со списками вручную без циклоподобных конструкций.

Печатает типа такого:

[4,7,5,2,6,1,3,8]
. . . @ . . . .
. . . . . . @ .
. . . . @ . . .
. @ . . . . . .
. . . . . @ . .
@ . . . . . . .
. . @ . . . . .
. . . . . . . @

[5,7,2,6,3,1,4,8]
. . . . @ . . .
. . . . . . @ .
. @ . . . . . .
. . . . . @ . .
. . @ . . . . .
@ . . . . . . .
. . . @ . . . .
. . . . . . . @

...

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

На всякий случай Makefile для обоих вариантов:

target = queens

all:
    erlc $(target).erl
    erl -noshell -s $(target) solve -s init stop

classic:
    erlc $(target)_classic.erl
    erl -noshell -s $(target)_classic solve -s init stop

clean:
    -rm *.beam *.dump

среда, 7 декабря 2011 г.

Visual Studio 11 Developer Preview

Поставил на рабочий ноут Visual Studio 11 Developer Preview.

Погонял разные самопальные бенчмарки типа решета Эратосфена, vector<int> vs vector<bool>, std::string vs char* и т.д., пытаясь выявить улучшения или ухудшения в оптимизации. Лично я ничего кардинального не выявил по сравнению с версией 10.

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

Например с ключом "/sdl" Студия 11 будет считать приведенные ниже предупреждения ошибками.

Warning Command line switch Description
C4146 /we4146 A unary minus operator was applied to an unsigned type, resulting in an unsigned result
C4308 /we4308 A negative integral constant converted to unsigned type, resulting in a possibly meaningless result
C4532 /we4532 Use of “continue”, “break” or “goto” keywords in a __finally/finally block has undefined behavior during abnormal termination
C4533 /we4533 Code initializing a variable will not be executed
C4700 /we4700 Use of an uninitialized local variable
C4789 /we4789 Buffer overrun when specific C run-time (CRT) functions are used
C4995 /we4995 Use of a function marked with pragma deprecated
C4996 /we4996 Use of a function marked as deprecated

Ссылки по теме:

среда, 30 ноября 2011 г.

MapReduce на Erlang'e

Я продолжаю погружение в Эрланг. Уже есть хитрый план переписать один из наших сервисов для мониторинга на Эрланге. Мы тут осваиваем облака Windows Azure и Amazon EC2 в качестве платформы для некоторых продуктов и внутренних задач типа QA, поэтому возможность использовать много ядер и машин без переписывания кода выглядить перспективно.

Итак, для начала простой, но реальный пример - есть проект ~2000 файлов. Надо составить список используемых переменных окружения. То есть найти вхождения строк "getenv(...)" и "GetVariable(...)" (это наш wrapper) и выдрать из них параметр.

Задача незамысловатая и давно решается программой на C++, которая даже обход каталогов не делает, а просто вызывает юниксовый "find", генерирующий список файлов по маске, и затем по списку лопатит файлы. На 2000 файлах работает пару секунд в один поток.

Теперь Эрланг. Тут хочется замутить что-нибудь более кучерявое, чем последовательный обход файлов. MapReduce как раз в тему - можно составить список файлов, затем анализ каждого файла делать параллельно (Map), аккумулируя найденные имена переменных, и в конце обработать все полученные входждение (Reduce), в нашем случае просто подсчитать количество вхождения каждой переменной.

Фактически мой код повторяет пример из "Programming Erlang" и использует модуль phofs (parallel higher-order functions) из этой же книги.

-module(find_variables).
-export([main/0, find_variables_in_file/2, process_found_variables/3]).

-define(PATH, "/Projects/interesting_project").
-define(MASK, "\\..*(cpp|c)").

main() ->
    io:format("Creating list of files...~n", []),
    % Стандартная функция обхода файловой системы. Последний параметр -
    % функтор, накапливающий имена в списке.
    Files = filelib:fold_files(?PATH, ?MASK, true,
                               fun(N, A) -> [N | A] end, []),
    io:format("Found ~b file(s)~n", [length(Files)]),
    F1 = fun find_variables_in_file/2,   % Map
    F2 = fun process_found_variables/3,  % Reduce
    % Вызываем MapReduce через функцию benchmark, считающую время
    % выполнения.
    benchmark(fun() ->
        L = phofs:mapreduce(F1, F2, [], Files),
        io:format("Found ~b variable(s)~n", [length(L)])
    end, "MapReduce").

benchmark(Worker, Title) ->
    {T, _} = timer:tc(fun() -> Worker() end),
    io:format("~s: ~f sec(s)~n", [Title, T/1000000]).

-define(REGEXP, "(getenv|GetVariable)\s*\\(\s*\"([^\"]+)\"\s*\\)").

% Map. Анализ одного файла.
find_variables_in_file(Pid, FileName) ->
    case file:open(FileName, [read]) of
        {ok, File} ->
            % Заранее компилируем регулярное выражение.
            {_, RE} = re:compile(?REGEXP),
            % Данный обратный вызов пошлет родительскому контролирующему
            % потому сообщение с именем найденной переменной.
            CallBack = fun(Var) -> Pid ! {Var, 1} end,
            find_variable_in_file(File, RE, CallBack),
            file:close(File);
        {error, Reason} ->
            io:format("Unable to process '~s', ~p~n", [FileName, Reason]),
            exit(1)
    end.

% Reduce. Анализ данных. Данная функция вызывается контролирующим
% процессом MapReduce для каждого найденного ключа вместе со списком
% значений, ассоциированных с ним. В нашем случае это будут пары
% {VarName, 1}. Мы просто подсчитаем для каждого VarName количество
% пришедших пар, то есть количество найденных вхождений этой переменной.
% Это и есть наш незамысловатый анализ.

process_found_variables(Key, Vals, A) ->
    [{Key, length(Vals)} | A].

% Построчный обход файла.
find_variable_in_file(File, RE, CallBack) ->
    case io:get_line(File, "") of
       eof -> void;
       Line ->
         scan_line_in_file(Line, RE, CallBack),
         find_variable_in_file(File, RE, CallBack)
    end.

% Поиск строки в строке по регулярному выражению (скомпилированному ранее),
% и в случае нахождение вызов CallBack с передачей ему имени найденной
% переменной.
scan_line_in_file(Line, RE, CallBack) ->
    case re:run(Line, RE) of
        {match, Captured} ->
            [_, _, {NameP, NameL}] = Captured,
            Name = string:substr(Line, NameP + 1, NameL),
            CallBack(Name);
        nomatch -> void
    end.

Для сборки программы нужен модуль phofs. Он является универсальным, независимым от конкретных функций Map и Reduce.

И Makefile на всякий случай:

target = find_variables

all:
    erlc $(target).erl
    erlc phofs.erl
    erl -noshell -s $(target) main -s init stop

clean:
    -rm *.beam *.dump

Пузомерка. Как я уже сказал, программа на C++ вместе со временем вызова "find" на моей машине работает 1-2 секунды. Версия на Erlang'e работает ~20 секунд. Плохо? Смотря как посмотреть. Если анализ каждого файла будет более длительным (то есть программа будет основное время тратить на анализ файла, а не обход каталогов), то тут уже не совсем очевидно, какое из решений будет более практично при увеличении числа файлов и сложности анализа.

Я новичок в Эрланге, поэтому будут признателен за критику кода.

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

суббота, 26 ноября 2011 г.

Улучшенный TCP/IP proxy на Erlang'e

Писал я про мое освоение Эрганга через написание программы для перехвата и удобного логирования TCP/IP соединений.

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

Что программа умеет особенно удобного (как мне кажется):

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

Про Эрланг. Меня начинает реально вставлять. Я почувствовал (для многих это и не новость), что тут можно написать что-то реальное, особенно связаное с сетью и многозадачностью.

Из насущных проблем:

  • Пока нет чувства разумного дробления на модули и даже функции. При общей тотальной иммутабельности сложно что-то напортачить, но когда количество функций разрастается, хочется их как-то группировать.
  • Нет чувства правильного форматирования кода. Вроде как 80-ти символьные строки и пробелы вместо табуляций меня пока никогда не подводили, но при функциональном стиле кода часто получаются длинные "лесенки".

Пузомерка. Я сделал тест на прокач шестидесятимегового файла через питоновскую и эрланговскую версию. Результаты интересные.

Кач напрямую:

curl http://www.erlang.org/download/otp_src_R14B04.tar.gz >direct

Через Питон:

Window 1: python pyspy.py -l 50000 -a www.erlang.org -p 80 -L log

Window 2: curl http://localhost:50000/download/otp_src_R14B04.tar.gz >via-proxy-python

Через Эрланг:

Window 1: escript tcp_proxy.erl 50000 www.erlang.org 80

Window 2: curl http://localhost:50000/download/otp_src_R14B04.tar.gz >via-proxy-erlang

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

На Эрланге картина иная. Файл прокачался практически за то же время, что и напрямую! Но вот полного сброса лога я так и дождался. Через шесть минут он успел сбросить где-то 10% лога.

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

На Эрланге же качалка работает, как мне показалось, очень быстро. Данные перекачиваются и параллельно загоняются в очередь на логирование. А вот производительность логирования оставляет желать лучшего. Ради эксперимента я закомментировал вызов функции создания шестнадцатеричного дампа, и время сброса лога также упало до минуты. Поэтому, как мне кажется, корень зла в моей кривой работе со строками и списками при создании дампа (возможно что-то где-то постоянно копируются, а в мире рекурсии и изменения данных только через копирование ошибки подобного рода дорого отражаются на производильности). А вот работа с сокетами и посылкой/приемом сообщений между потоками в Эрланге очень эффективная.

Я вообще заметил, что в Эрланге ты подсознательно начинашь писать многопотоковые программы. Например, тут в принципе нет глобальных объектов. И допустим, у тебя есть флаг, глобальная установка, которую хочется иметь везде. Так как глобально ее объявить нельзя, приходится таскать как параметр функций там и сям. А как вариант "навязанного конструктивного мышления", думаешь - а давай-ка я запущу этот кусок как поток и буду вызывать его функционал через посылку сообщений. В этом случае я могу передать мне нужный параметр один раз в начале при создании потока, тем самым сделав его типа глобальным для этого потока.

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

Для интересующихся - исходник доступен.

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

вторник, 22 ноября 2011 г.

TCP/IP proxy на Erlang'e

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

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

И так: программа многопоточна, и журналирование также происходит в отдельном потоке для обеспечения целостности многострочных дампов.

Про Эрланг. После многократных и пока полностью неуспешных заходов на Хаскелл и после все еще неудачных попыток на Lisp или Scheme написать что-то более менее реальное и жизненное, Эрланг был реальным прорывом для меня.

Удивительно, невозможность изменять переменные (представьте, что программируя на С++ надо все переменные делать const) является фантастическим способом борьбы с опечатками при cut-and-paste. Также когда делаешь циклы через хвостовую рекурсию, сразу осознаешь, как эффективно работать со списками, чтобы их не копировать, а всегда "таскать" за хвост или голову.

Ну а концепция легких потоков и обмена сообщениями между ними (как в Go), приправленная глобальной иммутабельностью, позволяет легко писать надежные многотопочные программы.

Например, истересен способ реализации многопотокового TCP/IP сервера. Обычно при его программировании есть распростраенный прием: один главный поток, принимающий соединения, и когда соединение принято, создается новый поток-исполнитель, который обрабатывает соединение и после этого умирает.

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

Для меня это было немного необычно.

-module(tcp_proxy).

-define(WIDTH, 16).

main([ListenPort, RemoteHost, RemotePort]) ->
    ListenPortN = list_to_integer(ListenPort),
    start(ListenPortN, RemoteHost, list_to_integer(RemotePort));

main(_) -> usage().

usage() ->
    io:format("~ntcp_proxy.erl local_port remote_port remote_host~n~n", []),
    io:format("Example:~n~n", []),
    io:format("tcp_proxy.erl 50000 google.com 80~n~n", []).

start(ListenPort, CalleeHost, CalleePort) ->
    io:format("Start listening on port ~p and forwarding data to ~s:~p~n",
              [ListenPort, CalleeHost, CalleePort]),
    {ok, ListenSocket} = gen_tcp:listen(ListenPort, [binary, {packet, 0},
                                                    {reuseaddr, true},
                                                    {active, true}]),
    io:format("Listener socket is started ~s~n", [socket_info(ListenSocket)]),
    spawn(fun() -> acceptor(ListenSocket, CalleeHost, CalleePort, 0) end),
    register(logger, spawn(fun() -> logger() end)),
    wait().

% Infinine loop to make sure that the main thread doesn't exit.
wait() -> receive _ -> true end, wait().

format_socket_info(Info) ->
    {ok, {{A, B, C, D}, Port}} = Info,
    lists:flatten(io_lib:format("~p.~p.~p.~p:~p", [A, B, C, D, Port])).

peer_info(Socket) -> format_socket_info(inet:peername(Socket)).

socket_info(Socket) -> format_socket_info(inet:sockname(Socket)).

acceptor(ListenSocket, RemoteHost, RemotePort, ConnN) ->
    case gen_tcp:accept(ListenSocket) of
      {ok, LocalSocket} ->
          spawn(fun() -> acceptor(ListenSocket, RemoteHost, RemotePort, ConnN + 1) end),
          LocalInfo = peer_info(LocalSocket),
          logger ! {message, "~4.16.0B: Incoming connection from ~s~n", [ConnN, LocalInfo]},
          case gen_tcp:connect(RemoteHost, RemotePort, [binary, {packet, 0}]) of
            {ok, RemoteSocket} ->
              RemoteInfo = peer_info(RemoteSocket),
              logger ! {message, "~4.16.0B: Connected to ~s~n", [ConnN, RemoteInfo]},
              exchange_data(LocalSocket, RemoteSocket, LocalInfo, RemoteInfo, ConnN, 0),
              logger ! {message, "~4.16.0B: Finished~n", [ConnN]};
            {error, Reason} ->
              logger ! {message, "~4.16.0B: Unable to connect to ~s:~s (error: ~p)~n",
                       [ConnN, RemoteHost, RemotePort, Reason]}
          end;
      {error, Reason} ->
          logger ! {message, "Socket accept error '~w'~n", [Reason]}
    end.

exchange_data(LocalSocket, RemoteSocket, LocalInfo, RemoteInfo, ConnN, PacketN) ->
    receive
        {tcp, RemoteSocket, Bin} ->
            logger ! {received, ConnN, Bin, RemoteInfo, PacketN},
            gen_tcp:send(LocalSocket, Bin),
            logger ! {sent, ConnN, LocalInfo, PacketN},
            exchange_data(LocalSocket, RemoteSocket, LocalInfo, RemoteInfo, ConnN, PacketN + 1);
        {tcp, LocalSocket, Bin} ->
            logger ! {received, ConnN, Bin, LocalInfo, PacketN},
            gen_tcp:send(RemoteSocket, Bin),
            logger ! {sent, ConnN, RemoteInfo, PacketN},
            exchange_data(LocalSocket, RemoteSocket, LocalInfo, RemoteInfo, ConnN, PacketN + 1);
        {tcp_closed, RemoteSocket} ->
            logger ! {message, "~4.16.0B: Disconnected from ~s~n", [ConnN, RemoteInfo]};
        {tcp_closed, LocalSocket} ->
            logger ! {message, "~4.16.0B: Disconnected from ~s~n", [ConnN, LocalInfo]}
    end.

logger() ->
    receive
        {received, Pid, Msg, From, PacketN} ->
            io:format("~4.16.0B: Received (#~p) ~p byte(s) from ~s~n",
                      [Pid, PacketN, byte_size(Msg), From]),
            dump_bin(Pid, Msg),
            logger();
        {sent, Pid, ToSocket, PacketN} ->
            io:format("~4.16.0B: Sent (#~p) to ~s~n", [Pid, PacketN, ToSocket]),
            logger();
        {message, Format, Args} ->
            io:format(Format, Args),
            logger()
    end.

dump_list(Prefix, L, Offset) ->
    {H, T} = lists:split(lists:min([?WIDTH, length(L)]), L),
    io:format("~4.16.0B: ", [Prefix]),
    io:format("~4.16.0B: ", [Offset]),
    io:format("~-*s| ", [?WIDTH * 3, dump_numbers(H)]),
    io:format("~-*s", [?WIDTH, dump_chars(H)]),
    io:format("~n", []),
    if length(T) > 0 -> dump_list(Prefix, T, Offset + 16); true -> [] end.

dump_numbers(L) when (is_list(L)) ->
    lists:flatten([io_lib:format("~2.16.0B ", [X]) || X <- L]).

dump_chars(L) ->
    lists:map(fun(X) ->
                if X >= 32 andalso X < 128 -> X;
                   true -> $.
                end
              end, L).

dump_bin(Prefix, Bin) ->
    dump_list(Prefix, binary_to_list(Bin), 0).

В работе может выводить примерно следующее:

alexander:erlang/>./tcp_proxy.sh 50000 pop.yandex.ru 110
Start listening on port 50000 and forwarding data to pop.yandex.ru:110
Listener socket is started 0.0.0.0:50000
0000: Incoming connection from 127.0.0.1:51402
0000: Connected to 213.180.204.37:110
0000: Received (#0) 38 byte(s) from 213.180.204.37:110
0000: 0000: 2B 4F 4B 20 50 4F 50 20 59 61 21 20 76 31 2E 30 | +OK POP Ya! v1.0
0000: 0010: 2E 30 6E 61 40 32 35 20 67 55 62 44 54 51 64 5A | .0na@25 gUbDTQdZ
0000: 0020: 6D 6D 49 31 0D 0A                               | mmI1..
0000: Sent (#0) to 127.0.0.1:51402
0000: Received (#1) 11 byte(s) from 127.0.0.1:51402
0000: 0000: 55 53 45 52 20 74 65 73 74 0D 0A                | USER test..
0000: Sent (#1) to 213.180.204.37:110
0000: Received (#2) 23 byte(s) from 213.180.204.37:110
0000: 0000: 2B 4F 4B 20 70 61 73 73 77 6F 72 64 2C 20 70 6C | +OK password, pl
0000: 0010: 65 61 73 65 2E 0D 0A                            | ease...
0000: Sent (#2) to 127.0.0.1:51402
0000: Received (#3) 11 byte(s) from 127.0.0.1:51402
0000: 0000: 50 41 53 53 20 70 61 73 73 0D 0A                | PASS pass..
0000: Sent (#3) to 213.180.204.37:110
0000: Received (#4) 72 byte(s) from 213.180.204.37:110
0000: 0000: 2D 45 52 52 20 5B 41 55 54 48 5D 20 6C 6F 67 69 | -ERR [AUTH] logi
0000: 0010: 6E 20 66 61 69 6C 75 72 65 20 6F 72 20 50 4F 50 | n failure or POP
0000: 0020: 33 20 64 69 73 61 62 6C 65 64 2C 20 74 72 79 20 | 3 disabled, try
0000: 0030: 6C 61 74 65 72 2E 20 73 63 3D 67 55 62 44 54 51 | later. sc=gUbDTQ
0000: 0040: 64 5A 6D 6D 49 31 0D 0A                         | dZmmI1..
0000: Sent (#4) to 127.0.0.1:51402
0000: Disconnected from 213.180.204.37:110
0000: Finished
0001: Incoming connection from 127.0.0.1:51405
0001: Connected to 213.180.204.37:110
0001: Received (#0) 38 byte(s) from 213.180.204.37:110
0001: 0000: 2B 4F 4B 20 50 4F 50 20 59 61 21 20 76 31 2E 30 | +OK POP Ya! v1.0
0001: 0010: 2E 30 6E 61 40 33 30 20 70 55 62 41 72 52 33 74 | .0na@30 pUbArR3t
0001: 0020: 6A 65 41 31 0D 0A                               | jeA1..
0001: Sent (#0) to 127.0.0.1:51405
0001: Received (#1) 6 byte(s) from 127.0.0.1:51405
0001: 0000: 51 55 49 54 0D 0A                               | QUIT..
0001: Sent (#1) to 213.180.204.37:110
0001: Received (#2) 20 byte(s) from 213.180.204.37:110
0001: 0000: 2B 4F 4B 20 73 68 75 74 74 69 6E 67 20 64 6F 77 | +OK shutting dow
0001: 0010: 6E 2E 0D 0A                                     | n...
0001: Sent (#2) to 127.0.0.1:51405
0001: Disconnected from 213.180.204.37:110
0001: Finished

Вывод: Эрланг - прекрасный вариант для начала функциональной карьеры.

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

Git для работы на нескольких платформах

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

После десятков версий изощренных скриптов я решил собрать волю в кулак и начать использовать Git.

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

Для простоты я решил использовать SSH как протокол. Благо все UNIX машины имеют ssh-клиент.

Плюсы - везде Git, все локальные изменения имеют версии и можно вести локальные ветки. Ну и центральная копия - тоже под Git. Минусы - потратить время и все это настроить.

Git/ssh как сервер на Windows - это целая тема, так как нужно поставить SSH сервер и правильно прикрутить к нему Git.

Благодаря двум (1, 2) ссылкам удалось настроить CopSSH в паре с msysgit.

Далее Git на клиентских машинах. С Linux и Windows все совсем просто (на Windows используется тот же msysgit).

На Solaris пришлось собрать GNU Make до 3.82 (на 3.75 Git не собирается).

На HPUX and AIX пришлось собрать coreutils (для нормального install), less (представляете, на HPUX нету less по умолчанию), python (опять для HPUX), zlib и свежие tcl/tk.

Один день на борьбу c CopSSH на Windows и день на сборки под UNIXами.

Зато теперь радость и благодать.

P.S. С CopSSH интересная история. Вчера (21 ноября) на их сайте можно было все скачать. Сегодня (22 ноября) читаю на том же сайте:

Termination of free solutions from ITeF!x
Submitted by tk on Tue, 22/11/2011 - 08:18 itefix
Development,distribution and support of free solutions from Itefix are now terminated due to lack of time and changes in priorities.

С их зеркала на sourceforge.net также пропали все файлы. Хорошо, что я не удалил дистрибутив CopSSH, скачанный вчера.

суббота, 19 ноября 2011 г.

Проба пера на новых языках

Как-то так сложилось, что когда я рассматриваю новый язык программирования, то помимо тривиальных программок типа "Hello world", хочется написать что-то более менее реальное или даже нужное.

У меня такой задачей стал многопотоковый перехватчик-прокси для TCP/IP. Такая программа ставится как промежуточное звено между клиентом и сервером, и можно удобно смотреть, что летает в канале туда-сюда.

Данная задача затрагивает многие аспекты языка - потоки, синхронизацию, сокеты, ввод/вывод, работу со строками.

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

Еще я писал это программу на C, C++, C++/boost, PHP, VB, Go, Ruby. На Erlang'e не осилил, пока.

А вы чем тестируете новые языки?

среда, 16 ноября 2011 г.

Тонкости использования getenv() и putenv()

Нарвался тут на интересные грабли с функциями getenv() и putenv().

С putenv() у меня уже был интересный опыт.

Часто люди пишут так:

if (getenv("A_FLAG")) {
  ...
}

Это работает неплохо для переменных-флагов, которые либо есть, либо нет. Значение не важно.

Что получилось у меня:

int main(...) {
  putenv("GLOBAL_FLAG=1");  // Глобальное значение для всей программы.
  ...
  system("xyz");            // Это программа должна видеть GLOBAL_FLAG=1.
  ...
  do_stuff();
  ...
}

void do_stuff() {
  ...
  if (something) {
    putenv("GLOBAL_FLAG="); // Убрать переменную.
    system("abc");          // А вот для этой программы флаг должен быть убран.
    ...
  }
  ...
  if (getenv("GLOBAL_FLAG") {
     // И вот тут начиналась ерунда на разных платформах.
  }
}

А корень зла тут в том, что после putenv() результат getenv() может стать либо NULL, либо "", в зависимости от платформы.

Например:

if (getenv("GLOBAL_FLAG") {

работает только на Windows и правильнее писать:

const char* p = getenv("GLOBAL_FLAG");
if (p != NULL && *p != '\0') {
  ...
}

И лучше сделать wrapper для getenv():

std::string GetEnv(const char* name) {
  const char* v = getenv(name);
  return v ? v : "";
}

И для проверки писать:

if (!GetEnv("var").empty()) {
  ..
}

Для теста я написал программу, которая выставляет переменную и проверяет ее значение через getenv() и через вызов дочерней программы.

#include <string>
#include <vector>
#include <iostream>
#include <cstdlib>

#ifdef WINDOWS
#define putenv _putenv
#endif

void PrintVariableViaShell(const std::string& name) {
  std::cout << "Value from shell:" << std::endl;
  const std::string cmd =
#ifdef WINDOWS
    std::string("cmd /c echo [%") + name + "%]";
#else
    std::string("/bin/sh -c \"echo [$") + name + "]\"";
#endif
  std::cout << cmd << std::endl;
  std::system(cmd.c_str());
}

void PrintVariableViaGetEnv(const std::string& name) {
  std::cout << "Value from getenv():" << std::endl;
  const char* v = std::getenv(name.c_str());
  std::cout << "[" << (v ? v : "<NULL>") << "]" << std::endl;
}

void SetVariableDeleteAndPrint(const char* name_value, const bool equ) {
  const std::string& name_value_s(name_value);
  const std::string name = name_value_s.substr(0, name_value_s.find('='));

  putenv(const_cast<char*>(name_value));
  std::vector<char> delete_without_equ(name.begin(), name.end());
  delete_without_equ.push_back('\0');
  putenv(&delete_without_equ[0]);

  std::cout << "Value after deleting WITHOUT '=':" << std::endl;
  PrintVariableViaShell(name);
  PrintVariableViaGetEnv(name);

  std::cout << std::endl;

  putenv(const_cast<char*>(name_value));
  std::vector<char> delete_with_equ(name.begin(), name.end());
  delete_with_equ.push_back('=');
  delete_with_equ.push_back('\0');
  putenv(&delete_with_equ[0]);

  std::cout << "Value after deleting WITH '=': " << std::endl;
  PrintVariableViaShell(name);
  PrintVariableViaGetEnv(name);
}

int main(int argc, char* argv[]) {
#ifdef WINDOWS
  std::cout << "WINDOWS" << std::endl;
#else
  system("uname");
#endif
  SetVariableDeleteAndPrint("ABC=123", true);
  return 0;
}

И вот результы с разных платформ.

Linux

Linux
Value after deleting WITHOUT '=':
Value from shell:
/bin/sh -c "echo [$ABC]"
[]
Value from getenv():
[<NULL>]

Value after deleting WITH '=':
Value from shell:
/bin/sh -c "echo [$ABC]"
[]
Value from getenv():
[]

AIX

AIX
Value after deleting WITHOUT '=':
Value from shell:
/bin/sh -c "echo [$ABC]"
[123]
Value from getenv():
[123]

Value after deleting WITH '=':
Value from shell:
/bin/sh -c "echo [$ABC]"
[]
Value from getenv():
[]

SunOS

SunOS
Value after deleting WITHOUT '=':
Value from shell:
/bin/sh -c "echo [$ABC]"
[123]
Value from getenv():
[123]

Value after deleting WITH '=':
Value from shell:
/bin/sh -c "echo [$ABC]"
[]
Value from getenv():
[]

HP-UX

HP-UX
Value after deleting WITHOUT '=':
Value from shell:
/bin/sh -c "echo [$ABC]"
[123]
Value from getenv():
[123]

Value after deleting WITH '=':
Value from shell:
/bin/sh -c "echo [$ABC]"
[]
Value from getenv():
[]

WINDOWS

WINDOWS
Value after deleting WITHOUT '=':
Value from shell:
cmd /c echo [%ABC%]
[123]
Value from getenv():
[123]

Value after deleting WITH '=':
Value from shell:
cmd /c echo [%ABC%]
[%ABC%]
Value from getenv():
[<NULL>]

Только на Windows getenv() возвращает NULL после удаления. На остальных это будет пустая строка.

Забавно, на Linux можно удалять переменные через putenv("name") (без знака "="), а тогда getenv() будет возвращать NULL.

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

воскресенье, 30 октября 2011 г.

Сколько дней в году работает программист

Как вы оцениваете потенциальную продуктивность програмистов?

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

Дано: работа программиста над устоявшимся продуктом, который имеет major релизы раз в год; в течение года выпускаются minor релизы и критические исправления; плюс в поддержке находятся релизы последних 5-6 лет.

Вот мой расчет:

365 дней в году
365 / 7 = 52 недели в году
52 * 5 = 260 рабочих дней в году
260 - 25 = 235 рабочих дней в году за вычетом отпуска
235 - 10 = 225 рабочих дней в году за вычетом еще и государственных выходных
225 - 15 = 210 еще минус две недели в среднем "по болезни"

Итак: 210 человеко-восьмичасовых-дней.

Общение с несколькими менеджерами программистов показало, что цифра "100 дней" является весьма распространенной как оценка сверху на то, сколько новой разработки планировать в год на человека.

Получается, что после поддержки, затрат на внутренюю инфраструктуру, совещаний и обучений реально на разработку остается только половина времени.

вторник, 25 октября 2011 г.

Закомментированные куски кода

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

Ребята купили в офис "Test Driven Development for Embedded C" от James W. Grenning.

Хоть там и много про hardware, но примеры как можно и нужно изолировать зависимости для упрощения тестирования на языке С очень полезные. Кроме того описаны пара xUnit библиотек для этого языка (хотя cmockery нет).

Итак, цитата (как есть, без сокращении и перевода).

Commented-out Code

Sources files littered with commented-out code are an ugly mess. New or returning programmers are faced with questions about what the code is supposed to do. "Should the code be uncommented?" "It is no longer needed?" "When will it be needed and under what circumstances is it needed?" "What's that for?"

The solution to this code smell is simple; delete the commented-out code. It can always be recovered from your source repository.

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

среда, 19 октября 2011 г.

Запрещенные слова для комментариев и имен

Мы договорились в команде, что слова "new", "now" и "old" являются запрещенными для употребления в комментариях, именах переменных окружения, описаниях коммитов, когда речь идет об изменении поведения чего-либо.

Например, переменная окружения, включающая "старый" алгоритм генерации ключа: вместо "OLD_KEY_ALGORITHM" должно быть "USE_ABCDEF_ALGORITHM".

Вместо ссылок на прошлое или будущее надо описывать конкретное поведение, так как характеристики "new", "now" and "old" становятся неактуальными уже через неделю. А еще хуже, что они приводят к появлению таких имен как "OLD_2_KEY_ALGORITHM" или "NEW_2_KEY_ALGORITHM".