【问题标题】:A Quickselect C Algorithm faster than C Qsort比 C Qsort 更快的 Quickselect C 算法
【发布时间】:2018-08-26 00:48:45
【问题描述】:

我已尝试按照本文 (3 way quicksort (C implementation)) 中的描述实现 C 快速选择算法。 但是,我得到的只是性能比默认的 qsort 低 5 到 10 倍(即使是初始改组)。 我试图挖掘此处提供的原始 qsort 源代码 (https://github.com/lattera/glibc/blob/master/stdlib/qsort.c),但它太复杂了。 有人有更简单,更好的算法吗? 欢迎任何想法。 谢谢, 注意:我最初的问题是尝试将数组的第 K 个最小值获取到第一个 Kth 索引。所以我打算调用quickselect K次 编辑 1:这是从上面的链接复制和改编的 Cython 代码

cdef void qswap(void* a, void* b, const size_t size) nogil:
    cdef char temp[size]# C99, use malloc otherwise
    #char serves as the type for "generic" byte arrays

    memcpy(temp, b,    size)
    memcpy(b,    a,    size)
    memcpy(a,    temp, size)

cdef void qshuffle(void* base, size_t num, size_t size) nogil: #implementation of Fisher
    cdef int i, j, tmp# create local variables to hold values for shuffle

    for i in range(num - 1, 0, -1): # for loop to shuffle
        j = c_rand() % (i + 1)#randomise j for shuffle with Fisher Yates
        qswap(base + i*size, base + j*size, size)

cdef void partition3(void* base,
                      size_t *low, size_t *high, size_t size,
                      QComparator compar) nogil:       
    # Modified median-of-three and pivot selection.                      
    cdef void *ptr = base
    cdef size_t lt = low[0]
    cdef size_t gt = high[0] # lt is the pivot
    cdef size_t i = lt + 1# (+1 !) we don't compare pivot with itself
    cdef int c = 0

    while (i <= gt):
        c = compar(ptr + i * size, ptr + lt * size)
        if (c < 0):# base[i] < base[lt] => swap(i++,lt++)
            qswap(ptr + lt * size, ptr + i * size, size)
            i += 1
            lt += 1
        elif (c > 0):#base[i] > base[gt] => swap(i, gt--)
            qswap(ptr + i * size, ptr + gt* size, size)
            gt -= 1
        else:#base[i] == base[gt]
            i += 1
    #base := [<<<<<lt=====gt>>>>>>]
    low[0] = lt                                          
    high[0] = gt 


cdef void qselectk3(void* base, size_t lo, size_t hi, 
   size_t size, size_t k, 
   QComparator compar) nogil:                                             
    cdef size_t low = lo                                          
    cdef size_t high = hi                                                      

    partition3(base, &low, &high,  size, compar)    

    if ((k - 1) < low): #k lies in the less-than-pivot partition           
        high = low - 1
        low = lo                      
    elif ((k - 1) >= low and  (k - 1) <= high): #k lies in the equals-to-pivot partition
        qswap(base, base + size*low, size)
        return                              
    else: # k > high => k lies in the greater-than-pivot partition                    
        low = high + 1
        high = hi 
    qselectk3(base, low, high, size, k, compar)

"""
 A selection algorithm to find the nth smallest elements in an unordered list. 
 these elements ARE placed at the nth positions of the input array                                                                         
"""
cdef void qselect(void* base, size_t num, size_t size,
                              size_t n,
                              QComparator compar) nogil:
    cdef int k
    qshuffle(base, num, size)
    for k in range(n):
        qselectk3(base + size*k, 0, num - k - 1, size, 1, compar)

我使用 python timeit 来获取方法 pyselect(with N=50) 和 pysort 的性能。 像这样

def testPySelect():
    A = np.random.randint(16, size=(10000), dtype=np.int32)
    pyselect(A, 50)
timeit.timeit(testPySelect, number=1)

def testPySort():
    A = np.random.randint(16, size=(10000), dtype=np.int32)
    pysort(A)
timeit.timeit(testPySort, number=1)

【问题讨论】:

  • 您需要展示您尝试过的内容,以及您是如何编译它的(编译时没有优化是导致性能问题的第二大常见原因)。
  • 我已经习惯了链接中提供的代码。对于编译,我在 Mac OS X Lion 上使用 GCC 5。
  • 但是在编译过程中使用了哪些标志/选项?
  • 我在项目中也使用了openmp。所以除了numpy,包括,只有openmp编译和链接选项(-fopenmp)。
  • 没有优化的编译是导致性能问题的第二大常见原因

标签: algorithm cython quicksort quickselect


【解决方案1】:

@chqrlie 的答案是好的和最终的答案,但为了完成这篇文章,我将发布 Cython 版本以及基准测试结果。 简而言之,提出的解决方案比长向量上的 qsort 快 2 倍!

cdef void qswap2(void *aptr, void *bptr, size_t size) nogil: cdef uint8_t* ac = aptr cdef uint8_t* bc = bptr cdef uint8_t t 而(大小> 0):t = ac [0]; ac[0] = bc[0]; bc[0] = t; ac += 1; bc += 1;大小 -= 1 cdef 结构 qselect2_stack: uint8_t *base uint8_t *最后 cdef void qselect2(void *base, size_t nmemb, size_t size, size_t k,QComparator 比较)诺吉尔: cdef qselect2_stack 堆栈[64] cdef qselect2_stack *sp = &stack[0] cdef uint8_t *lb cdef uint8_t*ub cdef uint8_t *p cdef uint8_t *i cdef uint8_t *j cdef uint8_t *顶部 如果(nmemb 基础 如果(k base sp.last = base + (nmemb - 1) * 大小 sp += 1 cdef size_t 偏移量 而(sp>堆栈): sp -= 1 lb = sp.base ub = sp.last 而(lb > 1 p = lb + offset - offset % size qswap2(磅,p,尺寸) #分成两段 我 = 磅 + 大小 j = ub 而1: 而(i 0): 我 += 大小 而 (j >= i 和比较 (j, lb) > 0): j -= 大小 如果 (i >= j): 休息 qswap2(i, j, 大小) 我 += 大小 j -= 大小 # 将枢轴移动到它所属的位置 qswap2(磅,j,尺寸) # 继续处理最小的段,最大的栈 如果(j - 磅 cdef int int_comp(void* a, void* b) nogil: cdef int ai = (a)[0] cdef int bi = (b)[0] 返回 (ai > bi ) - (ai &na[0] qselect2(a, len(na), sizeof(int), n, int_comp)

以下是基准测试结果(1,000 次测试):

#of 个元素 K #qsort (s) #qselect2 (s) 1,000 50 0.1261 0.0895 1,000 100 0.1261 0.0910 10,000 50 0.8113 0.4157 10,000 100 0.8113 0.4367 10,000 1,000 0.8113 0.4746 100,000 100 7.5428 3.8259 100,000 1,000 7,5428 3.8325 100,000 10,000 7,5428 4.5727

对于那些好奇的人来说,这段代码是使用神经网络进行表面重建领域的一颗明珠。 再次感谢@chqrlie,您的代码在网络上是独一无二的。

【讨论】:

    【解决方案2】:

    这里有一个快速实现:qsort_selectqsort 的简单实现,自动修剪不必要的范围。

    没有&amp;&amp; lb &lt; top,它的行为就像普通的qsort,除了更高级版本具有更好启发式的病理情况。此额外测试可防止对目标 0 .. (k-1) 之外的范围进行完全排序。该函数选择k 的最小值并对其进行排序,数组的其余部分具有不定顺序的剩余值。

    #include <stdio.h>
    #include <stdint.h>
    
    static void exchange_bytes(uint8_t *ac, uint8_t *bc, size_t size) {
        while (size-- > 0) { uint8_t t = *ac; *ac++ = *bc; *bc++ = t; }
    }
    
    /* select and sort the k smallest elements from an array */
    void qsort_select(void *base, size_t nmemb, size_t size,
                      int (*compar)(const void *a, const void *b), size_t k)
    {
        struct { uint8_t *base, *last; } stack[64], *sp = stack;
        uint8_t *lb, *ub, *p, *i, *j, *top;
    
        if (nmemb < 2 || size <= 0)
            return;
    
        top = (uint8_t *)base + (k < nmemb ? k : nmemb) * size;
        sp->base = (uint8_t *)base;
        sp->last = (uint8_t *)base + (nmemb - 1) * size;
        sp++;
        while (sp > stack) {
            --sp;
            lb = sp->base;
            ub = sp->last;
            while (lb < ub && lb < top) {
                /* select middle element as pivot and exchange with 1st element */
                size_t offset = (ub - lb) >> 1;
                p = lb + offset - offset % size;
                exchange_bytes(lb, p, size);
    
                /* partition into two segments */
                for (i = lb + size, j = ub;; i += size, j -= size) {
                    while (i < j && compar(lb, i) > 0)
                        i += size;
                    while (j >= i && compar(j, lb) > 0)
                        j -= size;
                    if (i >= j)
                        break;
                    exchange_bytes(i, j, size);
                }
                /* move pivot where it belongs */
                exchange_bytes(lb, j, size);
    
                /* keep processing smallest segment, and stack largest */
                if (j - lb <= ub - j) {
                    sp->base = j + size;
                    sp->last = ub;
                    sp++;
                    ub = j - size;
                } else {
                    sp->base = lb;
                    sp->last = j - size;
                    sp++;
                    lb = j + size;
                }
            }
        }
    }
    
    int int_cmp(const void *a, const void *b) {
        int aa = *(const int *)a;
        int bb = *(const int *)b;
        return (aa > bb) - (aa < bb);
    }
    
    #define ARRAY_SIZE  50000
    
    int array[ARRAY_SIZE];
    
    int main(void) {
        int i;
        for (i = 0; i < ARRAY_SIZE; i++) {
            array[i] = ARRAY_SIZE - i;
        }
        qsort_select(array, ARRAY_SIZE, sizeof(*array), int_cmp, 50);
        for (i = 0; i < 50; i++) {
            printf("%d%c", array[i], i + 1 == 50 ? '\n' : ',');
        }
        return 0;
    }
    

    【讨论】:

    • @N.Wells:这是qsort 的简单实现,自动修剪不必要的范围:没有` && lb , it behaves like the regular qsort` 除了更高级版本更好的病理情况启发式。额外的测试会阻止对目标 0 .. (k-1) 之外的范围进行完全排序。该函数选择k 的最小值并对它们进行排序,数组的其余部分具有不确定顺序的剩余值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-30
    • 1970-01-01
    • 1970-01-01
    • 2010-12-26
    • 1970-01-01
    • 2012-09-05
    相关资源
    最近更新 更多