Am 18.07.22 um 00:37 schrieb Thomas Munro:
Seems OK for a worst case.  It must still be a lot faster than doing
it in SQL.  Now I wonder what the exact requirements would be to
dispatch to a faster version that would handle int4.  I haven't
studied this in detail but perhaps to dispatch to a fast shuffle for
objects of size X, the requirement would be something like typlen == X
&& align_bytes <= typlen && typlen % align_bytes == 0, where
align_bytes is typalign converted to ALIGNOF_{CHAR,SHORT,INT,DOUBLE}?
Or in English, 'the data consists of densely packed objects of fixed
size X, no padding'.  Or perhaps you can work out the padded size and
use that, to catch a few more types.  Then you call
array_shuffle_{2,4,8}() as appropriate, which should be as fast as
your original int[] proposal, but work also for float, date, ...?

About your experimental patch, I haven't reviewed it properly or tried
it but I wonder if uint32 dat_offset, uint32 size (= half size
elements) would be enough due to limitations on varlenas.

I made another experimental patch with fast tracks for typelen4 and typelen8. alignments are not yet considered.
From 37269598a1dace99c9059514e0fdcb90b9240ed3 Mon Sep 17 00:00:00 2001
From: Martin Kalcher <martin.kalc...@aboutsource.net>
Date: Sun, 17 Jul 2022 18:06:04 +0200
Subject: [PATCH] introduce array_shuffle()

---
 src/backend/utils/adt/arrayfuncs.c | 178 +++++++++++++++++++++++++++++
 src/include/catalog/pg_proc.dat    |   3 +
 2 files changed, 181 insertions(+)

diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index fb167f226a..919cb2bfd6 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -6872,3 +6872,181 @@ trim_array(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+static ArrayType *
+array_shuffle_4(ArrayType *array)
+{
+	ArrayType	*result;
+	int32		*adat,
+				*rdat;
+	int			i,
+				j,
+				nitems;
+
+	result = create_array_envelope(ARR_NDIM(array),
+								   ARR_DIMS(array),
+								   ARR_LBOUND(array),
+								   ARR_SIZE(array),
+								   ARR_ELEMTYPE(array),
+								   array->dataoffset);
+
+	adat = (int32 *) ARR_DATA_PTR(array);
+	rdat = (int32 *) ARR_DATA_PTR(result);
+	nitems = ARR_DIMS(array)[0];
+
+	for (i = 0; i < nitems; i++)
+	{
+		j = random() % (i + 1);
+		rdat[i] = rdat[j];
+		rdat[j] = adat[i];
+	}
+	return result;
+}
+
+static ArrayType *
+array_shuffle_8(ArrayType *array)
+{
+	ArrayType	*result;
+	int64		*adat,
+				*rdat;
+	int			i,
+				j,
+				nitems;
+
+	result = create_array_envelope(ARR_NDIM(array),
+								   ARR_DIMS(array),
+								   ARR_LBOUND(array),
+								   ARR_SIZE(array),
+								   ARR_ELEMTYPE(array),
+								   array->dataoffset);
+
+	adat = (int64 *) ARR_DATA_PTR(array);
+	rdat = (int64 *) ARR_DATA_PTR(result);
+	nitems = ARR_DIMS(array)[0];
+
+	for (i = 0; i < nitems; i++)
+	{
+		j = random() % (i + 1);
+		rdat[i] = rdat[j];
+		rdat[j] = adat[i];
+	}
+	return result;
+}
+
+static ArrayType *
+array_shuffle_generic(ArrayType *array,
+					  int16 elmlen, bool elmbyval, char elmalign)
+{
+	ArrayType	*result;
+	char		*adat,
+				*rdat;
+	int 		i,
+				ndim,
+				*dims,
+				nitems,
+				nelms_per_item;
+
+	struct {
+		char *dat;
+		size_t size;
+	} *refs;
+
+	ndim = ARR_NDIM(array);
+	dims = ARR_DIMS(array);
+
+	nelms_per_item = 1;
+
+	for (i = 1; i < ndim; i++)
+		nelms_per_item *= dims[i];
+
+	nitems = dims[0];
+
+	refs = palloc(nitems * (sizeof(char *) + sizeof(size_t)));
+	adat = ARR_DATA_PTR(array);
+
+	/*
+	 * To allow shuffling of arrays with variable length elements, we are going
+	 * to shuffle references to the elements, because swapping variable length
+	 * elements in an array is complicated. In a second step we will iterate the
+	 * shuffled references and copy the elements to the destination array.
+	 * We are using the "inside-out" variant of the Fisher-Yates algorithm to
+	 * produce a shuffled array of references: Append each reference than swap it
+	 * with a radomly-chosen refence.
+	 */
+	for (i = 0; i < nitems; i++)
+	{
+		char *next = array_seek(adat, 0, NULL, nelms_per_item,
+							    elmlen, elmbyval, elmalign);
+		int j = random() % (i + 1);
+		refs[i] = refs[j];
+		refs[j].dat = adat;
+		refs[j].size = next - adat;
+		adat = next;
+	}
+
+	result = create_array_envelope(ARR_NDIM(array),
+								   ARR_DIMS(array),
+								   ARR_LBOUND(array),
+								   ARR_SIZE(array),
+								   ARR_ELEMTYPE(array),
+								   array->dataoffset);
+
+	rdat = ARR_DATA_PTR(result);
+
+	/* Collect all references and copy elements to the destination array */
+	for (i = 0; i < nitems; i++)
+	{
+		memcpy(rdat, refs[i].dat, refs[i].size);
+		rdat += refs[i].size;
+	}
+
+	pfree(refs);
+
+	return result;
+}
+
+Datum
+array_shuffle(PG_FUNCTION_ARGS)
+{
+	ArrayType		*array,
+					*result;
+	int16			elmlen;
+	bool			elmbyval;
+	char			elmalign;
+	Oid             elmtyp;
+	TypeCacheEntry	*typentry;
+
+	array = PG_GETARG_ARRAYTYPE_P(0);
+
+	if (ARR_HASNULL(array))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("shuffling arrays with null elements is not supported")));
+
+	if (ARR_NDIM(array) < 1 || ARR_DIMS(array)[0] < 2)
+		PG_RETURN_ARRAYTYPE_P(array);
+
+	elmtyp = ARR_ELEMTYPE(array);
+
+	typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
+
+	if (typentry == NULL || typentry->type_id != elmtyp)
+	{
+		typentry = lookup_type_cache(elmtyp, 0);
+		fcinfo->flinfo->fn_extra = (void *) typentry;
+	}
+	elmlen = typentry->typlen;
+	elmbyval = typentry->typbyval;
+	elmalign = typentry->typalign;
+
+	if (ARR_NDIM(array) == 1 && elmlen == 4)
+		result = array_shuffle_4(array);
+	else if (ARR_NDIM(array) == 1 && elmlen == 8)
+		result = array_shuffle_8(array);
+	else
+		result = array_shuffle_generic(array, elmlen, elmbyval, elmalign);
+
+	PG_FREE_IF_COPY(array, 0);
+
+	PG_RETURN_ARRAYTYPE_P(result);
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2e41f4d9e8..56aff551d3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1681,6 +1681,9 @@
   proname => 'arraycontjoinsel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int2 internal',
   prosrc => 'arraycontjoinsel' },
+{ oid => '7777', descr => 'shuffle array',
+  proname => 'array_shuffle', proisstrict => 'f', prorettype => 'anyarray',
+  proargtypes => 'anyarray', prosrc => 'array_shuffle' },
 
 { oid => '764', descr => 'large object import',
   proname => 'lo_import', provolatile => 'v', proparallel => 'u',
-- 
2.37.1

Reply via email to