Китайское искусство программирования

yuryserdyuk (10 пост(а)) 29.07.2009 15:23

Те, кто интересовался Intel Threading Challenge 2007-2008 и Intel Threading Challenge 2009 , наверно, заметили активное участие в них и, самое главное, отличные результаты программистов из Китая. Регулярно борются они за первые места и на чемпионатах мира по программированию (ACM ICPC). Причем, теперь уже по моим личным наблюдениям, с ними совершенно невозможно соперничать в классических задачах программирования, таких как, например, сортировки, поиск и др. Однако, кроме великолепного знания методов решения стандартных задач, не откажешь им и в креативности. Вот показательный пример на эту тему …

Наверняка, всем хорошо известна задача о расстановке ферзей на шахматной доске таким образом, чтобы они взаимно не били друг друга. Суть задачи, а точнее, ее обобщения состоит в том, чтобы подсчитать количество всех возможных таких расстановок на доске размером N x N (NQueens Problem). Удивительно то, что хотя самой задаче уже около 150 лет, но существенный прорыв в ее решении произошел совсем недавно, в 2002 г. (а еще один прорыв состоялся в июле 2009г., но об этом позже).

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

И автором этого прорыва, как вы уже, наверно, догадались, стал человек из Китая по имени Qiu Zongyan из Пекинского университета (его статья помещена в журнале ACM SIGPLAN Notices).

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

Суть гениального по своей простоте приема, предложенного Qiu Zongyan, проиллюстрируем на примере.

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

  0 0 Ф 0 0 0 0 0
  0 0 0 0 0 Ф 0 0

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

1) диагоналям, по которым выставленные ферзи бьют справа-налево,

2) вертикалям, по которым ферзи бьют сверху-вниз,

3) диагоналям, по которым ферзи бьют слева-направо.

Представим эти позиции, находящиеся под боем (отметив их единичками) в виде соответствующих трех битовых векторов:

left: 1 0 0 0 1 0 0 0
down: 0 0 1 0 0 1 0 0
right: 0 0 0 0 1 0 1 0

Поэлементно складываем все эти вектора логически по ИЛИ и получаем занятые/свободные позиции на третьей горизонтали:

  1 0 1 0 1 1 1 0

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

А теперь – внимание ! – ключевой момент алгоритма. Как нам получить новую конфигурацию доски, а именно, занятые позиции теперь уже на четвертой горизонтали? А делаем всего лишь вот что − ставим единицу во вторую позицию (т.е., позицию, куда мы поставили нового ферзя) каждого из векторов left, down и right, и

a) сдвигаем вектор left влево на 1 бит (вычисляем позиции под боем на диагоналях, идущих от уже поставленных ферзей справа-налево),

б) ничего не делаем с down,

в) сдвигаем вектор right вправо на 1 бит (аналогично пункту а, но для диагоналей слева-направо).

Суммируем чего мы добились − мы представили конфигурации ферзей на доске в виде битовых векторов, а их модификацию выразили исключительно через битовые операции.

Остальная часть алгоритма − это обычный бектрекинг с целью получения всех возможных расстановок. Полное решение этой задачи на языке C# представлено ниже.

using System;
public class NQueens   {
 public static int Backtrack(int y, int left, int down,
 int right, int N, int MASK)
 {
     int  bitmap, bit;
     int  count = 0;
     if (y == N) {
         return ( 1 );
     } else {
         bitmap = MASK & ~(left | down | right);
         while (bitmap != 0) {
             bit = -bitmap & bitmap;
             bitmap ^= bit;
             count += Backtrack(y+1,(left | bit)<<1, down | bit,
             (right | bit)>>1, N, MASK);
         }
         return ( count );
     }
 }
 public static void Main(String[] args )
 {
     int N    = Convert.ToInt32 ( args [ 0 ] );
     int MASK = (1 << N) - 1;
     int result = Backtrack(0, 0, 0, 0, N, MASK);
     Console.WriteLine ("N=" + N + " -> " + result);
 }
}

Схема параллелизации этого алгоритма не так интересна − она очевидна, и, похоже, единственна. О ней и о сопутствующих результатах, я надеюсь, мы поговорим в планирующемся продолжении этого поста. А пока лишь упомяну, что детальное описание приведенного выше алгоритма вместе со схемой параллелизации на языке MC# можно будет вскоре найти в учебном курсе “Параллельное программирование для многоядерных процессоров”, который будет размещен на сайте Интернет-университета информационных технологий и на сайте Microsoft MSDN Academic Alliance . Варианты решения этой же задачи с использованием OpenMP и Intel TBB можно найти в документе Parallelizing N-Queens with the Intel Parallel Composer , а с использованием Cilk++ - здесь.

В заключении, по традиции две загадки − как вы думаете, что это такое?

1)

  int t(int a, int b, int c){
     int d=0, e=a&~b&~c, f=1;
     if (a)
       for (f=0; d=(e-=d)&-e;)
         f+=t(a-d,(b+d)*2,(c+d)/2);
     return f;
  }
  int main( int argc, char *argv[]) {
    int q = atoi ( argv [ 1 ] );
    printf("%d\n",t(~(~0<<q),0,0));
  }
  

2)

Ответы − в следующем посте.

Категории: Intel Software Network, Параллельное программирование, Разработка софта

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

Комментарии (13)

29.07.2009 07:04

Dmitry Oganezov (Intel)
Dmitry Oganezov (Intel)Всего баллов:
25,473
Community Manager
Очень, очень элегантно. Напоминает раннего Страуструпа (всмысле, напоминает мои же ощущения от первой попытки прочитать Страуструпа).

Кстати, как там наши в TC2009? Что-то я не от русских участников, не от американских судей давненько ничего не слышал.

Что такое 1) я кажись понял. Пошел за чипсами и попкорном, надо подготовиться к холивару постов на 50 ;)


29.07.2009 08:37

yuryserdyuk
yuryserdyukВсего баллов:
5,180
зеленый пояс
>Пошел за чипсами и попкорном, надо подготовиться к холивару постов на 50 ;)

Не, я теперь уже усиленно слежу за тем, чтоб вдруг не написать, что
" ... решение X лучше, чем решение в Intel TBB" :-
29.07.2009 08:45

ksili
ksiliВсего баллов:
7,570
коричневый пояс
Я так понял, свои варианты ответов уже можно предлагать ))

1) Это тот самый обещанный прорыв 2009 года.
2) видимо какой-то умелец решил реализовать решение задачи о ферзях на цифровой схемотехнике. Может даже использовал VHDL, а схема сгенерилась более-менее полуавтоматически. Получилась такая схема. В ней наверно параллелизм уже такой, что сильнее распараллелить не удастся.
29.07.2009 08:53

ksili
ksiliВсего баллов:
7,570
коричневый пояс
Я кстати сам никогда не пробовал реализовать решение задчи о ферзях. Но прочитав сейчас задание и посмотрев на доску, у меня в голове возникла схема перебора, которую по идее наиболее правильно было бы реализовать при помощи рекурсии. А потом смотрю - в 1) тоже рекурсивное решение.
29.07.2009 08:59

yuryserdyuk
yuryserdyukВсего баллов:
5,180
зеленый пояс
>1) Это тот самый обещанный прорыв 2009 года.

Нет, неправильно - реализуйте приведенные выше 2 варианта программы на одном языке программирования и
замерьте время их работы. Что получится ?
Хотя по самому коду уже можно сделать вывод ...
Вспомним также строчку из поста
" ... Суммируем чего мы добились − мы представили конфигурации ферзей на доске в виде битовых векторов, а их модификацию выразили исключительно через битовые операции. "

29.07.2009 09:05

yuryserdyuk
yuryserdyukВсего баллов:
5,180
зеленый пояс
>2) видимо какой-то умелец решил реализовать решение задачи о ферзях на цифровой схемотехнике.

Ну да, почти правильно, только с тем замечанием, что утверждение, что аппаратный параллелизм сильнее,
чем программный не совсем верно. Другое дело, что в силу банальных причин аппаратная параллельная схема работает на порядок- другой быстрее любой программной параллельной реализации.
30.07.2009 05:33

Alexey Kukanov (Intel)
Alexey Kukanov (Intel)Всего баллов:
21,744
черный пояс
> усиленно слежу за тем, чтоб вдруг не написать, что " ... решение X лучше, чем решение в Intel TBB" :-)

Пишите, Юрий; при формулировке "лучше/хуже" по некоторому критерию (быстрее/медленнее, проще/сложнее) я буду только рад участвовать при конструктивном обсуждении. Но я возражаю против формулировок типа "правильный/неправильный" (подход, язык, модель) и против намеренно необъективного выставления языков/моделей/подходов, которые вы не разделяете, в невыгодном свете.

К примеру, если на основе кода в вашей задачке номер 1 постулировать, что С/С++ - язык позапрошлого века, потому что на С# решение выглядит куда более элегантно - это было бы передёргиванием, против которого я бы возражал.
30.07.2009 05:35

Alexey Kukanov (Intel)
Alexey Kukanov (Intel)Всего баллов:
21,744
черный пояс
"буду рад участвовать _в_ конструктивном обсуждении".
Извините, при правке фразы не заметил несогласованность, а редактировать комментарии невозможно.
30.07.2009 06:00

yuryserdyuk
yuryserdyukВсего баллов:
5,180
зеленый пояс
Да, Дима, оказался прав, но приходится отвечать - слишком уж серьезные обвинения выставлены ...

>намеренно необъективного выставления языков/моделей/подходов,
>которые вы не разделяете, в невыгодном свете.

Пожалуйста, приведите конкретные примеры
во-первых, "намеренного" и
во-вторых, "необъективного"
- далее по тексту ...

С этой же точки зрения, просьба прокомментировать пост Клэя Брешерса -
http://software.intel.com/en-us/blogs/2006/12/18/threading-b.....a-problem/
- попадает ли он тоже под Вашу статью "намеренного и необъективного" ?

Наконец, Вы, действительно, подумали, что приведенный мной вариант на С
противопоставлен варианту на C# ;) ?

Отнюдь нет - он приведен исключительно по двум причинам:

1) показать насколько компактно может быть записан этот алгоритм
(кстати, его в абсолютно таком же виде можно записать и на C#),
2) показать пример умышленно компактной записи, которая бы
затруднила верификацию этой программы
(см. http://why.lri.fr/queens/index.en.html).


30.07.2009 07:00

Alexey Kukanov (Intel)
Alexey Kukanov (Intel)Всего баллов:
21,744
черный пояс
Юрий, мне интересно Вас читать (было бы наоборот - я бы не стал вступать в споры), и мне не хотелось бы, чтобы Вы (и другие) думали, будто я целенаправленно на вас нападаю - поверьте, не имею такого намерения.

Я не считаю, что Дима оказался прав - мы же пока что здесь не спорим о языках? Я вообще свой комментарий писал с другими, если не противоположными, целями, нежели обвинения. Но раз Вы его так восприняли (а значит, я плохо выразил свои мысли и намерения), мне придётся отвечать Вам. Однако, если Вы позволите, давайте переведём выяснение отношений (и приведение примеров) в приват. А пока я отвечу на два других вопроса.

1) Пост Клэя не попадает под моё понимание некорректного сравнения. Клэй сравнивает конструкции того же самого уровня TBB и OpenMP, и возражает против технически некорректного маркетинга. Вопрос "What did TBB bring to the table that wasn't already available from something else?" корректен и уместен. Точно так же его можно задать в отношении PPL, OpenMP 3.0, или MC#. Его вывод "Perhaps the marketing has overreached the initial 1.0 version of the product" не вызывает возражений.

2) Нет, я не подумал о противопоставлении языков в этом случае, просто воспользовался "подручным материалом" для иллюстрации своей позиции. Спасибо за комментарий о целях, код действительно отлично иллюстрирует их.
30.07.2009 08:00

Dmitry Oganezov (Intel)
Dmitry Oganezov (Intel)Всего баллов:
25,473
Community Manager
Я не авторитарен, я просто всегда прав :)))))))

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




30.07.2009 13:41

mt2
mt2Всего баллов:
13,459
Зарегистрированный пользователь
> Но ведь на любом языке можно написать код так, чтобы он читался. И так, чтобы он выглядел как шифровка Штирлица.

В принципе верно, но попробуйте написать хорошочитаемый код этого примера на ФОРТРАНе-4 (который рекурсию не поддерживал, и с битовыми операциями были проблемы ;)))
16.08.2010 06:20


asdewq
Спасибо, интересно! Будем читать дальше.
http://infoprog.tk – программирование для начинающих и программирование для чайников

Обратная ссылка (2)


Оставить комментарий  

Для получения технической помощи посетите сайт службы поддержки.
Имя (обязательно)*

Электронная почта (обязательно; не будет отображено на этой странице)*

Ваш URL-адрес (необязательно)


Комментарий*