Replace linked-list insert-sort with merge-sort

Original code from eglib, modified for reuse with multiple linked-list implementations.

Adds sort functions: BLI_linklist_sort, BLI_linklist_sort_r
This commit is contained in:
2015-06-11 15:13:06 +10:00
parent b8b57d2da9
commit 867cd2048e
5 changed files with 420 additions and 32 deletions

View File

@@ -74,6 +74,8 @@ void BLI_linklist_free(struct LinkNode *list, LinkNodeFreeFP freefunc);
void BLI_linklist_freeN(struct LinkNode *list);
void BLI_linklist_free_pool(LinkNode *list, LinkNodeFreeFP freefunc, struct BLI_mempool *mempool);
void BLI_linklist_apply(struct LinkNode *list, LinkNodeApplyFP applyfunc, void *userdata);
struct LinkNode *BLI_linklist_sort(LinkNode *list, int (*cmp)(const void *, const void *));
struct LinkNode *BLI_linklist_sort_r(LinkNode *list, int (*cmp)(void *, const void *, const void *), void *thunk);
#define BLI_linklist_prepend_alloca(listp, ptr) \
BLI_linklist_prepend_nlink(listp, ptr, alloca(sizeof(LinkNode)))

View File

@@ -72,6 +72,7 @@ set(SRC
intern/hash_mm2a.c
intern/jitter.c
intern/lasso.c
intern/list_sort_impl.h
intern/listbase.c
intern/math_base.c
intern/math_base_inline.c

View File

@@ -38,6 +38,8 @@
#include "BLI_memarena.h"
#include "BLI_mempool.h"
#include "BLI_strict_flags.h"
int BLI_linklist_length(LinkNode *list)
{
int len;
@@ -308,3 +310,40 @@ void BLI_linklist_apply(LinkNode *list, LinkNodeApplyFP applyfunc, void *userdat
for (; list; list = list->next)
applyfunc(list->link, userdata);
}
/* -------------------------------------------------------------------- */
/* Sort */
#define SORT_IMPL_LINKTYPE LinkNode
#define SORT_IMPL_LINKTYPE_DATA link
/* regular call */
#define SORT_IMPL_FUNC linklist_sort_fn
#include "list_sort_impl.h"
#undef SORT_IMPL_FUNC
/* reentrant call */
#define SORT_IMPL_USE_THUNK
#define SORT_IMPL_FUNC linklist_sort_fn_r
#include "list_sort_impl.h"
#undef SORT_IMPL_FUNC
#undef SORT_IMPL_USE_THUNK
#undef SORT_IMPL_LINKTYPE
#undef SORT_IMPL_LINKTYPE_DATA
LinkNode *BLI_linklist_sort(LinkNode *list, int (*cmp)(const void *, const void *))
{
if (list && list->next) {
list = linklist_sort_fn(list, cmp);
}
return list;
}
LinkNode *BLI_linklist_sort_r(LinkNode *list, int (*cmp)(void *, const void *, const void *), void *thunk)
{
if (list && list->next) {
list = linklist_sort_fn_r(list, cmp, thunk);
}
return list;
}

View File

@@ -0,0 +1,341 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/blenlib/intern/list_sort_impl.h
* \ingroup bli
*
* Common implementation of linked-list a non-recursive mergesort.
*
* Originally from Mono's eglib, adapted for portable inclusion.
* This file is to be directly included in C-source,
* with defines to control its use.
*
* This code requires a typedef named `SORT_IMPL_LINKTYPE` for the list node.
* It is assumed that the list type is the type of a pointer to a list
* node, and that the node has a field named 'next' that implements to
* the linked list. No additional invariant is maintained
* (e.g. the `prev` pointer of a doubly-linked list node is _not_ updated).
* Any invariant would require a post-processing pass to update `prev`.
*
* Source file including this must define:
* - `SORT_IMPL_LINKTYPE`:
* Struct type for sorting.
* - `SORT_IMPL_LINKTYPE_DATA`:
* Data pointer or leave undefined to pass the link its self.
* - `SORT_IMPL_FUNC`:
* Function name of the sort function.
*
* Optionally:
* - `SORT_IMPL_USE_THUNK`:
* Takes an argument for the sort function (like `qsort_r`).
*/
/* -------------------------------------------------------------------- */
/* Handle External Defines */
/* check we're not building directly */
#if !defined(SORT_IMPL_LINKTYPE) || !defined(SORT_IMPL_FUNC)
# error "This file can't be compiled directly, include in another source file"
#endif
#define list_node SORT_IMPL_LINKTYPE
#define list_sort_do SORT_IMPL_FUNC
#ifdef SORT_IMPL_LINKTYPE_DATA
# define SORT_ARG(n) ((n)->SORT_IMPL_LINKTYPE_DATA)
#else
# define SORT_ARG(n) (n)
#endif
#ifdef SORT_IMPL_USE_THUNK
# define THUNK_APPEND1(a, thunk) a, thunk
# define THUNK_PREPEND2(thunk, a, b) thunk, a, b
#else
# define THUNK_APPEND1(a, thunk) a
# define THUNK_PREPEND2(thunk, a, b) a, b
#endif
#define _CONCAT_AUX(MACRO_ARG1, MACRO_ARG2) MACRO_ARG1 ## MACRO_ARG2
#define _CONCAT(MACRO_ARG1, MACRO_ARG2) _CONCAT_AUX(MACRO_ARG1, MACRO_ARG2)
#define _SORT_PREFIX(id) _CONCAT(SORT_IMPL_FUNC, _##id)
/* local identifiers */
#define SortInfo _SORT_PREFIX(SortInfo)
#define CompareFn _SORT_PREFIX(CompareFn)
#define init_sort_info _SORT_PREFIX(init_sort_info)
#define merge_lists _SORT_PREFIX(merge_lists)
#define sweep_up _SORT_PREFIX(sweep_up)
#define insert_list _SORT_PREFIX(insert_list)
typedef int (* CompareFn)(
#ifdef SORT_IMPL_USE_THUNK
void *thunk,
#endif
const void *,
const void *);
/* -------------------------------------------------------------------- */
/* MIT license from original source */
/*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Author:
* Raja R Harinath (rharinath@novell.com)
*/
/**
* The maximum possible depth of the merge tree
* - `ceiling(log2(maximum number of list nodes))`
* - `ceiling(log2(maximum possible memory size/size of each list node))`
* - number of bits in `'size_t' - floor(log2(sizeof(list_node *)))`
*
* Also, each list in #SortInfo is at least 2 nodes long:
* we can reduce the depth by 1.
*/
#define FLOOR_LOG2(x) \
(((x) >= 2) + ((x) >= 4) + ((x) >= 8) + ((x) >= 16) + ((x) >= 32) + ((x) >= 64) + ((x) >= 128))
#define MAX_RANKS \
((sizeof(size_t) * 8) - FLOOR_LOG2(sizeof(list_node)) - 1)
struct SortInfo {
unsigned int min_rank, n_ranks;
CompareFn func;
#ifdef SORT_IMPL_USE_THUNK
void *thunk;
#endif
/**
* Invariant: `ranks[i] == NULL || length(ranks[i]) >= 2**(i+1)`.
*
* ~ 128 bytes on 32bit, ~ 512 bytes on 64bit */
list_node *ranks[MAX_RANKS];
};
BLI_INLINE void init_sort_info(
struct SortInfo *si,
CompareFn func
#ifdef SORT_IMPL_USE_THUNK
,
void *thunk
#endif
)
{
si->min_rank = si->n_ranks = 0;
si->func = func;
/* we don't need to initialize si->ranks,
* since we never lookup past si->n_ranks. */
#ifdef SORT_IMPL_USE_THUNK
si->thunk = thunk;
#endif
}
BLI_INLINE list_node *merge_lists(
list_node *first, list_node *second,
CompareFn func
#ifdef SORT_IMPL_USE_THUNK
,
void *thunk
#endif
)
{
/* merge the two lists */
list_node *list = NULL;
list_node **pos = &list;
while (first && second) {
if (func(THUNK_PREPEND2(thunk, SORT_ARG(first), SORT_ARG(second))) > 0) {
*pos = second;
second = second->next;
}
else {
*pos = first;
first = first->next;
}
pos = &((*pos)->next);
}
*pos = first ? first : second;
return list;
}
/**
* Pre-condition:
* `upto <= si->n_ranks, list == NULL || length(list) == 1`
*/
BLI_INLINE list_node *sweep_up(struct SortInfo *si, list_node *list, unsigned int upto)
{
unsigned int i;
for (i = si->min_rank; i < upto; i++) {
list = merge_lists(si->ranks[i], list, THUNK_APPEND1(si->func, si->thunk));
si->ranks[i] = NULL;
}
return list;
}
/**
* The 'ranks' array essentially captures the recursion stack of a mergesort.
* The merge tree is built in a bottom-up manner. The control loop for
* updating the 'ranks' array is analogous to incrementing a binary integer,
* and the `O(n)` time for counting upto n translates to `O(n)` merges when
* inserting `rank-0` lists.
* When we plug in the sizes of the lists involved in those merges,
* we get the `O(n log n)` time for the sort.
*
* Inserting higher-ranked lists reduce the height of the merge tree,
* and also eliminate a lot of redundant comparisons when merging two lists
* that would've been part of the same run.
* Adding a `rank-i` list is analogous to incrementing a binary integer by
* `2**i` in one operation, thus sharing a similar speedup.
*
* When inserting higher-ranked lists, we choose to clear out the lower ranks
* in the interests of keeping the sort stable, but this makes analysis harder.
* Note that clearing the lower-ranked lists is `O(length(list))--` thus it
* shouldn't affect the `O(n log n)` behaviour.
* In other words, inserting one `rank-i` list is equivalent to inserting
* `2**i` `rank-0` lists, thus even if we do `i` additional merges
* in the clearing-out (taking at most `2**i` time) we are still fine.
*/
/**
* Pre-condition:
* `2**(rank+1) <= length(list) < 2**(rank+2)`
* (therefore: `length(list) >= 2`)
*/
BLI_INLINE void insert_list(
struct SortInfo *si,
list_node *list,
unsigned int rank)
{
unsigned int i;
if (rank > si->n_ranks) {
if (UNLIKELY(rank > MAX_RANKS)) {
// printf("Rank '%d' should not exceed " STRINGIFY(MAX_RANKS), rank);
rank = MAX_RANKS;
}
list = merge_lists(sweep_up(si, NULL, si->n_ranks), list, THUNK_APPEND1(si->func, si->thunk));
for (i = si->n_ranks; i < rank; ++i) {
si->ranks[i] = NULL;
}
}
else {
if (rank) {
list = merge_lists(sweep_up(si, NULL, rank), list, THUNK_APPEND1(si->func, si->thunk));
}
for (i = rank; i < si->n_ranks && si->ranks[i]; ++i) {
list = merge_lists(si->ranks[i], list, THUNK_APPEND1(si->func, si->thunk));
si->ranks[i] = NULL;
}
}
/* Will _never_ happen: so we can just devolve into quadratic ;-) */
if (UNLIKELY(i == MAX_RANKS)) {
i--;
}
if (i >= si->n_ranks) {
si->n_ranks = i + 1;
}
si->min_rank = i;
si->ranks[i] = list;
}
#undef MAX_RANKS
#undef FLOOR_LOG2
/**
* Main sort function.
*/
BLI_INLINE list_node *list_sort_do(
list_node *list,
CompareFn func
#ifdef SORT_IMPL_USE_THUNK
,
void *thunk
#endif
)
{
struct SortInfo si;
init_sort_info(
&si,
func
#ifdef SORT_IMPL_USE_THUNK
,
thunk
#endif
);
while (list && list->next) {
list_node *next = list->next;
list_node *tail = next->next;
if (func(THUNK_PREPEND2(thunk, SORT_ARG(list), SORT_ARG(next))) > 0) {
next->next = list;
next = list;
list = list->next;
}
next->next = NULL;
insert_list(&si, list, 0);
list = tail;
}
return sweep_up(&si, list, si.n_ranks);
}
#undef _CONCAT_AUX
#undef _CONCAT
#undef _SORT_PREFIX
#undef SortInfo
#undef CompareFn
#undef init_sort_info
#undef merge_lists
#undef sweep_up
#undef insert_list
#undef list_node
#undef list_sort_do
#undef THUNK_APPEND1
#undef THUNK_PREPEND2
#undef SORT_ARG

View File

@@ -42,6 +42,8 @@
#include "BLI_listbase.h"
#include "BLI_strict_flags.h"
/* implementation */
/**
@@ -205,6 +207,35 @@ void BLI_freelinkN(ListBase *listbase, void *vlink)
MEM_freeN(link);
}
/**
* Assigns all #Link.prev pointers from #Link.next
*/
static void listbase_double_from_single(Link *iter, ListBase *listbase)
{
Link *prev = NULL;
listbase->first = iter;
do {
iter->prev = prev;
prev = iter;
} while ((iter = iter->next));
listbase->last = prev;
}
#define SORT_IMPL_LINKTYPE Link
/* regular call */
#define SORT_IMPL_FUNC listbase_sort_fn
#include "list_sort_impl.h"
#undef SORT_IMPL_FUNC
/* reentrant call */
#define SORT_IMPL_USE_THUNK
#define SORT_IMPL_FUNC listbase_sort_fn_r
#include "list_sort_impl.h"
#undef SORT_IMPL_FUNC
#undef SORT_IMPL_USE_THUNK
#undef SORT_IMPL_LINKTYPE
/**
* Sorts the elements of listbase into the order defined by cmp
@@ -213,45 +244,19 @@ void BLI_freelinkN(ListBase *listbase, void *vlink)
*/
void BLI_listbase_sort(ListBase *listbase, int (*cmp)(const void *, const void *))
{
Link *current = NULL;
Link *previous = NULL;
Link *next = NULL;
if (listbase->first != listbase->last) {
for (previous = listbase->first, current = previous->next; current; current = next) {
next = current->next;
previous = current->prev;
BLI_remlink(listbase, current);
while (previous && cmp(previous, current) == 1) {
previous = previous->prev;
}
BLI_insertlinkafter(listbase, previous, current);
}
Link *head = listbase->first;
head = listbase_sort_fn(head, cmp);
listbase_double_from_single(head, listbase);
}
}
void BLI_listbase_sort_r(ListBase *listbase, int (*cmp)(void *, const void *, const void *), void *thunk)
{
Link *current = NULL;
Link *previous = NULL;
Link *next = NULL;
if (listbase->first != listbase->last) {
for (previous = listbase->first, current = previous->next; current; current = next) {
next = current->next;
previous = current->prev;
BLI_remlink(listbase, current);
while (previous && cmp(thunk, previous, current) == 1) {
previous = previous->prev;
}
BLI_insertlinkafter(listbase, previous, current);
}
Link *head = listbase->first;
head = listbase_sort_fn_r(head, cmp, thunk);
listbase_double_from_single(head, listbase);
}
}