пятница, 26 декабря 2008 г.

Про корпоративы

В очень не плохом фильме "Место встречи изменить нельзя" (али, кому ближе, в книге "Эра милосердия"), мудрый Жеглов, объясняет молодому Шарапову, что ему надо "освоить работу со свидетелями". Это же надо осваивать всякого рода менеджерам и прочим новомодным управленцам, коии поставлены вдохновлять и оживлять всевозможных программистов на создание мега программ.

Итак у Вайнеров, разговор Шарапова и Жеглова выглядит следующим образом:

– Вот это и есть третье правило: как можно скорее найди в разговоре тему, которая ему близка и интересна.

– Ничего себе задачка – найти интересную тему для незнакомого человека!

– А для этого и существует четвертое правило: с первого мига проявляй к человеку искренний интерес – понимаешь, не показывай ему интерес, а старайся изо всех сил проникнуть в него, понять его, узнать, чем живет, что собой представляет; и тут, конечно, надо напрячься до предела. Но, коли сможешь, он тебе все расскажет...


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

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

четверг, 18 декабря 2008 г.

Про оптимизацию компилятором

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

Сравнение оптимизации компиляторами MSVS 2003 и MSVS 2005


Один и тот же код (функция масштабирования ) компилирую на MSVS 2003 и MSVS 2005 (на 2008 не пробовал). Получаем вот такие графики скоростей (под скоростью здесь понимается размер результирующей картинки поделенный на время работы функции):



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

Директива __forceinline


Директива __forceinline прекрасная вещь. Написал код все того же масштабирования померил время - получается отлично (я для сравнения использовал такие же функции из интеловской либы IPP - моя получилось в полтора раза быстрее, что меня вполне устраивало). Решил немного пооптимизировать код, в том числе одну из внутренних функций наградил директивой __forceinline, которая там смотрелась вполне к месту. Замерил время. Увеличилось в полтора, два раза. Так что дурное использование __forceinline может дать результаты обратные тем, которые предполагались.

воскресенье, 14 декабря 2008 г.

Про оптимизацию функции memset используя SSE

Продолжаю интересоваться возможностями SIMD команд в плане оптимизации своего кода. Сегодня поговорим, про заполнение памяти и соответственно функцию memset.

Функция memset, как известно, заполняет область памяти переданным байтом, соответственно на вход получает указатель на эту самую область и сам байт. Поскольку задача оптимизировать функцию с использованием SSE команд, писать мы будем на ассемблере. А значит, параметры функции будем передавать в регистрах процессора. Договоримся следующим образом. Указатель на память, которую необходимо заполнить передаем в регистре EDI, размер памяти (в байтах) – в ECX, а значение которым будем заполнять - в EAX.

Чтобы работать с ассемблером непосредственно в Visual C++ рекомендую, кроме служебного слова _asm, посмотреть в MSDN, что означает вот такая конструкция __declspec(naked). Местами это сильно облегчит жизнь.

Стандартный вариант функции memset выглядит следующим образом

mov         bl,al 
mov bh,bl
mov eax,ebx
shl eax,10h
mov ax,bx
mov ebx, ecx
shr ecx, 2
rep stosd
mov ecx,ebx
and ecx,3
rep stosb


Наша задача ускорить его, используя SSE команды.

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

Когда мы используем SSE, следует учитывать, что память, с которой мы работаем, должна быть выровнена к 16 байтам (иначе пересылка содержимого регистров XMM в память будет крайне медленной). Поэтому работа новой функции разбивается на три этапа. Первый заполняем память командой rep stosb, до того момента пока указатель не будет кратен 16, затем помещаем во все байты регистра XMM0, и в цикле копируем его в память. И, наконец, вновь при помощи rep stosb заполняем остаток. На первом и последнем шаге размер буфера будет от 0 и до 15 байт.

Выглядит эта новая функция, следующим образом:

 test ecx, ecx
jz func_exit

cmp ecx, 0x1F
jge sse_part

mov bl,al
mov bh,bl
mov eax,ebx
shl eax,10h
mov ax,bx
mov ebx, ecx
shr ecx, 2
rep stosd
mov ecx,ebx
and ecx,3
rep stosb
jmp func_exit
sse_part:
mov ebx, edi
and ebx, 0x0F
jz xmm_align

xor bl, 0x0F
inc ebx
sub ecx, ebx

loop_byte_2:
mov [edi], al
inc edi
dec ebx
jnz loop_byte_2

xmm_align:
pxor xmm0, xmm0
movd xmm0, eax
punpcklwd xmm0, xmm0
pshufd xmm0, xmm0, 0
movdqa xmm1, xmm0
psllw xmm1, 8
por xmm0, xmm1
mov ebx, ecx
and ebx, 0x0F
shr ecx, 4

loop_xmm:
movntdq [edi], xmm0
add edi, 0x10
dec ecx
jnz loop_xmm

mov ecx, ebx
rep stosb

func_exit:
sfence
emms
ret


Итак, и первый и второй вариант прекрасно работают. Теперь про интересное, а именно посмотрим на график скорости в зависимости от размера заполняемого блока. На нем по вертикальной оси прописаны размеры блока в байтах, гистограммы показывают скорость копирования в МБ/сек.



Не трудно видеть, что обычная функция существенно обгоняет SSE вариант на блоках до 1 мегабайта, однако проигрывает практически вдвое в случае, когда размер блока больше 1 мегабайта. Дополнительные измерения на отрезке от 500 000 байт до 1 000 000 байт показали, что смена лидера происходит в случае, когда размер блока примерно равен 590 000 байт. Не трудно догадаться, с чем связано такое поведение. У процессора, на котором производилось тестирование, L2 Cache – 512 КБ, соответственно, когда мы заполняли один и тот же блок постоянно, вся работа производилась внутри кеша, пока его хватало. На больших же блоках кеша не хватало, и мы имели резкое падение скорости работы обычной функции. Т.е. фактически мы имеем некорректный тестовый стенд.

Ну и что-то вроде вывода из всего этого хозяйства, который я сделал лично для себя. Если размер блока большой (больше мегабайта), то я пользую SSE вариант и не забиваю голову сомненьями. Если меньше, то смотрю по ситуации. Например, если много маленьких блоков разбросанных по памяти, и работать приходится то с одним то с другим, т.е. гарантировать, что все они поместятся в кешь и будут там постоянно находится я не могу, то снова пользую SSE вариант. А если надо 10 байт обнулить и потом с ними постоянно работать, то ну его этот SSE буду пользовать классический rep stosb.

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

четверг, 11 декабря 2008 г.

Про оптимизацию подробно

Решил собрать в кучу всякие мысли про оптимизацию. И расписать все это более или менее подробно.

Чтобы разговаривать по возможности предметно в качестве модельной рассмотрим такую задачу.

Допустим, нам надо генерировать черно-белую картинку (более точно массив байт, где 0 - соответствует белому цвету, 255 - черному). Размеры картинки будем предполагать от 5000 до 10 000 пикселей по каждой из осей. Генерация будет заключаться в отрисовке заданного набора черных прямоугольников (от 500 до 1000 штук, с размерами от 100 до 200 пикселей по каждой из осей) на белом фоне. Так же ограничимся случаем, когда стороны прямоугольников параллельны осям.

Задачка достаточно простая, и имеет массу решений. Однако, нам интересно рассмотреть подход к выбору из этих решений оптимального.

В работе по оптимизации программы можно выделить три этапа или три класса оптимизации.

1. Архитектурная оптимизация
2. Алгоритмическая оптимизация
3. Оптимизация кода

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

Архитектурная оптимизация


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

Обратимся к нашей модели. Нам надо понять: как будет использоваться сгенерированная картинка? Например, возможно картинка нужна нам для вывода на экран. Экраны размеров 5 000 на 5 000 маловероятны, и это позволяет нам при запросе создать только часть картинки. Даже, если по запросу вместо 25 000 000 = 5 000 * 5 000 пикселей, мы будем генерировать 1 920 * 1 080 = 2 073 600 пикселей (т.е. в 12 раз меньше), это даст существенный прирост в скорости генерации (надо понимать, что в зависимости от выбора алгоритма прирост может быть и не 12 кратный, а при особом упорстве можно добиться даже уменьшения скорости, нет вершин, которые бы не взял человеческий идиотизм).

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

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

Дабы не быть голословным рассмотрим еще несколько уточнений, которые могут существенно повлиять на выбор варианта решения.

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

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

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


Алгоритмическая оптимизация


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

Вновь вернемся к примеру с закраской прямоугольников. Самый простой алгоритм, который можно предложить для ее решения такой: проходим по всем пикселям картинки, для каждого пикселя проверяем, лежит ли он внутри прямоугольника или нет, и соответственно выставляем в нужный байт 0xFF или 0x00. Алгоритм ужасен, поскольку в худшем случае (например, когда все прямоугольники совпадают и вырождены в точку), необходимо будет для каждого пикселя проверить все прямоугольники и не найти пересечения. Второй вариант, заполнить сначала нулями весь выделенный под изображение массив, а затем, перебирая прямоугольники, закрашивать покрываемые ими пиксели. На самом деле тоже не слишком хороший вариант, например, когда прямоугольников много и они равномерно покрывают изображения, начальное обнуление массива становится длительной и лишней операцией.

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

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

Оптимизация кода


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

На этом этапе кроме головы и компилятора, понадобятся еще и специфические инструменты - так называемые «профилировщики». Программ таких не сказать, что много, но их есть. Я пользуюсь CodeAnalyst-ом от AMD, он достаточно простой в работе, вполне устраивает меня по функциональности (хотя и имеет корявый интерфейс), а самое главное бесплатен, в отличие от схожей утилиты от Intel, стоящей что-то около 600 долларов (честно говоря, с Intel-овской утилитой не разбирался вполне допускаю, что там и возможностей побогаче и интерфейс поудобнее).

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

Именно на данном этапе некоторые из частей кода, возможно, необходимо будет переписать на ассемблере, а некоторые и оптимизировать, используя SIMD инструкции. Однако, здесь важно не переусердствовать, ибо современные компиляторы с оптимизацией кода справляются вполне успешно, и стоит соблюдать баланс между «читабельностью» (которой код на ассемблере обладает в минимальной степени) и скоростью работы.




На этом пока все. В данном случае отзывы и замечания по статье приветствуются.

среда, 10 декабря 2008 г.

И здесь тоже непонятное

"Этот блог заблокирован в связи с возможным нарушением Условий предоставления услуг Blogger. Вы не сможете публиковать новые сообщения, пока блог не будет пересмотрен и разблокирован.

Если вы не запросите пересмотр, блог будет удален в течение 20 дней."

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

UPD: Кажись разблокировали - оперативненько. Что радует.

суббота, 6 декабря 2008 г.

"Он и в третий раз ходил за елкой..."(с)

В пятницу эпопея с картинами повторилась, художники вернулись. С нетерпением жду понедельника, узнать удержалась ли живопись на стенах.

UPD: Как и следовало ожидать - не удержалась.

четверг, 4 декабря 2008 г.

Про Opera 10.0 Alpha

Не впечатлила. blogger.com как обычно, т.е. совсем не так как надо. Разве что теперь по Ctrl+Shift не открывается предварительный просмотр (что в 9.6 просто конкретно задалбливало).

Speed dial - долго думал, но сайты так и не подгрузил - что из рук вон.

Смена скина на старый, конечно, сработала, но как-то не совсем, т.е. такое ощущение, что высота закладок стала больше.

Да и в принципе, меня как-то бета-тестинг не возбуждает. Через это снес эту альфу, буду пытаться привыкать к ИЕ7.

Про совпадения

Бывают в жизни совпадения.

Решил попрощаться с Oper-ой. Собственно к ней есть несколько претензий.

1. Далеко не все сайты отображаются нормально. Например, писать в этот блог практически не возможно.

2. Очень долго выгружается. Понятно почему - кешь здоровый и явно организован через известное место.

3. Жрет ресурсы. Когда начал заниматься оптимизацией и измерять время выполнения функций, стало заметно, что от того запущена или нет Opera время зависит весьма существенно.

4. Очень жрет ресурсы, т.е. открыв страницу может подвиснуть на 1-2 секнуды для рендеринга (как я думаю, может и еще для чего)

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

Вообщем решил вчера вечером, вчера же попробовал Firefox. В принципе, нареканий особых нет, по пп. 1, 2, 4 все вроде в порядке. И даже выглядит не сказать что ужасно. Конечно, не кеширует, но это не приоритетно. Но дернул же меня черт. Очень мне у Opera нравилась "Speed dial" страничка. Решил что надо бы такую заиметь и на Firefox. Нашел какой-то add-on, скачал. Мать моя родная - страничка у них получалась, страшнее чем моя жизнь. Т.е. отрендерить для эскиза страничку в большой размер, а потом замасштабировать идея в целом хорошая, но люди добрые, соотношение сторон то надо сохранить! Т.е. надо хотя бы в целом взглянуть на результат и как-то что-то докрутить. Ну ужас же смертный.

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

Сегодня на работе попробовал пользоваться IE7 - опять же по всем пунктам кроме 3 подходит (третий просто не проверял). Скачал и навесил на него какой-то мега изменятель, который дал возможность выкинуть комбик поиска (ибо если можно искать в адресной строке, то на фига бы он сдалось) и поставить главное меню на его привычное место (почему в IE7 эти опции не вынесены в настройки самой MS понять отказываюсь, честно говоря это похоже на вредительство, ну или на идиотизм, что встречается существенно более часто). Заметил кстати, что порботав с Oper-ой, отучился закрывать броузер, и чуствую себя не уютно, если он не весит в панеле задач.

Но собственно про совпадения.

Вечером зашел на softpedia (единственный кстати более менее приличный сайт из софтархивов, остальные из видинных, либо новодельная помойка в которой за adsense программу уже не разглядеть, либо как доунлоадс.ком - хер чего найдешь и ели ворочается) и чтобы вы думали - Opera 10.0 альфа. Вот скачал - буду поглядеть.

Несколько кусков кода

Хочу предложить некую подборку маленьких кусочков кода, для стандартных задач, часто встречающихся при программировании на MMX-SSE. Тут никаких откровений не будет, но может кому пригодится.

1. занулить все биты в SSE регистре (xmm0)

pxor  xmm0, xmm0


2. выставить все биты в SSE регистре (xmm0)

pcmpeqb  xmm0, xmm0


3. дублируем word CX в xmm0


movd xmm0, ecx ; xmm0 = ** ** ** ** | ** ** ** ** | ** ** ** ** | 00 00 VV UU
punpcklwd xmm0, xmm0 ; xmm0 = ** ** ** ** | ** ** ** ** | 00 00 00 00 | VV UU VV UU
pshufd xmm0, xmm0, 0 ; xmm0 = VV UU VV UU | VV UU VV UU | VV UU VV UU | VV UU VV UU


4. дублируем байт CL в xmm0



movd xmm0, ecx ; xmm0 = ** ** ** ** | ** ** ** ** | ** ** ** ** | 00 00 00 UU
punpcklwd xmm0, xmm0 ; xmm0 = ** ** ** ** | ** ** ** ** | 00 00 00 00 | 00 UU 00 UU
pshufd xmm0, xmm0, 0 ; xmm0 = 00 UU 00 UU | 00 UU 00 UU | 00 UU 00 UU | 00 UU 00 UU
movdqa xmm1, xmm0 ; xmm1 = 00 UU 00 UU | 00 UU 00 UU | 00 UU 00 UU | 00 UU 00 UU
psllw xmm1, 8 ; xmm1 = UU 00 UU 00 | UU 00 UU 00 | UU 00 UU 00 | UU 00 UU 00
por xmm0, xmm1 ; xmm0 = UU UU UU UU | UU UU UU UU | UU UU UU UU | UU UU UU UU


5. Перевернуть SSE регистр побайтово


__declspec( align(16) ) static BYTE g_carShufleMask[16] =
{
0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
};
_asm
{
movdqa xmm1, oword ptr [g_carShufleMask]
pshufb xmm0, xmm1
}


Последнее надо использовать аккуратно, pshufb - SSE2 команда (например, MSVC2003 вообще про нее не знает).

На последок. Крайне рекомендую найти и почитать статью "Using Block Prefetch for Optimized Memory Performance" очень хороший пошаговый разбор оптимизации функции memcpy с использованием кеша процессора. Скорость увеличивается в 2.5 раза (по статье в 3, но у меня на четырех компьютерах было от 2 до 2.5). Понятно, что ускорение memcpy (в особенности в тех условиях, которые заявлены в статье) не слишком интересно - но в целом почитать для начинающих вроде меня полезно.

Про искусство

Как сказал один не глупый дядя: "из всех искусств важнейшим для нас является кино". Т.е. сказал он не много по-другому, но цитата прижилась именно в таком виде.

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

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

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

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

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

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

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