Подтвердить что ты не робот

Сортировка объектов динамического размера

Проблема

Предположим, что у меня есть большой массив байтов (до 4 ГБ), содержащий некоторые данные. Эти байты соответствуют отдельным объектам таким образом, что каждый s байтов (думаю s до 32) будет представлять собой один объект. Важным является тот факт, что этот размер s одинаковый для всех объектов, не сохраненный в самих объектах, и не известен во время компиляции.

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

Идеи до сих пор

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

1. C quicksort

Конечно, алгоритм быстрой сортировки C доступен также в приложениях С++. Его подпись почти полностью соответствует моим требованиям. Но тот факт, что использование этой функции запретит встраивание функции сравнения, будет означать, что каждое сравнение несет служебные вызовы функции. Я надеялся на то, чтобы избежать этого. Любой опыт о том, как C qsort_r сравнивается с STL с точки зрения производительности, будет очень желанным.

2. Направление с использованием объектов, указывающих на данные

Было бы легко написать кучу объектов, содержащих указатели на их соответствующие данные. Тогда можно было бы их отсортировать. Здесь есть два аспекта. С одной стороны, просто перемещение указателей вместо всех данных будет означать меньше операций с памятью. С другой стороны, не перемещение объектов, вероятно, приведет к разрыву локальности памяти и, таким образом, кэш-памяти. Вероятность того, что более глубокие уровни рекурсии quicksort могут фактически получить доступ ко всем их данным с нескольких страниц кеша, исчезнут почти полностью. Вместо этого каждая страница кэшированной памяти будет иметь только очень мало используемых элементов данных перед заменой. Если бы кто-нибудь мог представить некоторый опыт компромисса между копированием и локализацией памяти, я был бы очень рад.

3. Пользовательский итератор, объекты ссылок и значений

Я написал класс, который служит итератором по диапазону памяти. Разыменование этого итератора дает не ссылку, а вновь построенный объект, чтобы удерживать указатель на данные и размер s, который задается при построении итератора. Таким образом, эти объекты можно сравнить, и у меня даже есть реализация std::swap для них. К сожалению, кажется, что std::swap недостаточно для std::sort. В некоторых частях процесса моя реализация gcc использует сортировку вставки (как реализовано в __insertion_sort в файле stl_alog.h), которая перемещает значение из последовательности, перемещает число элементов на один шаг и затем возвращает первое значение назад в последовательность в соответствующем положении:

          typename iterator_traits<_RandomAccessIterator>::value_type
            __val = _GLIBCXX_MOVE(*__i);
          _GLIBCXX_MOVE_BACKWARD3(__first, __i, __i + 1);
          *__first = _GLIBCXX_MOVE(__val);

Знаете ли вы о стандартной реализации сортировки, которая не требует типа значения, но может работать только с свопами?

Поэтому мне нужен не только мой класс, который будет служить ссылкой, но мне также понадобится класс для хранения временного значения. И поскольку размер моих объектов является динамическим, мне пришлось бы выделить это в куче, что означает выделение памяти на самых листах дерева recusrion. Возможно, одним из альтернатив будет тип vaue со статическим размером, который должен быть достаточно большим, чтобы удерживать объекты с размерами, которые я в настоящее время намерен поддерживать. Но это означало бы, что будет еще больше хакерства в отношении между reference_type и value_type класса итератора. И это означало бы, что мне придется обновить этот размер для моего приложения, чтобы в один прекрасный день поддерживать более крупные объекты. Некрасиво.

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

4. Пользовательская сортировка

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

5. Полностью другой алгоритм

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

Вопрос

Итак, в основном, мой вопрос таков:
"Как бы вы эффективно сортировали объекты динамического размера в кучной памяти?"

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

4b9b3361

Ответ 1

Поскольку существует только 31 различных вариаций объектов (от 1 до 32 байтов), вы можете легко создать тип объекта для каждого и выбрать вызов std::sort на основе оператора switch. Каждый вызов будет встроен и оптимизирован.

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

Ответ 2

Наиболее практичным решением является использование стиля C qsort, о котором вы упомянули.

template <unsigned S>
struct my_obj {
    enum { SIZE = S; };
    const void *p_;
    my_obj (const void *p) : p_(p) {}
    //...accessors to get data from pointer
    static int c_style_compare (const void *a, const void *b) {
        my_obj aa(a);
        my_obj bb(b);
        return (aa < bb) ? -1 : (bb < aa);
    }
};

template <unsigned N, typename OBJ>
void my_sort (const char (&large_array)[N], const OBJ &) {
    qsort(large_array, N/OBJ::SIZE, OBJ::SIZE, OBJ::c_style_compare);
}

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

Ответ 3

Если вы можете наложить объект на свой буфер, вы можете использовать std::sort, если ваш тип наложения можно скопировать. (В этом примере 4 64-битных целых числа). С 4 ГБ данных вам понадобится много памяти.

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

Вот простой пример:

#include <vector>
#include <algorithm>
#include <iostream>
#include <ctime>

template <int WIDTH>
struct variable_width
{
   unsigned char w_[WIDTH];
};

typedef variable_width<8> vw8;
typedef variable_width<16> vw16;
typedef variable_width<32> vw32;
typedef variable_width<64> vw64;
typedef variable_width<128> vw128;
typedef variable_width<256> vw256;
typedef variable_width<512> vw512;
typedef variable_width<1024> vw1024;

bool operator<(const vw64& l, const vw64& r)
{
   const __int64* l64 = reinterpret_cast<const __int64*>(l.w_);
   const __int64* r64 = reinterpret_cast<const __int64*>(r.w_);

   return *l64 < *r64;
}

std::ostream& operator<<(std::ostream& out, const vw64& w)
{
   const __int64* w64 = reinterpret_cast<const __int64*>(w.w_);
   std::cout << *w64;
   return out;
}

int main()
{
   srand(time(NULL));
   std::vector<unsigned char> buffer(10 * sizeof(vw64));
   vw64* w64_arr = reinterpret_cast<vw64*>(&buffer[0]);

   for(int x = 0; x < 10; ++x)
   {
      (*(__int64*)w64_arr[x].w_) = rand();
   }

   std::sort(
      w64_arr,
      w64_arr + 10);

   for(int x = 0; x < 10; ++x)
   {
      std::cout << w64_arr[x] << '\n';
   }

   std::cout << std::endl;

   return 0;
}

Ответ 4

Я согласен с std::sort с использованием пользовательского итератора, ссылки и типа значений; лучше всего использовать стандартную технику, где это возможно.

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

Ответ 5

Учитывая огромный размер (4 ГБ), я бы серьезно подумал о динамическом генерации кода. Скомпилируйте пользовательский сортир в общую библиотеку и динамически загрузите его. Единственным неинтерминированным вызовом должен быть вызов в библиотеку.

С предварительно скомпилированными заголовками время компиляции может быть не так уж плохо. Целый заголовок <algorithm> не изменяется, а также ваша логика обертки. Вам просто нужно перекомпилировать один предикат каждый раз. И поскольку это единственная функция, которую вы получаете, соединение является тривиальным.

Ответ 6

#define OBJECT_SIZE 32
struct structObject
{
    unsigned char* pObject;
    bool operator < (const structObject &n) const
    {
        for(int i=0; i<OBJECT_SIZE; i++)
        {
            if(*(pObject + i) != *(n.pObject + i))
                return (*(pObject + i) < *(n.pObject + i));
        }

        return false;       
    }
};

int _tmain(int argc, _TCHAR* argv[])
{       
    std::vector<structObject> vObjects;
    unsigned char* pObjects = (unsigned char*)malloc(10 * OBJECT_SIZE); // 10 Objects


    for(int i=0; i<10; i++)
    {
        structObject stObject;
        stObject.pObject = pObjects + (i*OBJECT_SIZE);      
        *stObject.pObject = 'A' + 9 - i; // Add a value to the start to check the sort
        vObjects.push_back(stObject);
    }

    std::sort(vObjects.begin(), vObjects.end());


    free(pObjects);

Чтобы пропустить #define

struct structObject
{
    unsigned char* pObject; 
};

struct structObjectComparerAscending 
{
    int iSize;

    structObjectComparerAscending(int _iSize)
    {
        iSize = _iSize;
    }

    bool operator ()(structObject &stLeft, structObject &stRight)
    { 
        for(int i=0; i<iSize; i++)
        {
            if(*(stLeft.pObject + i) != *(stRight.pObject + i))
                return (*(stLeft.pObject + i) < *(stRight.pObject + i));
        }

        return false;       
    }
};

int _tmain(int argc, _TCHAR* argv[])
{   
    int iObjectSize = 32; // Read it from somewhere

    std::vector<structObject> vObjects;
    unsigned char* pObjects = (unsigned char*)malloc(10 * iObjectSize);

    for(int i=0; i<10; i++)
    {
        structObject stObject;
        stObject.pObject = pObjects + (i*iObjectSize);      
        *stObject.pObject = 'A' + 9 - i; // Add a value to the start to work with something...  
        vObjects.push_back(stObject);
    }

    std::sort(vObjects.begin(), vObjects.end(), structObjectComparerAscending(iObjectSize));


    free(pObjects);