Алгоритм быстрого размещения блоков, нужен совет?

Мне нужно эмулировать стратегию размещения окон оконного менеджера Fluxbox.

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

Если в вашей системе установлены Fluxbox и Xterm, вы можете попробовать xwinmidiarptoy BASH-скрипт, чтобы увидеть грубый прототип того, что я хочу. См. примечания xwinmidiarptoy.txt, которые я сделал написано об этом, объясняя, что он делает и как его следует использовать.

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

Алгоритм должен быть Онлайн-алгоритм, обрабатывающий данные "по частям". -кусок в последовательном порядке, т. е. в том порядке, в котором ввод подается в алгоритм, без того, чтобы весь ввод был доступен с самого начала».

Стратегия размещения окна Fluxbox имеет три бинарных варианта, которым я хочу подражать:

  • Windows строит горизонтальные строки или вертикальные столбцы (потенциально)

  • Окна располагаются слева направо или справа налево

  • Окна располагаются сверху вниз или снизу вверх

Различия между целевым алгоритмом и алгоритмом размещения окна

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

Почему алгоритм является проблемой?

Он должен работать в соответствии со сроками потока реального времени в аудиоприложении.

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

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

Пока у меня есть два варианта, для которых я создал свободные прототипы:

1) Порт алгоритма размещения Fluxbox в моем коде.

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

Для демонстрации этого я создал программу под названием text_boxer-0.0.2.tar.bz2, который принимает текстовый файл в качестве входных данных и упорядочивает его в полях ASCII. Выпустите make, чтобы построить его. Немного недружелюбно, используйте --help (или любой другой недопустимый параметр) для списка параметров командной строки. Вы должны указать текстовый файл с помощью параметра.

2) Мой альтернативный подход.

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

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

ВАЖНОЕ ПРАВИЛО: каждый блок может соприкасаться только с одним блоком с каждой стороны. Это правило специфично для алгоритма хранения свободного неиспользуемого пространства и не влияет на то, сколько фактических окон могут соприкасаться друг с другом.

Проблема с этим подходом в том, что он очень сложен. Я реализовал простые случаи, когда 1) пространство удалено из одного угла блока, 2) разделение соседних блоков, чтобы соблюдалось ВАЖНОЕ ПРАВИЛО.

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

Я реализовал этот алгоритм на C - языке, который я использую для этого проекта (я не использовал C++ в течение нескольких лет, и мне неудобно его использовать после того, как я сосредоточил все свое внимание на разработке C, это хобби). Реализация состоит из 700+ строк кода (включая множество пустых строк, фигурных скобок, комментариев и т. д.). Реализация работает только для стратегии размещения горизонтальные ряды + лево-право + верх-низ.

Так что я должен либо добавить какой-то способ заставить эти +700 строк кода работать для других 7 вариантов стратегии размещения, либо мне придется продублировать эти +700 строк кода для других семи вариантов. Ни один из них не привлекателен, первый из-за того, что существующий код достаточно сложен, второй из-за наворотов.

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

Текущее состояние реализации этого алгоритма на C: freespace.c . Я использую gcc -O0 -ggdb freespace.c для сборки и запускаю его в xterm размером не менее 124 x 60 символов.

Что еще?

Я пролистал и сделал скидку:

  • Алгоритмы Bin Packing: упор на оптимальное соответствие не соответствует требованиям этого алгоритма.

  • Алгоритмы Recursive Bisection Placement: звучит многообещающе, но они предназначены для проектирования схем. Их упор делается на оптимальную длину провода.

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

Что вы думаете об этом? Как бы вы подошли к этому? На какие еще алгоритмы следует обратить внимание? Или даже какие концепции мне следует исследовать, поскольку я не изучал информатику/программную инженерию?

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

После того, как вы задали этот вопрос, возникли дополнительные идеи

  • Некоторая комбинация моего «альтернативного алгоритма» с пространственной хэш-картой для определения того, будет ли размещаемое большое окно покрывать несколько блоков свободного пространства.

person Community    schedule 30.04.2010    source источник
comment
О, почему я настаиваю на том, чтобы задавать такие сложные вопросы в пятницу вечером (по местному времени), когда у всех есть дела поважнее????   -  person James Morris    schedule 30.04.2010
comment
Это единственные интересные вопросы на SO! Ежедневный поток тривиальных вопросов заставляет меня не хотеть посещать этот сайт.   -  person Victor Liu    schedule 30.04.2010
comment
@Джеймс Моррис: у меня странное чувство, что я уже читал более короткую версию этого вопроса...   -  person ire_and_curses    schedule 30.04.2010
comment
@ire_and_curses: Да, я продолжаю задавать очень похожие вопросы, не получая ответов, а потом удаляя их.   -  person James Morris    schedule 30.04.2010
comment
@James Morris: Должны ли окна быть выровнены по сетке 127x127 или они могут занимать, скажем, половину ячейки сетки? Каково распределение размеров окон?   -  person Victor Liu    schedule 30.04.2010
comment
Никаких полуклеток нет. Распределение размеров окон, самое маленькое должно быть 1x1, самое большое... Я не уверен, возможно 64x64?   -  person James Morris    schedule 30.04.2010
comment
Если весь ваш домен имеет размер всего 127x127, почему бы просто не использовать растровое изображение? Просто запишите, какие ячейки сетки заняты, а какие нет. Перебор всего этого не может занять много времени. Как быстро вам нужно иметь возможность добавлять/удалять окна?   -  person Victor Liu    schedule 30.04.2010
comment
@Victor: Ах, вот здесь все становится сложнее, потому что, хотя алгоритм размещения никогда не должен перекрывать окна, будут определенные ситуации, благодаря нашему дорогому другу пользователю, что окна действительно перекрываются.   -  person James Morris    schedule 30.04.2010
comment
@James: Итак, если вместо того, чтобы отслеживать, заняты ли блоки, вы просто сохраняете количество окон, использующих этот блок. Можете ли вы обновлять этот номер каждый раз, когда пользователь перемещает окно? Кроме того, что это за сумасшедшее приложение?!   -  person Victor Liu    schedule 30.04.2010
comment
Ритмичный рисунок вызовет размещение ящиков. Координаты x и y каждого размещенного прямоугольника будут определять высоту тона и скорость нажатия триггера, а затем MIDI-сообщение будет выводиться на JACK, т.е. запланировано, чтобы это был своего рода MIDI-секвенсор/арпеджиатор. Между тем появление/исчезновение всех этих окон загипнотизирует пользователя!   -  person James Morris    schedule 30.04.2010
comment
Разрешается ли вашему алгоритму изменять положение существующих окон? Не будет ли это слишком запутанным для пользователя?   -  person outis    schedule 01.05.2010
comment
@outis: нет, алгоритм не может изменять положение окон.   -  person James Morris    schedule 01.05.2010


Ответы (3)


Я бы рассмотрел какую-то пространственную структуру хеширования. Представьте, что все ваше свободное пространство грубо размечено сеткой, назовите их блоками. Когда окна появляются и исчезают, они занимают определенные наборы смежных прямоугольных блоков. Для каждого блока отслеживайте наибольший неиспользуемый прямоугольник, попадающий в каждый угол, поэтому вам нужно хранить 2*4 действительных числа в каждом блоке. Для пустого блока прямоугольники в каждом углу имеют размер, равный блоку. Таким образом, блок может быть «использован» только по его углам, поэтому в любом блоке может находиться не более 4 окон.

Теперь каждый раз, когда вы добавляете окно, вы должны искать прямоугольный набор блоков, для которых окно подойдет, и когда вы это сделаете, обновить размеры свободных углов. Размер блоков должен быть таким, чтобы несколько (~ 4x4) из них помещались в обычное окно. Для каждого окна отслеживайте, каких блоков оно касается (вам нужно отслеживать только экстенты), а также какие окна касаются данного блока (максимум 4, в этом алгоритме). Существует очевидный компромисс между степенью детализации блоков и объемом работы на вставку/удаление окна.

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

Я представляю себе структуру данных, например

struct block{
    int free_x[4]; // 0 = top left, 1 = top right,
    int free_y[4]; // 2 = bottom left, 3 = bottom right
    int n_windows; // number of windows that occupy this block
    int window_id[4]; // IDs of windows that occupy this block
};
block blocks[NX][NY];

struct window{
    int id;
    int used_block_x[2]; // 0 = first index of used block,
    int used_block_y[2]; // 1 = last index of used block
};

Изменить

Вот картинка:

альтернативный текст

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

Вы упомянули в редактировании, что сетка, на которой будут размещены ваши окна, уже довольно грубая (127x127), поэтому размеры блоков, вероятно, будут примерно 4 ячейки сетки на стороне, что, вероятно, не принесет вам многого. Этот метод подходит, если координаты угла вашего окна могут принимать много значений (я думал, что это будут пиксели), но не так много в вашем случае. Вы все еще можете попробовать, так как это просто. Вы, вероятно, захотите также сохранить список полностью пустых блоков, чтобы, если появляется окно, ширина которого превышает 2 блока, вы сначала смотрели в этот список, прежде чем искать подходящее свободное место в сетке блоков.

person Victor Liu    schedule 30.04.2010
comment
+1 от меня, Виктор, как бы странно это ни звучало, совершенно другого подхода я не ожидал. Это очень интересно, просто я еще не совсем в этом разбираюсь. Смотрите мое редактирование, в котором упоминаются единицы измерения, насколько грубо сетка свободного пространства? Есть шанс схемы? - person James Morris; 30.04.2010

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

Следующая функция просматривает массив в поисках области размером width * height. Первая найденная позиция записывает верхние левые координаты туда, куда указывают resultx и resulty.

_Bool freespace_remove( freespace* fs,
                        int width,     int height,
                        int* resultx,  int* resulty)
{
    int x = 0;
    int y = 0;
    const int rx = FSWIDTH - width;
    const int by = FSHEIGHT - height;

    *resultx = -1;
    *resulty = -1;

    char* buf[height];

    for (y = 0; y < by; ++y)
    {
        x = 0;
        char* scanx = fs->buf[y];

        while (x < rx)
        {
            while(x < rx && *(scanx + x))
                ++x;

            int w, h;

            for (h = 0; h < height; ++h)
                buf[h] = fs->buf[y + h] + x;

            _Bool usable = true;
            w = 0;

            while (usable && w < width)
            {
                h = 0;
                while (usable && h < height)
                    if (*(buf[h++] + w))
                        usable = false;
                ++w;
            }

            if (usable)
            {
                for (w = 0; w < width; ++w)
                    for (h = 0; h < height; ++h)
                        *(buf[h] + w) = 1;

                *resultx = x;
                *resulty = y;
                return true;
            }

            x += w;
        }
    }

    return false;
}

Массив 2d инициализирован нулем. Любые области в массиве, где используется пространство, устанавливаются равными 1. Эта структура и функция будут работать независимо от фактического списка окон, занимающих области, отмеченные единицами.

Преимуществом этого метода является его простота. Он использует только одну структуру данных — массив. Функция короткая, и ее не должно быть слишком сложно адаптировать для обработки оставшихся вариантов размещения (здесь она обрабатывает только Row Smart + Left to Right + Top to Bottom).

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

Компилируемый тестовый код здесь: http://jwm-art.net/art/text/freespace_grid.c
(в Linux я использую gcc -ggdb -O0 freespace_grid.c для компиляции)

person Community    schedule 03.05.2010
comment
Я собираюсь попробовать использовать отдельные биты, а не целые символы для хранения, используется ли ячейка или нет. - person James Morris; 04.05.2010
comment
Я все еще работаю над использованием каждого отдельного бита массива 2d для представления, занята ли каждая ячейка окном или нет. Это означает, что 2d-массив теперь buf_type buf[128][128 / (sizeof(buf_type) * CHAR_BIT)] — да, каждый отдельный элемент в 2d-массиве является типом с определенным типом, поэтому я могу протестировать использование char против int, short, long и посмотреть, как это повлияет на производительность. Как только у меня будет работать конкретная реализация row-smart + top-left + top-bottom, я опубликую ее как еще один ответ с подробным описанием того, как она сравнивается с этим (намного) более простым решением. - person James Morris; 08.05.2010

#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>


#define FSWIDTH 128
#define FSHEIGHT 128


#ifdef USE_64BIT_ARRAY
    #define FSBUFBITS 64
    #define FSBUFWIDTH 2
    typedef uint64_t fsbuf_type;
    #define TRAILING_ZEROS( v ) __builtin_ctzl(( v ))
    #define LEADING_ONES( v )   __builtin_clzl(~( v ))
#else
#ifdef USE_32BIT_ARRAY
    #define FSBUFBITS 32
    #define FSBUFWIDTH 4
    typedef uint32_t fsbuf_type;
    #define TRAILING_ZEROS( v ) __builtin_ctz(( v ))
    #define LEADING_ONES( v )   __builtin_clz(~( v ))
#else
#ifdef USE_16BIT_ARRAY
    #define FSBUFBITS 16
    #define FSBUFWIDTH 8
    typedef uint16_t fsbuf_type;
    #define TRAILING_ZEROS( v ) __builtin_ctz( 0xffff0000 | ( v ))
    #define LEADING_ONES( v )   __builtin_clz(~( v ) << 16)
#else
#ifdef USE_8BIT_ARRAY
    #define FSBUFBITS 8
    #define FSBUFWIDTH 16
    typedef uint8_t fsbuf_type;
    #define TRAILING_ZEROS( v ) __builtin_ctz( 0xffffff00 | ( v ))
    #define LEADING_ONES( v )   __builtin_clz(~( v ) << 24)
#else
    #define FSBUFBITS 1
    #define FSBUFWIDTH 128
    typedef unsigned char fsbuf_type;
    #define TRAILING_ZEROS( v ) (( v ) ? 0 : 1)
    #define LEADING_ONES( v )   (( v ) ? 1 : 0)
#endif
#endif
#endif
#endif


static const fsbuf_type fsbuf_max =   ~(fsbuf_type)0;
static const fsbuf_type fsbuf_high =  (fsbuf_type)1 << (FSBUFBITS - 1);


typedef struct freespacegrid
{
    fsbuf_type buf[FSHEIGHT][FSBUFWIDTH];

    _Bool left_to_right;
    _Bool top_to_bottom;

} freespace;


void freespace_dump(freespace* fs)
{
    int x, y;

    for (y = 0; y < FSHEIGHT; ++y)
    {
        for (x = 0; x < FSBUFWIDTH; ++x)
        {
            fsbuf_type i = FSBUFBITS;
            fsbuf_type b = fs->buf[y][x];

            for(; i != 0; --i, b <<= 1)
                putchar(b & fsbuf_high ? '#' : '/');
/*
            if (x + 1 < FSBUFWIDTH)
                putchar('|');
*/
        }
        putchar('\n');
    }
}


freespace* freespace_new(void)
{
    freespace* fs = malloc(sizeof(*fs));

    if (!fs)
        return 0;

    int y;

    for (y = 0; y < FSHEIGHT; ++y)
    {
        memset(&fs->buf[y][0], 0, sizeof(fsbuf_type) * FSBUFWIDTH);
    }

    fs->left_to_right = true;
    fs->top_to_bottom = true;

    return fs;
}


void freespace_delete(freespace* fs)
{
    if (!fs)
        return;

    free(fs);
}

/* would be private function: */
void fs_set_buffer( fsbuf_type buf[FSHEIGHT][FSBUFWIDTH],
                    unsigned x,
                    unsigned y1,
                    unsigned xoffset,
                    unsigned width,
                    unsigned height)
{
    fsbuf_type v;
    unsigned y;

    for (; width > 0 && x < FSBUFWIDTH; ++x)
    {
        if (width < xoffset)
            v = (((fsbuf_type)1 << width) - 1) << (xoffset - width);
        else if (xoffset < FSBUFBITS)
            v = ((fsbuf_type)1 << xoffset) - 1;
        else
            v = fsbuf_max;

        for (y = y1; y < y1 + height; ++y)
        {
#ifdef FREESPACE_DEBUG
            if (buf[y][x] & v)
                printf("**** over-writing area ****\n");
#endif
            buf[y][x] |= v;
        }

        if (width < xoffset)
            return;

        width -= xoffset;
        xoffset = FSBUFBITS;
    }
}


_Bool freespace_remove(   freespace* fs,
                          unsigned width, unsigned height,
                          int* resultx,   int* resulty)
{
    unsigned x, x1, y;
    unsigned w, h;
    unsigned xoffset, x1offset;
    unsigned tz; /* trailing zeros */

    fsbuf_type* xptr;
    fsbuf_type mask =   0;
    fsbuf_type v;

    _Bool scanning = false;
    _Bool offset = false;

    *resultx = -1;
    *resulty = -1;

    for (y = 0; y < (unsigned) FSHEIGHT - height; ++y)
    {
        scanning = false;
        xptr = &fs->buf[y][0];

        for (x = 0; x < FSBUFWIDTH; ++x, ++xptr)
        {
            if(*xptr == fsbuf_max)
            {
                scanning = false;
                continue;
            }

            if (!scanning)
            {
                scanning = true;
                x1 = x;
                x1offset = xoffset = FSBUFBITS;
                w = width;
            }
retry:
            if (w < xoffset)
                mask = (((fsbuf_type)1 << w) - 1) << (xoffset - w);
            else if (xoffset < FSBUFBITS)
                mask = ((fsbuf_type)1 << xoffset) - 1;
            else
                mask = fsbuf_max;

            offset = false;

            for (h = 0; h < height; ++h)
            {
                v = fs->buf[y + h][x] & mask;

                if (v)
                {
                    tz = TRAILING_ZEROS(v);
                    offset = true;
                    break;
                }
            }

            if (offset)
            {
                if (tz)
                {
                    x1 = x;
                    w = width;
                    x1offset = xoffset = tz;
                    goto retry;
                }
                scanning = false;
            }
            else
            {
                if (w <= xoffset) /***** RESULT! *****/
                {
                    fs_set_buffer(fs->buf, x1, y, x1offset, width, height);
                    *resultx = x1 * FSBUFBITS + (FSBUFBITS - x1offset);
                    *resulty = y;
                    return true;
                }
                w -= xoffset;
                xoffset = FSBUFBITS;
            }
        }
    }
    return false;
}


int main(int argc, char** argv)
{
    int x[1999];
    int y[1999];
    int w[1999];
    int h[1999];

    int i;

    freespace* fs = freespace_new();

    for (i = 0; i < 1999; ++i, ++u)
    {
        w[i] = rand() % 18 + 4;
        h[i] = rand() % 18 + 4;

        freespace_remove(fs, w[i], h[i], &x[i], &y[i]);
/*
        freespace_dump(fs);
        printf("w:%d h:%d x:%d y:%d\n", w[i], h[i], x[i], y[i]);
        if (x[i] == -1)
            printf("not removed space %d\n", i);
        getchar();
*/
    }

    freespace_dump(fs);
    freespace_delete(fs);

    return 0;
}

Приведенный выше код требует определения одного из USE_64BIT_ARRAY, USE_32BIT_ARRAY, USE_16BIT_ARRAY, USE_8BIT_ARRAY, иначе он вернется к использованию только старшего бита unsigned char для хранения состояния ячеек сетки.

Функция fs_set_buffer не будет объявлена ​​в заголовке и станет статической в ​​реализации, когда этот код будет разделен между файлами .h и .c. Для удаления используемого пространства из сетки будет предоставлена ​​более удобная функция, скрывающая детали реализации.

В целом, эта реализация быстрее без оптимизации, чем мой предыдущий ответ с максимальной оптимизацией (с использованием GCC на 64-битной версии Gentoo, опции оптимизации -O0 и -O3 соответственно).

Что касается USE_NNBIT_ARRAY и разных размеров битов, я использовал два разных метода синхронизации кода, который делает 1999 вызовов freespace_remove.

Время main() с использованием команды Unix time (и отключение любого вывода в коде), казалось, подтвердило мои ожидания - более высокие размеры битов быстрее.

С другой стороны, синхронизация отдельных вызовов freespace_remove (используя gettimeofday) и сравнение максимального времени, затраченного на вызовы 1999 г., казалось, указывало на то, что более низкие битовые размеры были быстрее.

Это было протестировано только на 64-битной системе (Intel Dual Core II).

person Community    schedule 15.05.2010