PostgreSQL的学习心得和知识总结(一百五十四)|高级数据结构和算法之快速排序算法quicksort的实现及使用

news2024/10/12 22:17:25

注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下:

1、参考书籍:《PostgreSQL数据库内核分析》
2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》
3、PostgreSQL数据库仓库链接,点击前往
4、日本著名PostgreSQL数据库专家 铃木启修 网站主页,点击前往
5、参考书籍:《PostgreSQL中文手册》
6、参考书籍:《PostgreSQL指南:内幕探索》,点击前往
7、参考书籍:《事务处理 概念与技术》


1、本文内容全部来源于开源社区 GitHub和以上博主的贡献,本文也免费开源(可能会存在问题,评论区等待大佬们的指正)
2、本文目的:开源共享 抛砖引玉 一起学习
3、本文不提供任何资源 不存在任何交易 与任何组织和机构无关
4、大家可以根据需要自行 复制粘贴以及作为其他个人用途,但是不允许转载 不允许商用 (写作不易,还请见谅 💖)
5、本文内容基于PostgreSQL master源码开发而成


高级数据结构和算法之快速排序算法quicksort的实现及使用

  • 文章快速说明索引
  • 快速排序内部使用
  • 快速排序内部实现



文章快速说明索引

学习目标:

做数据库内核开发久了就会有一种 少年得志,年少轻狂 的错觉,然鹅细细一品觉得自己其实不算特别优秀 远远没有达到自己想要的。也许光鲜的表面掩盖了空洞的内在,每每想到于此,皆有夜半临渊如履薄冰之感。为了睡上几个踏实觉,即日起 暂缓其他基于PostgreSQL数据库的兼容功能开发,近段时间 将着重于学习分享Postgres的基础知识和实践内幕。


学习内容:(详见目录)

1、高级数据结构和算法之快速排序算法quicksort的实现及使用


学习时间:

2024年10月12日 21:01:55


学习产出:

1、PostgreSQL数据库基础知识回顾 1个
2、CSDN 技术博客 1篇
3、PostgreSQL数据库内核深入学习


注:下面我们所有的学习环境是Centos8+PostgreSQL master +Oracle19C+MySQL8.0

postgres=# select version();
                                                  version                                                   
------------------------------------------------------------------------------------------------------------
 PostgreSQL 18devel on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-21), 64-bit
(1 row)

postgres=#

#-----------------------------------------------------------------------------#

SQL> select * from v$version;          

BANNER        Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production	
BANNER_FULL	  Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production Version 19.17.0.0.0	
BANNER_LEGACY Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production	
CON_ID 0


#-----------------------------------------------------------------------------#

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.27    |
+-----------+
1 row in set (0.06 sec)

mysql>

快速排序内部使用

不知道大家在日常的学习和工作中,有没有注意到PostgreSQL在内部实际上是使用了一些高级数据结构和算法?例如下面这个简单的执行计划:

postgres=# SET enable_hashjoin = 'off';
SET
postgres=# explain (verbose, costs off, analyze) SELECT * FROM norm_test WHERE x IN (VALUES (1), (29));
                                         QUERY PLAN                                          
---------------------------------------------------------------------------------------------
 Nested Loop (actual time=0.184..0.309 rows=97 loops=1)
   Output: norm_test.x, norm_test.payload
   ->  Unique (actual time=0.010..0.014 rows=2 loops=1)
         Output: "*VALUES*".column1
         ->  Sort (actual time=0.009..0.010 rows=2 loops=1)
               Output: "*VALUES*".column1
               Sort Key: "*VALUES*".column1
               Sort Method: quicksort  Memory: 25kB
               ->  Values Scan on "*VALUES*" (actual time=0.002..0.003 rows=2 loops=1)
                     Output: "*VALUES*".column1
   ->  Bitmap Heap Scan on public.norm_test (actual time=0.089..0.135 rows=48 loops=2)
         Output: norm_test.x, norm_test.payload
         Recheck Cond: (norm_test.x = "*VALUES*".column1)
         Heap Blocks: exact=10
         ->  Bitmap Index Scan on norm_test_x_idx (actual time=0.061..0.061 rows=48 loops=2)
               Index Cond: (norm_test.x = "*VALUES*".column1)
 Planning Time: 0.442 ms
 Execution Time: 0.373 ms
(18 rows)

postgres=#

如上,中间出现Sort Method: quicksort这样的字眼,那么数据库内部是怎么实现以及使用这个快速排序的呢?当时上学的时候,没有少手撕代码,死去的回忆突然间开始攻击我!有兴趣的小伙伴可以看一下本人之前(long long time ago)的博客:

  • DSA之十大排序算法第六种:Quick Sort,点击前往

简单看了一眼内核代码,该算法是快速排序的变种算法,该算法是改编至J. L. Bentley and M. D. McIlroy在专刊Software–Practice and Experience发表的名为Engineering a sort function论文,如下:

// src/include/lib/sort_template.h

/*
 * Qsort routine based on J. L. Bentley and M. D. McIlroy,
 * "Engineering a sort function",
 * Software--Practice and Experience 23 (1993) 1249-1265.
 *
 * We have modified their original by adding a check for already-sorted
 * input, which seems to be a win per discussions on pgsql-hackers around
 * 2006-03-21.
 * 我们修改了原来的版本,增加了对已排序输入的检查,
 * 根据 2006-03-21 左右 pgsql-hackers 上的讨论,这似乎是一个胜利。
 *
 * Also, we recurse on the smaller partition and iterate on the larger one,
 * which ensures we cannot recurse more than log(N) levels (since the
 * partition recursed to is surely no more than half of the input).  Bentley
 * and McIlroy explicitly rejected doing this on the grounds that it's "not
 * worth the effort", but we have seen crashes in the field due to stack
 * overrun, so that judgment seems wrong.
 * 此外,我们在较小的分区上进行递归,在较大的分区上进行迭代,
 * 这确保了我们不能递归超过 log(N) 级(因为递归到的分区肯定不超过输入的一半)。
 * 
 * Bentley 和 McIlroy 明确拒绝这样做,理由是“不值得付出努力”,
 * 但我们在现场看到过由于堆栈溢出而导致的崩溃,因此这种判断似乎是错误的。
 */

在PostgreSQL内核中直接调用非常简单,本来想的是直接调试一个现有的逻辑 但是复杂的调试过程不便于理解学习的重点。我随便写了一个简单的插件,因为比较简单 而且我很懒,就不再仓库上传,这里直接贴源码 如下:

[postgres@localhost:~/postgres/contrib/test_use_qsort → master]$ git branch 
  REL_17_0
* master
[postgres@localhost:~/postgres/contrib/test_use_qsort → master]$ ls
Makefile  meson.build  test_use_qsort--1.0.sql  test_use_qsort.c  test_use_qsort.control  test_use_qsort.o  test_use_qsort.so
[postgres@localhost:~/postgres/contrib/test_use_qsort → master]$ 
[postgres@localhost:~/postgres/contrib/test_use_qsort → master]$ make clean
rm -f test_use_qsort.so test_use_qsort.o  \
    test_use_qsort.bc
[postgres@localhost:~/postgres/contrib/test_use_qsort → master]$ 
[postgres@localhost:~/postgres/contrib/test_use_qsort → master]$ pwd
/home/postgres/postgres/contrib/test_use_qsort
[postgres@localhost:~/postgres/contrib/test_use_qsort → master]$
/* -------------------------------------------------------------------------
 *
 * test_use_qsort.c
 *
 * Copyright (c) 2010-2024, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *		contrib/test_use_qsort/test_use_qsort.c
 *
 * -------------------------------------------------------------------------
 */
#include "postgres.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/hsearch.h"

#include <stdlib.h>
#include <time.h>

PG_FUNCTION_INFO_V1(test_internal_qsort);

PG_MODULE_MAGIC;

typedef struct Student
{
	int class_id;
	int student_id;
	char *name;
	bool gender;
} Student;

static inline int student_comparator(const Student *a, const Student *b);

#define ST_SORT sort_student
#define ST_ELEMENT_TYPE Student
#define ST_COMPARE(a, b) student_comparator(a, b)
#define ST_SCOPE static
#define ST_DEFINE
#include <lib/sort_template.h>

typedef struct
{
	int class_id;
	int student_id;
} StudentKey;

typedef struct StudentEntry
{
	StudentKey key;
	bool active;
} StudentEntry;

static inline int
student_comparator(const Student *a, const Student *b)
{
	/* compare student */
	if (a->class_id < b->class_id)
		return -1;
	else if (a->class_id > b->class_id)
		return 1;

	if (a->student_id < b->student_id)
		return -1;
	else if (a->student_id > b->student_id)
		return 1;

	return 0;
}

void
_PG_init(void)
{
	/* other plugins can perform things here */
}

Datum test_internal_qsort(PG_FUNCTION_ARGS)
{
	int		max_class = 3,
			max_student = 20;

	Student	*students = NULL;
	HASHCTL	hashCtl;
	HTAB	*student_hash = NULL;

	int stu_num = PG_GETARG_INT32(0);

	if(stu_num <= 0 || stu_num > max_class * max_student)
	{
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid num")));
	}

	students = (Student *)palloc0(sizeof(Student) * stu_num);

	hashCtl.keysize = sizeof(StudentKey);
	hashCtl.entrysize = sizeof(StudentEntry);

	student_hash = hash_create("student hash",
								stu_num,
								&hashCtl,
								HASH_ELEM | HASH_BLOBS);

	srand((unsigned)time(NULL));

	for(int i = 0; i < stu_num; ++i)
	{
		bool found = false;
		StudentKey key = {0, 0};
		StudentEntry *entry = NULL;
		char student_name[24] = { 0 };

retry:
		key.class_id = rand() % max_class + 1;
		key.student_id = rand() % max_student + 1;

		hash_search(student_hash, &key, HASH_FIND, &found);

		if(found)
		{
			goto retry;
		}
		else
		{
			entry = hash_search(student_hash, &key, HASH_ENTER, NULL);
			entry->active = true;
		}

		students[i].class_id = key.class_id;
		students[i].student_id = key.student_id;
#define STUDENT_NAME_PREFIX "songbaobao"
		sprintf(student_name, "%s_%d_%d", STUDENT_NAME_PREFIX, students[i].class_id, students[i].student_id);
		students[i].name = pstrdup(student_name);
		students[i].gender = (rand() % 1) == 0 ? false : true;
	}

	for(int i = 0; i < stu_num; ++i)
	{
		ereport(NOTICE,
				(errmsg("Student %2d class id: %d, student id: %2d, student name: %s",
						i + 1, students[i].class_id, students[i].student_id, students[i].name)));
	}

	ereport(NOTICE,
				(errmsg("Sorting, please wait...")));

	sort_student(students, stu_num);

	ereport(NOTICE,
				(errmsg("Sorting End.")));

	for(int i = 0; i < stu_num; ++i)
	{
		ereport(NOTICE,
				(errmsg("Student %2d class id: %d, student id: %2d, student name: %s",
						i + 1, students[i].class_id, students[i].student_id, students[i].name)));
	}

	for(int i = 0; i < stu_num; ++i)
	{
		pfree(students[i].name);
	}
	pfree(students);
	hash_destroy(student_hash);

	PG_RETURN_BOOL(true);
}
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION test_use_qsort" to load this file. \quit

CREATE FUNCTION test_internal_qsort(studentNum int)
RETURNS bool
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

在这里插入图片描述

编译安装之后,简单测试如下:

在这里插入图片描述

postgres=# select test_internal_qsort(10);
NOTICE:  Student  1 class id: 1, student id:  8, student name: songbaobao_1_8
NOTICE:  Student  2 class id: 3, student id:  3, student name: songbaobao_3_3
NOTICE:  Student  3 class id: 3, student id: 12, student name: songbaobao_3_12
NOTICE:  Student  4 class id: 1, student id: 17, student name: songbaobao_1_17
NOTICE:  Student  5 class id: 3, student id: 18, student name: songbaobao_3_18
NOTICE:  Student  6 class id: 1, student id:  4, student name: songbaobao_1_4
NOTICE:  Student  7 class id: 3, student id: 14, student name: songbaobao_3_14
NOTICE:  Student  8 class id: 2, student id:  5, student name: songbaobao_2_5
NOTICE:  Student  9 class id: 2, student id:  3, student name: songbaobao_2_3
NOTICE:  Student 10 class id: 2, student id:  4, student name: songbaobao_2_4
NOTICE:  Sorting, please wait...
NOTICE:  Sorting End.
NOTICE:  Student  1 class id: 1, student id:  4, student name: songbaobao_1_4
NOTICE:  Student  2 class id: 1, student id:  8, student name: songbaobao_1_8
NOTICE:  Student  3 class id: 1, student id: 17, student name: songbaobao_1_17
NOTICE:  Student  4 class id: 2, student id:  3, student name: songbaobao_2_3
NOTICE:  Student  5 class id: 2, student id:  4, student name: songbaobao_2_4
NOTICE:  Student  6 class id: 2, student id:  5, student name: songbaobao_2_5
NOTICE:  Student  7 class id: 3, student id:  3, student name: songbaobao_3_3
NOTICE:  Student  8 class id: 3, student id: 12, student name: songbaobao_3_12
NOTICE:  Student  9 class id: 3, student id: 14, student name: songbaobao_3_14
NOTICE:  Student 10 class id: 3, student id: 18, student name: songbaobao_3_18
 test_internal_qsort 
---------------------
 t
(1 row)

postgres=#

如上函数非常简单,这里不再赘述。核心代码,如下:

...
#define ST_SORT sort_student
#define ST_ELEMENT_TYPE Student
#define ST_COMPARE(a, b) student_comparator(a, b)
#define ST_SCOPE static
#define ST_DEFINE
#include <lib/sort_template.h>
...

快速排序内部实现

接下来,我们就看一下数据库内部算法的核心实现,如下:

// src/include/lib/sort_template.h

 *
 * Usage notes:
 *
 *	  To generate functions specialized for a type, the following parameter
 *	  macros should be #define'd before this file is included.
 *	  要生成专门针对某种类型的函数,应在包含此文件之前#define 以下参数宏。
 *
 *	  - ST_SORT - the name of a sort function to be generated 要生成的排序函数的名称
 *	  - ST_ELEMENT_TYPE - type of the referenced elements 引用元素的类型
 *	  - ST_DECLARE - if defined the functions and types are declared 如果定义了函数和类型则声明
 *	  - ST_DEFINE - if defined the functions and types are defined 如果已定义,则定义了函数和类型
 *	  - ST_SCOPE - scope (e.g. extern, static inline) for functions
 *	  - ST_CHECK_FOR_INTERRUPTS - if defined the sort is interruptible 如果已定义,则排序是可中断的
 *
 *	  Instead of ST_ELEMENT_TYPE, ST_ELEMENT_TYPE_VOID can be defined.  Then
 *	  the generated functions will automatically gain an "element_size"
 *	  parameter.  This allows us to generate a traditional qsort function.
 *	  可以定义 ST_ELEMENT_TYPE_VOID 来代替 ST_ELEMENT_TYPE。
 *	  然后生成的函数将自动获得“element_size”参数。
 *	  这使我们能够生成传统的 qsort 函数。
 *
 *	  One of the following macros must be defined, to show how to compare
 *	  elements.  The first two options are arbitrary expressions depending
 *	  on whether an extra pass-through argument is desired, and the third
 *	  option should be defined if the sort function should receive a
 *	  function pointer at runtime.
 *	  必须定义以下宏之一,以显示如何比较元素。
 *	  前两个选项是任意表达式,具体取决于是否需要额外的传递参数,如果排序函数应在运行时接收函数指针,则应定义第三个选项。
 *
 *	  - ST_COMPARE(a, b) - a simple comparison expression
 *	  - ST_COMPARE(a, b, arg) - variant that takes an extra argument 接受额外参数的变体
 *	  - ST_COMPARE_RUNTIME_POINTER - sort function takes a function pointer 排序函数接受一个函数指针
 *
 *	  NB: If the comparator function is inlined, some compilers may produce
 *	  worse code with the optimized comparison routines in common/int.h than
 *	  with code with the following form:
 *
 *	      if (a < b)
 *	          return -1;
 *	      if (a > b)
 *	          return 1;
 *	      return 0;
 *
 *	  To say that the comparator and therefore also sort function should
 *	  receive an extra pass-through argument, specify the type of the
 *	  argument.
 *	  要说明比较器以及排序函数应该接收额外的传递参数,请指定参数的类型。
 *
 *	  - ST_COMPARE_ARG_TYPE - type of extra argument
 *
 *	  The prototype of the generated sort function is:
 *
 *	  void ST_SORT(ST_ELEMENT_TYPE *data, size_t n,
 *				   [size_t element_size,]
 *				   [ST_SORT_compare_function compare,]
 *				   [ST_COMPARE_ARG_TYPE *arg]);
 *
 *	  ST_SORT_compare_function is a function pointer of the following type:
 *
 *	  int (*)(const ST_ELEMENT_TYPE *a, const ST_ELEMENT_TYPE *b,
 *			  [ST_COMPARE_ARG_TYPE *arg])
 *

如上 宏的设置根据自己的需要而定,就像数据库中其他使用的那样:

// src/backend/utils/sort/tuplesort.c

#define ST_SORT qsort_tuple_unsigned
#define ST_ELEMENT_TYPE SortTuple
#define ST_COMPARE(a, b, state) qsort_tuple_unsigned_compare(a, b, state)
#define ST_COMPARE_ARG_TYPE Tuplesortstate
#define ST_CHECK_FOR_INTERRUPTS
#define ST_SCOPE static
#define ST_DEFINE
#include "lib/sort_template.h"

#if SIZEOF_DATUM >= 8
#define ST_SORT qsort_tuple_signed
#define ST_ELEMENT_TYPE SortTuple
#define ST_COMPARE(a, b, state) qsort_tuple_signed_compare(a, b, state)
#define ST_COMPARE_ARG_TYPE Tuplesortstate
#define ST_CHECK_FOR_INTERRUPTS
#define ST_SCOPE static
#define ST_DEFINE
#include "lib/sort_template.h"
#endif

#define ST_SORT qsort_tuple_int32
#define ST_ELEMENT_TYPE SortTuple
#define ST_COMPARE(a, b, state) qsort_tuple_int32_compare(a, b, state)
#define ST_COMPARE_ARG_TYPE Tuplesortstate
#define ST_CHECK_FOR_INTERRUPTS
#define ST_SCOPE static
#define ST_DEFINE
#include "lib/sort_template.h"

#define ST_SORT qsort_tuple
#define ST_ELEMENT_TYPE SortTuple
#define ST_COMPARE_RUNTIME_POINTER
#define ST_COMPARE_ARG_TYPE Tuplesortstate
#define ST_CHECK_FOR_INTERRUPTS
#define ST_SCOPE static
#define ST_DECLARE
#define ST_DEFINE
#include "lib/sort_template.h"

#define ST_SORT qsort_ssup
#define ST_ELEMENT_TYPE SortTuple
#define ST_COMPARE(a, b, ssup) \
	ApplySortComparator((a)->datum1, (a)->isnull1, \
						(b)->datum1, (b)->isnull1, (ssup))
#define ST_COMPARE_ARG_TYPE SortSupportData
#define ST_CHECK_FOR_INTERRUPTS
#define ST_SCOPE static
#define ST_DEFINE
#include "lib/sort_template.h"

接下来,看一下今天的重中之重ST_SORT函数,如下:

/*
 * Sort an array.
 */
ST_SCOPE void
ST_SORT(ST_ELEMENT_TYPE * data, size_t n
		ST_SORT_PROTO_ELEMENT_SIZE
		ST_SORT_PROTO_COMPARE
		ST_SORT_PROTO_ARG)
{
	ST_POINTER_TYPE *a = (ST_POINTER_TYPE *) data,
			   *pa,
			   *pb,
			   *pc,
			   *pd,
			   *pl,
			   *pm,
			   *pn;
	size_t		d1,
				d2;
	int			r,
				presorted;

loop:
	DO_CHECK_FOR_INTERRUPTS();
	if (n < 7)
	{
		for (pm = a + ST_POINTER_STEP; pm < a + n * ST_POINTER_STEP;
			 pm += ST_POINTER_STEP)
			for (pl = pm; pl > a && DO_COMPARE(pl - ST_POINTER_STEP, pl) > 0;
				 pl -= ST_POINTER_STEP)
				DO_SWAP(pl, pl - ST_POINTER_STEP);
		return;
	}
	presorted = 1;
	for (pm = a + ST_POINTER_STEP; pm < a + n * ST_POINTER_STEP;
		 pm += ST_POINTER_STEP)
	{
		DO_CHECK_FOR_INTERRUPTS();
		if (DO_COMPARE(pm - ST_POINTER_STEP, pm) > 0)
		{
			presorted = 0;
			break;
		}
	}
	if (presorted)
		return;
	pm = a + (n / 2) * ST_POINTER_STEP;
	if (n > 7)
	{
		pl = a;
		pn = a + (n - 1) * ST_POINTER_STEP;
		if (n > 40)
		{
			size_t		d = (n / 8) * ST_POINTER_STEP;

			pl = DO_MED3(pl, pl + d, pl + 2 * d);
			pm = DO_MED3(pm - d, pm, pm + d);
			pn = DO_MED3(pn - 2 * d, pn - d, pn);
		}
		pm = DO_MED3(pl, pm, pn);
	}
	DO_SWAP(a, pm);
	pa = pb = a + ST_POINTER_STEP;
	pc = pd = a + (n - 1) * ST_POINTER_STEP;
	for (;;)
	{
		while (pb <= pc && (r = DO_COMPARE(pb, a)) <= 0)
		{
			if (r == 0)
			{
				DO_SWAP(pa, pb);
				pa += ST_POINTER_STEP;
			}
			pb += ST_POINTER_STEP;
			DO_CHECK_FOR_INTERRUPTS();
		}
		while (pb <= pc && (r = DO_COMPARE(pc, a)) >= 0)
		{
			if (r == 0)
			{
				DO_SWAP(pc, pd);
				pd -= ST_POINTER_STEP;
			}
			pc -= ST_POINTER_STEP;
			DO_CHECK_FOR_INTERRUPTS();
		}
		if (pb > pc)
			break;
		DO_SWAP(pb, pc);
		pb += ST_POINTER_STEP;
		pc -= ST_POINTER_STEP;
	}
	pn = a + n * ST_POINTER_STEP;
	d1 = Min(pa - a, pb - pa);
	DO_SWAPN(a, pb - d1, d1);
	d1 = Min(pd - pc, pn - pd - ST_POINTER_STEP);
	DO_SWAPN(pb, pn - d1, d1);
	d1 = pb - pa;
	d2 = pd - pc;
	if (d1 <= d2)
	{
		/* Recurse on left partition, then iterate on right partition */
		if (d1 > ST_POINTER_STEP)
			DO_SORT(a, d1 / ST_POINTER_STEP);
		if (d2 > ST_POINTER_STEP)
		{
			/* Iterate rather than recurse to save stack space */
			/* DO_SORT(pn - d2, d2 / ST_POINTER_STEP) */
			a = pn - d2;
			n = d2 / ST_POINTER_STEP;
			goto loop;
		}
	}
	else
	{
		/* Recurse on right partition, then iterate on left partition */
		if (d2 > ST_POINTER_STEP)
			DO_SORT(pn - d2, d2 / ST_POINTER_STEP);
		if (d1 > ST_POINTER_STEP)
		{
			/* Iterate rather than recurse to save stack space */
			/* DO_SORT(a, d1 / ST_POINTER_STEP) */
			n = d1 / ST_POINTER_STEP;
			goto loop;
		}
	}
}

第一部分:小数组的处理:对于小于7个元素的数组,函数使用插入排序,这是一种时间复杂度为O(n²)的简单算法,但在小数据集上的表现优于快速排序。这通过以下代码实现:

	if (n < 7)
	{
		for (pm = a + ST_POINTER_STEP; pm < a + n * ST_POINTER_STEP;
			 pm += ST_POINTER_STEP)
			for (pl = pm; pl > a && DO_COMPARE(pl - ST_POINTER_STEP, pl) > 0;
				 pl -= ST_POINTER_STEP)
				DO_SWAP(pl, pl - ST_POINTER_STEP);
		return;
	}

第二部分:验证是否已经排过序,已然有序就可以直接返回了:

	presorted = 1;
	for (pm = a + ST_POINTER_STEP; pm < a + n * ST_POINTER_STEP;
		 pm += ST_POINTER_STEP)
	{
		DO_CHECK_FOR_INTERRUPTS();
		if (DO_COMPARE(pm - ST_POINTER_STEP, pm) > 0)
		{
			presorted = 0;
			break;
		}
	}
	if (presorted)
		return;

第三部分:array size大于7,使用快排:首先求取基准数,尤其是当数组大小超过40,为了更好取样,将划分8个为一组,求取median:

	pm = a + (n / 2) * ST_POINTER_STEP;
	if (n > 7)
	{
		pl = a;
		pn = a + (n - 1) * ST_POINTER_STEP;
		if (n > 40)
		{
			size_t		d = (n / 8) * ST_POINTER_STEP;

			pl = DO_MED3(pl, pl + d, pl + 2 * d);
			pm = DO_MED3(pm - d, pm, pm + d);
			pn = DO_MED3(pn - 2 * d, pn - d, pn);
		}
		pm = DO_MED3(pl, pm, pn);
	}

这里的DO_MED3目的:找到3个数的中间数(靠的是compare),如下:

/*
 * Find the median of three values.  Currently, performance seems to be best
 * if the comparator is inlined here, but the med3 function is not inlined
 * in the qsort function.
 * 找到三个值的中位数。
 * 目前,如果比较器在此处内联,性能似乎最佳,但 med3 函数未在 qsort 函数中内联。
 *
 * Refer to the comment at the top of this file for known caveats to consider
 * when writing inlined comparator functions.
 */
static pg_noinline ST_ELEMENT_TYPE *
ST_MED3(ST_ELEMENT_TYPE * a,
		ST_ELEMENT_TYPE * b,
		ST_ELEMENT_TYPE * c
		ST_SORT_PROTO_COMPARE
		ST_SORT_PROTO_ARG)
{
	return DO_COMPARE(a, b) < 0 ?
		(DO_COMPARE(b, c) < 0 ? b : (DO_COMPARE(a, c) < 0 ? c : a))
		: (DO_COMPARE(b, c) > 0 ? b : (DO_COMPARE(a, c) < 0 ? a : c));
}

然后正式进入排序的过程,首先 pa,pb 指向第二个元素, pc,pd指向尾部;而基准数则是放在第一位:

	DO_SWAP(a, pm);
	pa = pb = a + ST_POINTER_STEP;
	pc = pd = a + (n - 1) * ST_POINTER_STEP;
	for (;;)
	{
		while (pb <= pc && (r = DO_COMPARE(pb, a)) <= 0)
		{
			if (r == 0)
			{
				DO_SWAP(pa, pb);
				pa += ST_POINTER_STEP;
			}
			pb += ST_POINTER_STEP;
			DO_CHECK_FOR_INTERRUPTS();
		}
		while (pb <= pc && (r = DO_COMPARE(pc, a)) >= 0)
		{
			if (r == 0)
			{
				DO_SWAP(pc, pd);
				pd -= ST_POINTER_STEP;
			}
			pc -= ST_POINTER_STEP;
			DO_CHECK_FOR_INTERRUPTS();
		}
		if (pb > pc)
			break;
		DO_SWAP(pb, pc);
		pb += ST_POINTER_STEP;
		pc -= ST_POINTER_STEP;
	}
  • 上面第一个while循环是从头开始筛选出与基准数相等和小于基准数的值,如果相等则从头向尾排列
  • 注意基准数也就是a 都是在和它进行比较
  • 而第二个循环则是从尾开始筛选出与基准数相等和大于基准数的值,如果相等则从尾向头排列

下面是一轮排序完后的可能图,头和尾都是与基准数相等的数值,再是小于和大于的数值,中间是还未比较的数,因为pb大于基准数,pc小于基准数,会导致循环退出,此时将两者交换进行下轮比较。

在这里插入图片描述

而排序完后的示意图,如下:

在这里插入图片描述


第四部分:这里还需要对相等数据进行合并,如下:

	pn = a + n * ST_POINTER_STEP;
	d1 = Min(pa - a, pb - pa);
	DO_SWAPN(a, pb - d1, d1); // 判断相等数据量 和 小于的数据量,实际只要移动数据量少的一方

	d1 = Min(pd - pc, pn - pd - ST_POINTER_STEP);
	DO_SWAPN(pb, pn - d1, d1); // 判断相等数据量 和 大于的数据量,实际只要移动数据量少的一方
	
	d1 = pb - pa; // 小于区的长度 乱序
	d2 = pd - pc; // 大于区的长度 乱序

在这里插入图片描述

如上,合并完,中间部分是与基准数相等的,左边是小于基准数的,右边是大于基准数的。


第五部分:接下来可以对两边不等的数据递归进行减而治之,PostgreSQL并没有简单递归,而是对数据量小的部分递归,大的部分进行迭代(原因上面已经说了)。

	if (d1 <= d2)
	{
		/* Recurse on left partition, then iterate on right partition */
		// 在左分区上递归,然后在右分区上迭代
		if (d1 > ST_POINTER_STEP)
			DO_SORT(a, d1 / ST_POINTER_STEP);
		if (d2 > ST_POINTER_STEP)
		{
			/* Iterate rather than recurse to save stack space */
			// 迭代而不是递归以节省堆栈空间
			/* DO_SORT(pn - d2, d2 / ST_POINTER_STEP) */
			a = pn - d2;
			n = d2 / ST_POINTER_STEP;
			goto loop;
		}
	}
	else
	{
		/* Recurse on right partition, then iterate on left partition */
		if (d2 > ST_POINTER_STEP)
			DO_SORT(pn - d2, d2 / ST_POINTER_STEP);
		if (d1 > ST_POINTER_STEP)
		{
			/* Iterate rather than recurse to save stack space */
			// 在右分区上递归,然后在左分区上迭代
			/* DO_SORT(a, d1 / ST_POINTER_STEP) */
			n = d1 / ST_POINTER_STEP;
			goto loop;
		}
	}

在这里插入图片描述

至于其他的后面有机会再深入学习!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2208913.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

鸿蒙UI系统组件16——富文本编辑器(RichEditor)

如果你也对鸿蒙开发感兴趣&#xff0c;加入“Harmony自习室”吧&#xff01;扫描下方名片&#xff0c;关注公众号&#xff0c;公众号更新更快&#xff0c;同时也有更多学习资料和技术讨论群。 RichEditor是支持图文混排和文本交互式编辑的组件&#xff0c;通常用于响应用户的对…

豆包MarsCode 合伙人计划限时招募中,推广最高赢万元现金!

豆包MarsCode 合伙人计划正式上线啦&#xff01;作为官方推出的推广激励项目&#xff0c;豆包MarsCode 编程助手号召和鼓励所有用户向我们推荐新用户。 现在正式开启首轮合伙人招募&#xff0c;诚邀各位有意愿推广普及 AI 编程产品的伙伴成为我们的合伙人&#xff0c;全国限量…

【Linux】操作系统基础

1.冯诺依曼体系结构介绍 冯诺依曼体系结构如下&#xff1a; 在上图中「输⼊设备」和「输出设备」⼀般被称为计算机的外设&#xff0c;⽽「存储器」在冯 诺依曼体系结构中表示「内存」 输⼊设备⼀般包括&#xff1a;⽹卡、磁盘、键盘、触摸屏等 输出设备⼀般包括&#xff1a;…

用 logfire 提高应用的可观测性

Logfire是由 Pydantic 团队打造的平台, 还有供 app 使用的 library, 我们经常提到对应用要做 LMT(Log, Metrics, Trace), Logfire 可以用来收集、存储、分析和可视化日志数据和应用性能指标。通过集成日志和度量&#xff0c;Logfire 提供了一个统一的界面来管理应用程序和系统的…

时间序列预测(一)——线性回归(linear regression)

目录 一、原理与目的 1、线性回归基于两个的假设&#xff1a; 2、线性回归的优缺点: 3、线性回归的主要目的是&#xff1a; 二、损失函数&#xff08;loss function&#xff09; 1、平方误差损失函数&#xff08;忽略了噪声误差&#xff09; 2、均方误差损失函数 三、随…

springboot项目通过maven的profile功能实现通过不同文件夹的方式来组织不同环境配置文件

写在前面 本文看下springboot项目如何通过文件夹的方式来组织不同环境配置文件。 1&#xff1a;正文 一般的我们写springboot项目时配置文件是这个样子的&#xff1a; appliction.yaml --> 通过spring.profiles.activexxx来激活某个指定后缀的配置文件 application-evn1…

【Java学习笔记】多线程

当我们在饭店聚餐时&#xff0c;多人同时吃一道菜的时候很容易发生争抢。例如&#xff0c;上了一道好菜&#xff0c;两个人同时夹这道菜&#xff0c;一人刚伸出筷子&#xff0c;结果伸到的时候菜已经被夹走了。为了避免这种现象&#xff0c;必须等一人 夹完一口后&#xff0c;另…

【大数据技术基础 | 实验一】配置SSH免密登录

文章目录 一、实验目的二、实验要求三、实验原理&#xff08;一&#xff09;大数据实验一体机&#xff08;二&#xff09;SSH免密认证 四、实验环境五、实验内容和步骤&#xff08;一&#xff09;搭建集群服务器&#xff08;二&#xff09;添加域名映射&#xff08;三&#xff…

工业物联网关-ModbusTCP

Modbus-TCP模式把网关视作Modbus从端设备&#xff0c;主端设备可以通过Modbus-TCP协议访问网关上所有终端设备。用户可以自定义多条通道&#xff0c;每条通道可以配置为TCP Server或者TCP Slave。注意&#xff0c;该模式需要指定采集通道&#xff0c;采集通道可以是串口和网口通…

51WORLD携手浙江科技大学,打造智慧校园新标杆

当前&#xff0c;国家教育数字化战略行动扎实推进&#xff0c;高等教育数字化转型步伐加快。 紧抓数字教育发展战略机遇&#xff0c;浙江科技大学联合51WORLD、正方软件股份有限公司&#xff08;简称&#xff1a;正方软件&#xff09;&#xff0c;共同研发打造浙科大孪生数智校…

为什么很多人宁愿加钱买港版,也不愿买国行 iPhone 16

最近的 iPhone 16 市场&#xff0c;真的是倒反天罡&#xff0c;攻守异形啊。 过去&#xff0c;港版 iPhone 都是性价比的次选&#xff0c;便宜个 10% 都得考虑考虑。但今年&#xff0c;港版 iPhone 16 的价格&#xff0c;反而比国行还贵。 比如&#xff0c;闲鱼上某个卖家&am…

Java消息摘要:MD5验证数据完整性、密码的加密与校验

MD5&#xff08;Message-Digest Algorithm 5&#xff09;是一种被广泛使用的密码散列函数&#xff0c;是一种不可逆的加密算法&#xff0c;该算法可以产生出一个128位&#xff08;16字节&#xff09;的散列值&#xff08;hash value&#xff09;&#xff0c;用于确保信息传输完…

【工具变量】知识产权示范城市DID(2000-2023年)

数据简介&#xff1a;为深入贯彻落实《国家知识产权战略纲要》&#xff0c;强化知识产权治理效能&#xff0c;国家知识产权局制定了《国家知识产权试点、示范城市&#xff08;城区&#xff09;评定和管理办法》&#xff0c;知识产权示范城市成为知识产权强国战略落地的城市支撑…

echarts 入门

工作中第一次碰到echarts&#xff0c;当时有大哥。二进宫没办法&#xff0c;只能搞定它。 感觉生活就是这样&#xff0c;不能解决的问题总是会反复出现。通过看视频、查资料&#xff0c;完成了工作要求。写一篇Hello World&#xff0c;进行备查。 基本使用 快速上手 <!DO…

QNAP新手必看!轻松搞定反向代理设置

反向代理是一种服务器配置&#xff0c;允许你通过一个域名或者IP地址来访问不同的内部应用服务。在QNAP NAS上配置反向代理可以提升应用程序的安全性和可访问性。 准备工作 确保QNAP NAS已连接网络并有公网IPv4/IPv6。 确认已启用Web服务 步骤 1&#xff1a;启用Web服务 登…

相机光学(三十九)——光学暗角与机械暗角

1.什么是暗角 在玩摄影一段时间,拍摄一定数量的照片之后,每个人都会不可避免地遇上一个新问题,那就是暗角现象。所谓暗角,是指在拍摄亮度均匀的场景时,画面的四角却出现与实际景物不符的、亮度降低的现象,又被称为“失光“。 2.暗角的成因 (1)边角的成像光线与镜头光轴…

【智能控制】第2章 专家系统,专家控制,模糊关系,模糊推理,专家PID控制

目录 2.1 专家系统 2.1.1 专家系统概述 2.1.2 专家系统构成 2.1.3 专家系统的建立 1&#xff0e;知识库 2&#xff0e;推理机 3&#xff0e;知识的表示 4&#xff0e;专家系统开发语言 5&#xff0e;专家系统建立步骤 第二节 专家控制 2&#xff0e;功能 3 与专家…

三、账号密码存储

使用Playfers存储 Unity本地持久化类Playerprefs使用详解 - PlaneZhong - 博客园 (cnblogs.com) 一、登陆界面切换 1、登陆界面的脚本&#xff08;机制类脚本&#xff09; 在这个UI上挂载一个脚本LoginWnd 先声明一下这个脚本&#xff0c;拖拽 2、在登录模块中调用 这里的l…

华为全联接大会2024 | 聚焦运维智能化,麒麟信安分享“基于大模型的新一代智能运维平台”

2024年9月19日至21日&#xff0c;以“共赢行业智能化”为主题的华为全联接大会2024在上海世博中心盛大召开。麒麟信安受邀出席大会&#xff0c;与全球的思想领袖、商业精英、技术专家和合作伙伴&#xff0c;共同探讨智能化、数字化技术赋能千行万业&#xff0c;把握新机遇&…

第十五章 Java多线程--线程池

目录 一、线程池基础概念 常见的线程池类型&#xff1a; 创建线程池的例子&#xff1a; 注意事项&#xff1a; 二、线程池使用场景 三、JDK自带的构建线程池的方式 1 newFixedThreadPool 2 newSingleThreadExecutor 3 newCachedThreadPool 4 newScheduleThreadPool …