Никто не в состоянии предсказывать будущее — но я сделаю несколько обоснованных предположений.
Предположим, что Intel правы, что их проект Keifer выстрелит. Если это случится, то 32-х ядерные процессоры появятся на рынке не позже 2009-2010.
Ничего удивительного здесь нет. Sun уже продает восьмиядерные Niagara с 4-мя «hyperthreads» на каждом ядре, что эквивалентно 32-ум ядрам.
Это разработка, которая осчастливит программистов на Erlang. Они 20 лет ждали этого события, и теперь настало время расплаты.
Хорошие новости для Erlang-программистов:
На N-ядерном процессоре ваша программа будет работать в N раз быстрее.
Верно ли это?
Почти. Пока что еще рановато об этом говорить, но мы оптимистичны (дико оптимистичны — я не видел такого оптимизма за последние 20 лет!).
В некоторых случаях вам придётся немного подправить ваш код — когда я запускал свою программу генерации документации для Erlang на Sun Niagara (эквивалентно 32-х ядерному процессору), то изменения коснулись лишь одной строчки кода (поменял map на pmap — извиняюсь за тех. подробности, pmap это просто «параллельный map»).
Программа (которая генерировала 63 документа из вики-разметки) стала работать в 7 раз быстрее. Не в 32 раза, конечно — но, я уверяю вас, намного быстрее (впоследствии выяснилось, что узким местом был дисковый I/O, а так как он не был распараллелен, то мы уперлись в семикратный прирост :).
В Ericsson, где я работал, и где был разработан Erlang, мы портируем некоторые приложения на 4-х ядерные процессоры — и знаете, что? После легкого допиливания они работают практически в 4 раза быстрее. У нас уже терпения не хватает дождаться 80-ядерных камешков, с которыми Intel играется в лабораториях…
Так почему же наши программы с такой простотой работают быстрее? Всё дело в изменяемом состоянии и параллелизме.
Переменные и параллелизм
C давних времён (диких 20 лет назад) существуют две модели параллелизма:
- С разделяемой памятью (shared state)
На обмене сообщениями (message passing)
С тех пор весь мир пошел по одному пути (разделяемая память), ну а мы выбрали другой.
Лишь очень малое количество языков пошло по дороге параллелизма на обмене сообщениями — к примеру, Oz и Occam.
Говоря о параллелизме на обмене сообщениями мы постулируем: «разделяемой памяти нет». Все вычисления производятся в изолированных процессах и единственный способ обменяться информацией — асинхронный обмен сообщениями.
Так чем же это хорошо?
Параллелизм с разделяемой памятью построен на принципе «изменяемого состояния» (буквально — память, которую можно менять). Все языки вроде C, Java, C++ и т.п. несут в себе понятие «памяти», которую мы можем менять.
Это хорошо только в том случае, если у ровно один процесс, производящий изменения.
Но если у вас несколько процессов, делящих одну память и изменяющих её, то начинается форменный кошмар.
Чтобы предотвратить одновременную запись в разделяемую память, нам потребуется механизм блокировок. Назовите его мьютексом или синхронизированным методом, да как хотите, так и назовите — это останется блокировкой.
Если программа рушится в критическом регионе (т.е. в то время, когда включена блокировка) — то это катастрофа. Все остальные программы не знают, что делать.
Как программисты решают эти проблемы? Они молятся. На одноядерном процессоре их программы могут «просто работать», но на многоядерном — всё падает.
Существуют разные способы не допустить этого (транзакционная память, вероятно лучший способ), но всё равно будут ляпы, а в худшем случае — кошмар.
В Erlang нет изменяемых структур данных
(вообще говоря, есть, но это неважно)
Нет изменяемых данных → нет блокировок.
Нет изменяемых данных → легко распараллелить.
Ну и как же мы осуществляем параллелизацию? Легко — программист разбивает решение проблемы на несколько параллельных процессов.
Этот стиль программирования называется…
(барабанная дробь)
Concurrency Oriented Programming
Erlang — не про объекты. У него своя метафора.
Объекты не в моде. Встречайте параллелизм.
Мир — параллелен. Куча событий вокруг нас происходит одновременно. Я бы не смог вести свой автомобиль по шоссе, не понимая принцип параллельности на интуитивном уровне — чистой параллельности на обмене сообщениями, чем мы и живём.
Представьте группу людей. У них нет общего состояния.
У меня своя личная память (в моей голове), а у вас — своя. И она не общая. Мы общаемся передавая сообщения (световые и звуковые волны). Мы обновляем наше внутреннее состояние основываясь на восприятии этих сигналов.
Это то, чем является параллельное программирование в своей сути.
Мы словно скрываем изменяемое состояние внутри объекта — то самое свойство, которое делает паралеллизацию до невозможности сложной проблемой.
И это работает?
Да. Erlang используется по всему миру в хай-тек проектах, где требуется надежность. Флагманский проект на Erlang, разработанный Ericsson, шведской телефонной компанией — коммутатор AXD301. В нём более двух миллионов строк Erlang'а.
AXD301 — это девять девяток надежности (да, ваши глаза вас не обманывают — 99.999999999%). Пять девяток считаются хорошим показателем (downtime — 5.2 минуты в год). Семь девяток практически недостижимы… но нам удалось достичь 9-и.
Благодаря чему? Отсутствие разделяемой памяти плюс продвинутая модель восстановления из крэшей. Вы можете прочитать обо всём этом подробнее в моей диссертации на Ph.D.
Пользователи Erlang'а?
- те, кто «в теме»
стартапы
Ericsson
wings, программа для 3D-моделирования
ejabberd, IM-сервер для jabber/XMPP
tsung, многопротокольный инструмент для распределенного тестирования нагрузки
yaws, высокопроизводительный веб-сервер
тысячи хакеров, мечтающих: «вот бы заюзать это на моей работе в офисе»
А Erlang сложный?
Нет. Но он необычный.
У Erlang нет «похожего на C синтаксиса, чтобы было полегче учить». Он не «объектно-ориентированный», у него нет «переменных» и он — «функциональный язык программирования».
Это всё очень недружелюбная фигня — и, после поверхностного знакомства, отпугивающая новых пользователей. Но самое смешное, что на самом деле — это очень маленький и простой язык.
Вам наверняка уже стало интересно, как выглядит код на Erlang. Язык предполагает повсеместное использование синтаксиса pattern matching (сопоставления с образцом) — вот маленький пример кода на Erlang (взято из новой книги о нём):
-module(geometry).
-export([area/1]).
area({rectangle, Width, Ht}) -> Width * Ht;
area({square, X}) -> X * X;
area({circle, R}) -> 3.14159 * R * R.
Скомпилируем его и запустим в оболочке Erlang:
1> c(geometry).
{ok,geometry}
2> geometry:area({rectangle, 10, 5}).
50
3> geometry:area({circle, 1.4}).
6.15752
В общем-то, несложно… Вот Java-код, делающий то же самое:
abstract class Shape {
abstract double area();
}
class Circle extends Shape {
final double radius;
Circle(double radius) { this.radius = radius; }
double area() { return Math.PI * radius*radius; }
}
class Rectangle extends Shape {
final double ht;
final double width;
Rectangle(double width, double height) {
this.ht = height;
this.width = width;
}
double area() { return width * ht; }
}
class Square extends Shape {
final double side;
Square(double side) {
this.side = side;
}
double area() { return side * side; }
}
От переводчика: код на Erlang, создающий два процесса ping и pong, пингующие друг друга несколько раз:
-module(pingpong).
-export([start/0, ping/2, pong/0]).
ping(0, Pong_PID) ->
Pong_PID! finished,
io:format(«ping finished~n», []);
ping(N, Pong_PID) ->
Pong_PID! {ping, self()},
receive
pong ->
io:format(«Ping received pong~n», [])
end,
ping(N — 1, Pong_PID).
pong() ->
receive
finished ->
io:format(«Pong finished~n», []);
{ping, Ping_PID} ->
io:format(«Pong received ping~n», []),
Ping_PID! pong,
pong()
end.
start() ->
Pong_PID = spawn(pingpong, pong, []),
spawn(pingpong, ping, [3, Pong_PID]).
Вывод:
1> c(pingpong).
{ok,pingpong}
2> pingpong:start().
<0.36.0>
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
ping finished
Pong finished
Где мне достать Erlang?
Скачайте его с erlang.org
Прим. переводчика: куча ссылок из конца оригинальной статьи поскипана
- те, кто «в теме»