SIMD optimization for list_sort
Hi All,
We propose enabling SIMD-based sort for list_sort using the x86-simd-sort library (https://github.com/intel/x86-simd-sort).
The existing list_sort takes a comparator function to compare pairs of ListCell. On the other hand, x86-simd-sort requires an array of numeric values to sort, and it returns an array of sorted indices. To enable x86-simd-sort, we add new list_sort_simd functions that take an extractor function. The function extracts a value (float or uint32) from a ListCell. Those values are then collected into an array for x86-simd-sort to work on. A comparator function can still be passed to be used as tie-breaker.
typedef float (*list_sort_extractor_float)(const ListCell *a);
typedef uint32 (*list_sort_extractor_uint32)(const ListCell *a);
void list_sort_simd_float(List *list, list_sort_extractor_float extract, list_sort_comparator cmp);
void list_sort_simd_uint32(List *list, list_sort_extractor_uint32 extract, list_sort_comparator cmp);
These functions will exist alongside the current list_sort. Existing list_sort use cases in Postgres or extensions will not be affected by default, and they can be converted to list_sort_simd functions where it makes sense in terms of performance.
This feature introduces the x86-simd-sort library as a dependency. We plan to make the dependency optional, using a configuration option, such as --with-x86-simd-sort. If building without this option, list_sort_simd functions could fall back to the existing list_sort (e.g., by creating a comparator function that combines the extracted value and the tie-breaker comparator function). Alternatively (or in addition), we could add a function to report whether x86-simd-sort is available.
We identified a first use case for list_sort_simd_float in pgvector. As part of HNSW index construction, pgvector uses list_sort to sort candidate vectors by distance. Using list_sort_simd_float, we observed reduction in index build time in some scenarios. For example, we observed 7% reduction in index build time with the gist-960 dataset and 10% with the dbpedia-openai-1000k dataset (ANN-Benchmarks, HNSW index with halfvec, m=80). We are also looking into microbenchmarks to measure list_sort performance independently.
We'd appreciate feedback on this approach. In the meantime, we will complete the patch to share. We also plan to extend SIMD-based sort to tuple sort in the future.
Thanks!
Luca Giacchino
On 22/11/2024 01:27, Giacchino, Luca wrote:
The existing list_sort takes a comparator function to compare pairs of
ListCell. On the other hand, x86-simd-sort requires an array of numeric
values to sort, and it returns an array of sorted indices. To enable
x86-simd-sort, we add new list_sort_simd functions that take an
extractor function. The function extracts a value (float or uint32) from
a ListCell. Those values are then collected into an array for x86-simd-
sort to work on. A comparator function can still be passed to be used as
tie-breaker.typedef float (*list_sort_extractor_float)(const ListCell *a);
typedef uint32 (*list_sort_extractor_uint32)(const ListCell *a);
void list_sort_simd_float(List *list, list_sort_extractor_float extract,
list_sort_comparator cmp);void list_sort_simd_uint32(List *list, list_sort_extractor_uint32
extract, list_sort_comparator cmp);These functions will exist alongside the current list_sort. Existing
list_sort use cases in Postgres or extensions will not be affected by
default, and they can be converted to list_sort_simd functions where it
makes sense in terms of performance.
I'd suggest targeting pg_qsort() directly, instead of list_sort().
list_sort() is not used in very performance critical parts.
We identified a first use case for list_sort_simd_float in pgvector. As
part of HNSW index construction, pgvector uses list_sort to sort
candidate vectors by distance. Using list_sort_simd_float, we observed
reduction in index build time in some scenarios. For example, we
observed 7% reduction in index build time with the gist-960 dataset and
10% with the dbpedia-openai-1000k dataset (ANN-Benchmarks, HNSW index
with halfvec, m=80). We are also looking into microbenchmarks to measure
list_sort performance independently.
That's interesting. I'd suggest proposing this to the pgvector project
directly, since pgvector would immediately benefit.
We’d appreciate feedback on this approach. In the meantime, we will
complete the patch to share. We also plan to extend SIMD-based sort to
tuple sort in the future.
If you could use this to speed up tuple sorting, that would be much more
interesting for PostgreSQL itself.
--
Heikki Linnakangas
Neon (https://neon.tech)
On Fri, Nov 22, 2024 at 06:01:22PM +0200, Heikki Linnakangas wrote:
On 22/11/2024 01:27, Giacchino, Luca wrote:
We�d appreciate feedback on this approach. In the meantime, we will
complete the patch to share. We also plan to extend SIMD-based sort to
tuple sort in the future.If you could use this to speed up tuple sorting, that would be much more
interesting for PostgreSQL itself.
+1
--
nathan
On Fri, Nov 22, 2024 at 6:27 AM Giacchino, Luca
<luca.giacchino@intel.com> wrote:
We’d appreciate feedback on this approach. In the meantime, we will complete the patch to share. We also plan to extend SIMD-based sort to tuple sort in the future.
Coincidentally, I'll be prototyping a tuple sort that will take better
advantage of keys that are integers (whether authoritative or
abbreviated), using scalar, portable branch-free techniques. This will
require some re-architecting and I believe that will also make it
easier for you to experiment with sorting networks there.
--
John Naylor
Amazon Web Services
On 22/11/2024 06:27, Giacchino, Luca wrote:
We’d appreciate feedback on this approach. In the meantime, we will
complete the patch to share. We also plan to extend SIMD-based sort to
tuple sort in the future.
Nice! I continually see performance reports when sorting and group order
impact performance. Because of that, efforts have been made to optimise
sorts with recombination of clause order [1,2]. It would be nice to
review your Sort operator optimisation. I hope, it might improve
performance of the cases provided in these threads.
[1]: POC: GROUP BY optimization /messages/by-id/7c79e6a5-8597-74e8-0671-1c39d124c9d6@sigaev.ru
/messages/by-id/7c79e6a5-8597-74e8-0671-1c39d124c9d6@sigaev.ru
[2]: Consider the number of columns in the sort cost model /messages/by-id/8742aaa8-9519-4a1f-91bd-364aec65f5cf@gmail.com
/messages/by-id/8742aaa8-9519-4a1f-91bd-364aec65f5cf@gmail.com
--
regards, Andrei Lepikhov
Hi All,
Thank you very much for your feedback! We investigated the recommendations as we developed the current patch. Please refer to the comments below.
I'd suggest targeting pg_qsort() directly, instead of list_sort(). list_sort() is not used in very performance critical parts.
Using x86-simd-sort would require a new pg_qsort variant taking an extractor function. The new API would still have to bubble up to functions like list_sort, which internally call pg_qsort. We targeted list_sort first, as we were aware of at least one use case that benefits from SIMD sorting (pgvector). As we target more use cases (such as tuple sort), we may need to move the SIMD-enabled implementation to a common location, and a new variant of pg_qsort may be a good candidate.
I'd suggest proposing this to the pgvector project directly, since pgvector would immediately benefit.
Yes, we’ll share the performance gains on HNSW index build time with the pgvector community. However, other users of list_sort (e.g., other extensions) may benefit from the simd-sort version of list_sort as well. As it is not a pgvector-specific optimization, we propose it for PostgreSQL.
If you could use this to speed up tuple sorting, that would be much more interesting for PostgreSQL itself.
Enabling x86-simd-sort for tuple sorting is in development. We observed promising results, but there is still work to do (e.g., to ensure there are no regressions in different conditions). We are planning to share another patch for tuple sort building on this one.
I continually see performance reports when sorting and group order impact performance. Because of that, efforts have been made to optimise sorts with recombination of clause order. It would be nice to review your Sort operator optimisation. I hope, it might improve performance of the cases provided in these threads.
The submitted patch gives more details on the optimization.
Based on the previously shared proposal, we’re providing a patch for enabling SIMD-based sort for list_sort using the x86-simd-sort library (https://github.com/intel/x86-simd-sort).
As described earlier, we add a new function that takes an extractor function.
void list_sort_simd_float(List *list, list_sort_extractor_float extract, list_sort_comparator cmp);
The extract function retrieves the float values based on which the ListCells are sorted by x86-simd-sort. The comparator function is used to break ties. To break the ties, we call the existing list_sort function, passing the comparator.
The dependency on x86-simd-sort is optional through a --with-simdsort flag. If Postgres is not built with x86-simd-sort, the above function falls back to the existing list_sort.
The current patch provides a SIMD implementation for sorting float values. Support for additional data types (such as int32) will be added in the future.
Note that there are a few unexpected changes in the generated configure file. We will investigate.
An extension is also provided to stress test list_sort (under /src/test/modules/test_listsort). The extension defines a “test_listsort” function that takes 4 parameters. The first is the size of the list to be generated and sorted. The second and the third parameters (“random_number” and “tie_break_limit”) are used to introduce repeats (ties) to better simulate real-world data. The fourth parameter is a boolean flag to select the SIMD-enabled version or the existing list_sort.
To measure performance, we use pgbench to run the function repeatedly. In the first test below, a list of size 100K is generated. To populate each cell, a random float f is generated. If (f % 500) <= 100 (1/5 of the cells), the cell is assigned a number between 0 and 100 (which will cause repeats). The rest of the cells are assigned the original random float (for which repeats are unlikely). In the second example, tie_break_limit is increased to generate more repeats.
The data was collected on an AWS m7i.metal-24xl instance (Ubuntu 22.04, gcc12.3)
tps gains with SIMD-enabled list_sort over existing list_sort:
size=100k, max_random=500, tie_break_limit=100: 2.63x (1 client), 2.15x (48 clients)
size=100k, max_random=500, tie_break_limit=250: 2.09x (1 client), 1.75x (48 clients)
The gains are very promising, and we will characterize more conditions. Please let us know your thoughts.
Thanks!
Best regards,
Rakshit Rajeev
Best regards,
-----Original Message-----
From: Andrei Lepikhov <lepihov@gmail.com>
Sent: Saturday, November 23, 2024 8:50 AM
To: Giacchino, Luca <luca.giacchino@intel.com>; pgsql-hackers@lists.postgresql.org
Cc: R, Rakshit <rakshit.r@intel.com>; Shankaran, Akash <akash.shankaran@intel.com>; Devulapalli, Raghuveer <raghuveer.devulapalli@intel.com>
Subject: Re: SIMD optimization for list_sort
On 22/11/2024 06:27, Giacchino, Luca wrote:
We’d appreciate feedback on this approach. In the meantime, we will
complete the patch to share. We also plan to extend SIMD-based sort to
tuple sort in the future.
Nice! I continually see performance reports when sorting and group order impact performance. Because of that, efforts have been made to optimise sorts with recombination of clause order [1,2]. It would be nice to review your Sort operator optimisation. I hope, it might improve performance of the cases provided in these threads.
[1]: POC: GROUP BY optimization /messages/by-id/7c79e6a5-8597-74e8-0671-1c39d124c9d6@sigaev.ru
/messages/by-id/7c79e6a5-8597-74e8-0671-1c39d124c9d6@sigaev.ru
[2]: Consider the number of columns in the sort cost model /messages/by-id/8742aaa8-9519-4a1f-91bd-364aec65f5cf@gmail.com
--
regards, Andrei Lepikhov
Attachments:
v1-0001-Add-list_sort-implementation-based-on-x86-simd-so.patchapplication/octet-stream; name=v1-0001-Add-list_sort-implementation-based-on-x86-simd-so.patchDownload
From 8781b8feef917fdb826c520f60a9556c678025cc Mon Sep 17 00:00:00 2001
From: "R, Rakshit" <rakshit.r@intel.com>
Date: Thu, 1 Aug 2024 11:16:58 +0530
Subject: [PATCH v1] Add list_sort implementation based on x86-simd-sort
library
---
configure | 228 ++++++++++++++-
configure.ac | 31 ++
meson.build | 24 ++
meson_options.txt | 6 +
src/backend/nodes/list.c | 219 ++++++++++++++
src/include/nodes/pg_list.h | 2 +
src/include/pg_config.h.in | 6 +
src/test/modules/Makefile | 1 +
src/test/modules/test_listsort/.gitignore | 4 +
src/test/modules/test_listsort/Makefile | 23 ++
src/test/modules/test_listsort/README | 10 +
.../test_listsort/expected/test_listsort.out | 13 +
.../test_listsort/sql/test_listsort.sql | 4 +
.../test_listsort/test_listsort--1.0.sql | 9 +
.../modules/test_listsort/test_listsort.c | 210 ++++++++++++++
.../test_listsort/test_listsort.control | 5 +
src/test/regress/expected/list_sort_simd.out | 87 ++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/regress.c | 266 ++++++++++++++++++
src/test/regress/sql/list_sort_simd.sql | 32 +++
src/tools/pgindent/typedefs.list | 1 +
21 files changed, 1176 insertions(+), 7 deletions(-)
create mode 100644 src/test/modules/test_listsort/.gitignore
create mode 100644 src/test/modules/test_listsort/Makefile
create mode 100644 src/test/modules/test_listsort/README
create mode 100644 src/test/modules/test_listsort/expected/test_listsort.out
create mode 100644 src/test/modules/test_listsort/sql/test_listsort.sql
create mode 100644 src/test/modules/test_listsort/test_listsort--1.0.sql
create mode 100644 src/test/modules/test_listsort/test_listsort.c
create mode 100644 src/test/modules/test_listsort/test_listsort.control
create mode 100644 src/test/regress/expected/list_sort_simd.out
create mode 100644 src/test/regress/sql/list_sort_simd.sql
diff --git a/configure b/configure
index ceeef9b0915..336de2f6adb 100755
--- a/configure
+++ b/configure
@@ -701,6 +701,9 @@ with_zstd
LZ4_LIBS
LZ4_CFLAGS
with_lz4
+SIMDSORT_LIBS
+SIMDSORT_CFLAGS
+with_simdsort
with_zlib
with_system_tzdata
with_libxslt
@@ -803,6 +806,7 @@ infodir
docdir
oldincludedir
includedir
+runstatedir
localstatedir
sharedstatedir
sysconfdir
@@ -868,6 +872,7 @@ with_libxml
with_libxslt
with_system_tzdata
with_zlib
+with_simdsort
with_lz4
with_zstd
with_ssl
@@ -897,6 +902,8 @@ ICU_LIBS
XML2_CONFIG
XML2_CFLAGS
XML2_LIBS
+SIMDSORT_CFLAGS
+SIMDSORT_LIBS
LZ4_CFLAGS
LZ4_LIBS
ZSTD_CFLAGS
@@ -945,6 +952,7 @@ datadir='${datarootdir}'
sysconfdir='${prefix}/etc'
sharedstatedir='${prefix}/com'
localstatedir='${prefix}/var'
+runstatedir='${localstatedir}/run'
includedir='${prefix}/include'
oldincludedir='/usr/include'
docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
@@ -1197,6 +1205,15 @@ do
| -silent | --silent | --silen | --sile | --sil)
silent=yes ;;
+ -runstatedir | --runstatedir | --runstatedi | --runstated \
+ | --runstate | --runstat | --runsta | --runst | --runs \
+ | --run | --ru | --r)
+ ac_prev=runstatedir ;;
+ -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
+ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
+ | --run=* | --ru=* | --r=*)
+ runstatedir=$ac_optarg ;;
+
-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
ac_prev=sbindir ;;
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@@ -1334,7 +1351,7 @@ fi
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
datadir sysconfdir sharedstatedir localstatedir includedir \
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
- libdir localedir mandir
+ libdir localedir mandir runstatedir
do
eval ac_val=\$$ac_var
# Remove trailing slashes.
@@ -1487,6 +1504,7 @@ Fine tuning of the installation directories:
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
--libdir=DIR object code libraries [EPREFIX/lib]
--includedir=DIR C header files [PREFIX/include]
--oldincludedir=DIR C header files for non-gcc [/usr/include]
@@ -1579,6 +1597,7 @@ Optional Packages:
--with-system-tzdata=DIR
use system time zone data in DIR
--without-zlib do not use Zlib
+ --with-simdsort build with SIMDSORT support
--with-lz4 build with LZ4 support
--with-zstd build with ZSTD support
--with-ssl=LIB use LIB for SSL/TLS support (openssl)
@@ -1610,6 +1629,10 @@ Some influential environment variables:
XML2_CONFIG path to xml2-config utility
XML2_CFLAGS C compiler flags for XML2, overriding pkg-config
XML2_LIBS linker flags for XML2, overriding pkg-config
+ SIMDSORT_CFLAGS
+ C compiler flags for SIMDSORT, overriding pkg-config
+ SIMDSORT_LIBS
+ linker flags for SIMDSORT, overriding pkg-config
LZ4_CFLAGS C compiler flags for LZ4, overriding pkg-config
LZ4_LIBS linker flags for LZ4, overriding pkg-config
ZSTD_CFLAGS C compiler flags for ZSTD, overriding pkg-config
@@ -9036,6 +9059,147 @@ fi
+#
+# SIMDSORT
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with SIMDSORT support" >&5
+$as_echo_n "checking whether to build with SIMDSORT support... " >&6; }
+
+
+
+# Check whether --with-simdsort was given.
+if test "${with_simdsort+set}" = set; then :
+ withval=$with_simdsort;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_SIMDSORT 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-simdsort option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_simdsort=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_simdsort" >&5
+$as_echo "$with_simdsort" >&6; }
+
+
+if test "$with_simdsort" = yes; then
+
+pkg_failed=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for x86simdsortcpp" >&5
+$as_echo_n "checking for x86simdsortcpp... " >&6; }
+
+if test -n "$SIMDSORT_CFLAGS"; then
+ pkg_cv_SIMDSORT_CFLAGS="$SIMDSORT_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"x86simdsortcpp\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "x86simdsortcpp") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_SIMDSORT_CFLAGS=`$PKG_CONFIG --cflags "x86simdsortcpp" 2>/dev/null`
+ test "x$?" != "x0" && pkg_failed=yes
+else
+ pkg_failed=yes
+fi
+ else
+ pkg_failed=untried
+fi
+if test -n "$SIMDSORT_LIBS"; then
+ pkg_cv_SIMDSORT_LIBS="$SIMDSORT_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"x86simdsortcpp\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "x86simdsortcpp") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_SIMDSORT_LIBS=`$PKG_CONFIG --libs "x86simdsortcpp" 2>/dev/null`
+ test "x$?" != "x0" && pkg_failed=yes
+else
+ pkg_failed=yes
+fi
+ else
+ pkg_failed=untried
+fi
+
+
+
+if test $pkg_failed = yes; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+ _pkg_short_errors_supported=yes
+else
+ _pkg_short_errors_supported=no
+fi
+ if test $_pkg_short_errors_supported = yes; then
+ SIMDSORT_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "x86simdsortcpp" 2>&1`
+ else
+ SIMDSORT_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "x86simdsortcpp" 2>&1`
+ fi
+ # Put the nasty error message in config.log where it belongs
+ echo "$SIMDSORT_PKG_ERRORS" >&5
+
+ as_fn_error $? "Package requirements (x86simdsortcpp) were not met:
+
+$SIMDSORT_PKG_ERRORS
+
+Consider adjusting the PKG_CONFIG_PATH environment variable if you
+installed software in a non-standard prefix.
+
+Alternatively, you may set the environment variables SIMDSORT_CFLAGS
+and SIMDSORT_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details." "$LINENO" 5
+elif test $pkg_failed = untried; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it
+is in your PATH or set the PKG_CONFIG environment variable to the full
+path to pkg-config.
+
+Alternatively, you may set the environment variables SIMDSORT_CFLAGS
+and SIMDSORT_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.
+
+To get pkg-config, see <http://pkg-config.freedesktop.org/>.
+See \`config.log' for more details" "$LINENO" 5; }
+else
+ SIMDSORT_CFLAGS=$pkg_cv_SIMDSORT_CFLAGS
+ SIMDSORT_LIBS=$pkg_cv_SIMDSORT_LIBS
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+fi
+ # We only care about -I, -D, and -L switches;
+ # note that -lsimdsort will be added by AC_CHECK_LIB below.
+ for pgac_option in $SIMDSORT_CFLAGS; do
+ case $pgac_option in
+ -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+ esac
+ done
+ for pgac_option in $SIMDSORT_LIBS; do
+ case $pgac_option in
+ -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+ esac
+ done
+fi
+
#
# LZ4
#
@@ -12753,6 +12917,56 @@ fi
fi
+if test "$with_simdsort" = yes ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for keyvalue_qsort_float_sizet in -lx86simdsortcpp" >&5
+$as_echo_n "checking for keyvalue_qsort_float_sizet in -lx86simdsortcpp... " >&6; }
+if ${ac_cv_lib_x86simdsortcpp_keyvalue_qsort_float_sizet+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lx86simdsortcpp $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char keyvalue_qsort_float_sizet ();
+int
+main ()
+{
+return keyvalue_qsort_float_sizet ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_x86simdsortcpp_keyvalue_qsort_float_sizet=yes
+else
+ ac_cv_lib_x86simdsortcpp_keyvalue_qsort_float_sizet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_x86simdsortcpp_keyvalue_qsort_float_sizet" >&5
+$as_echo "$ac_cv_lib_x86simdsortcpp_keyvalue_qsort_float_sizet" >&6; }
+if test "x$ac_cv_lib_x86simdsortcpp_keyvalue_qsort_float_sizet" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBX86SIMDSORTCPP 1
+_ACEOF
+
+ LIBS="-lx86simdsortcpp $LIBS"
+
+else
+ as_fn_error $? "library 'x86simdsortcpp' is required for SIMDSORt support" "$LINENO" 5
+fi
+
+fi
+
if test "$with_lz4" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for LZ4_compress_default in -llz4" >&5
$as_echo_n "checking for LZ4_compress_default in -llz4... " >&6; }
@@ -12854,6 +13068,8 @@ fi
fi
# Note: We can test for libldap_r only after we know PTHREAD_LIBS
+# also, on AIX, we may need to have openssl in LIBS for this step.
+
if test "$with_ldap" = yes ; then
_LIBS="$LIBS"
if test "$PORTNAME" != "win32"; then
@@ -14785,7 +15001,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14831,7 +15047,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14855,7 +15071,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14900,7 +15116,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14924,7 +15140,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
diff --git a/configure.ac b/configure.ac
index d713360f340..bdd32ea1732 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1069,6 +1069,31 @@ PGAC_ARG_BOOL(with, zlib, yes,
[do not use Zlib])
AC_SUBST(with_zlib)
+#
+# SIMDSORT
+#
+AC_MSG_CHECKING([whether to build with SIMDSORT support])
+PGAC_ARG_BOOL(with, simdsort, no, [build with SIMDSORT support],
+ [AC_DEFINE([USE_SIMDSORT], 1, [Define to 1 to build with SIMDSORT support. (--with-simdsort)])])
+AC_MSG_RESULT([$with_simdsort])
+AC_SUBST(with_simdsort)
+
+if test "$with_simdsort" = yes; then
+ PKG_CHECK_MODULES(SIMDSORT, x86simdsortcpp)
+ # We only care about -I, -D, and -L switches;
+ # note that -lsimdsort will be added by AC_CHECK_LIB below.
+ for pgac_option in $SIMDSORT_CFLAGS; do
+ case $pgac_option in
+ -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";;
+ esac
+ done
+ for pgac_option in $SIMDSORT_LIBS; do
+ case $pgac_option in
+ -L*) LDFLAGS="$LDFLAGS $pgac_option";;
+ esac
+ done
+fi
+
#
# LZ4
#
@@ -1353,6 +1378,10 @@ if test "$with_libxslt" = yes ; then
AC_CHECK_LIB(xslt, xsltCleanupGlobals, [], [AC_MSG_ERROR([library 'xslt' is required for XSLT support])])
fi
+if test "$with_simdsort" = yes ; then
+ AC_CHECK_LIB(x86simdsortcpp, keyvalue_qsort_float_sizet, [], [AC_MSG_ERROR([library 'x86simdsortcpp' is required for SIMDSORt support])])
+fi
+
if test "$with_lz4" = yes ; then
AC_CHECK_LIB(lz4, LZ4_compress_default, [], [AC_MSG_ERROR([library 'lz4' is required for LZ4 support])])
fi
@@ -1362,6 +1391,8 @@ if test "$with_zstd" = yes ; then
fi
# Note: We can test for libldap_r only after we know PTHREAD_LIBS
+# also, on AIX, we may need to have openssl in LIBS for this step.
+
if test "$with_ldap" = yes ; then
_LIBS="$LIBS"
if test "$PORTNAME" != "win32"; then
diff --git a/meson.build b/meson.build
index 1ceadb9a830..197e0a80b11 100644
--- a/meson.build
+++ b/meson.build
@@ -895,6 +895,28 @@ else
libxslt = not_found_dep
endif
+###############################################################
+# Library: simdsort
+###############################################################
+
+simdsortopt = get_option('simdsort')
+if not simdsortopt.disabled()
+ simdsort = dependency('x86simdsortcpp', required: false)
+ # Unfortunately the dependency is named differently with cmake
+ if not simdsort.found() # combine with above once meson 0.60.0 is required
+ simdsort = dependency('x86simdsortcpp', required: simdsortopt,
+ method: 'cmake', modules: ['SIMDSORT::simdsort_shared'],
+ )
+ endif
+
+ if simdsort.found()
+ cdata.set('USE_SIMDSORT', 1)
+ cdata.set('HAVE_LIBSIMDSORT', 1)
+ endif
+
+else
+ simdsort = not_found_dep
+endif
###############################################################
@@ -3069,6 +3091,7 @@ backend_both_deps += [
ldap,
libintl,
libxml,
+ simdsort,
lz4,
pam,
ssl,
@@ -3723,6 +3746,7 @@ if meson.version().version_compare('>=0.57')
'libxml': libxml,
'libxslt': libxslt,
'llvm': llvm,
+ 'simdsort':simdsort,
'lz4': lz4,
'nls': libintl,
'openssl': ssl,
diff --git a/meson_options.txt b/meson_options.txt
index d9c7ddccbc4..e21b39e3f00 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -112,6 +112,9 @@ option('libxslt', type: 'feature', value: 'auto',
option('llvm', type: 'feature', value: 'disabled',
description: 'LLVM support')
+option('simdsort', type: 'feature', value: 'auto',
+ description: 'SIMDSORT support')
+
option('lz4', type: 'feature', value: 'auto',
description: 'LZ4 support')
@@ -174,6 +177,9 @@ option('FOP', type: 'string', value: 'fop',
option('GZIP', type: 'string', value: 'gzip',
description: 'Path to gzip binary')
+option('SIMDSORT', type: 'string', value: 'simdsort',
+ description: 'Path to simdsort binary')
+
option('LZ4', type: 'string', value: 'lz4',
description: 'Path to lz4 binary')
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 1597b8e8127..4fa5c7a793f 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -23,6 +23,9 @@
#include "utils/memdebug.h"
#include "utils/memutils.h"
+#ifdef USE_SIMDSORT
+void keyvalue_qsort_float_sizet(float *, size_t *, size_t);
+#endif
/*
* The previous List implementation, since it used a separate palloc chunk
@@ -1655,6 +1658,222 @@ list_copy_deep(const List *oldlist)
return newlist;
}
+#ifdef USE_SIMDSORT
+/*
+ * Checks whether the given index is in between the first and the last index of a sequence of the specified length..
+ * Returns true if the index is neither the first element nor the last element.
+ */
+static bool
+is_not_boundary(int index, int length)
+{
+ return index != 0 && index != length - 1;
+}
+
+/*
+ * Checks at the given index, if start of the tie interval is found in the key array.
+ * Returns true if the start of a tie interval is found at the given index, otherwise returns false.
+ */
+static bool
+is_start_found(int index, int length, float *key)
+{
+ if (is_not_boundary(index, length))
+ {
+ return (key[index] == key[index + 1]) && (key[index] != key[index - 1]);
+ }
+ else
+ {
+ return (index == 0) && (key[index] == key[index + 1]);
+ }
+}
+
+/*
+ * Checks at the given index, if end of the tie interval is found in the key array.
+ * Returns true if the end of the tie interval is found at the given index, otherwise returns false.
+ */
+static bool
+is_end_found(int index, int length, float *key)
+{
+ if (is_not_boundary(index, length))
+ {
+ return (key[index] != key[index + 1]) && (key[index] == key[index - 1]);
+ }
+ else
+ {
+ return (index == length - 1) && (key[index] == key[index - 1]);
+ }
+}
+
+static void
+free_list_sort_resources(float *key, size_t *arg, bool *done)
+{
+ pfree(key);
+ pfree(arg);
+ pfree(done);
+}
+
+/*
+ * Sort a list using the x86-simd-sort library.
+ *
+ * The list is sorted in-place.
+ *
+ * The extract function provides an implementation to extract a float value from each ListCell.
+ * The ListCells are sorted based on the float value.
+ *
+ * The comparator function is the same as the one required by list_sort.
+ *
+ * Like qsort(), this provides no guarantees about sort stability
+ * for equal keys.
+ */
+static void
+list_sort_simd_float_impl(List *list, list_sort_extractor_float extract, list_sort_comparator cmp)
+{
+ typedef int (*qsort_comparator) (const void *a, const void *b);
+ int len;
+ ListCell *list_cells;
+
+ float *key;
+ size_t *arg;
+ bool *done;
+
+ size_t start;
+ size_t end;
+ bool start_found;
+
+ check_list_invariants(list);
+
+ len = list_length(list);
+ if (len <= 1)
+ {
+ return;
+ }
+
+ /*
+ * The null check for this is taken care in check_list_invariants function
+ */
+ list_cells = list->elements;
+ if (extract == NULL)
+ {
+ list_sort(list, cmp);
+ return;
+ }
+
+ /*
+ * key array stores the float values based on which ListCells are sorted.
+ * arg array stores the indexes corresponding to the sorted order after
+ * sorting is done. done array tracks rearrangement of the values based on
+ * the sorted indices
+ */
+ key = (float *) palloc(len * sizeof(float));
+ arg = (size_t *) palloc(len * sizeof(size_t));
+ done = (bool *) palloc(len * sizeof(bool));
+
+ if (key == NULL || arg == NULL || done == NULL)
+ {
+ if (key != NULL)
+ {
+ pfree(key);
+ }
+ if (arg != NULL)
+ {
+ pfree(arg);
+ }
+ if (done != NULL)
+ {
+ pfree(done);
+ }
+ list_sort(list, cmp);
+ return;
+ }
+
+ for (size_t ii = 0; ii < len; ii++)
+ {
+ key[ii] = extract(&list_cells[ii]);
+ arg[ii] = ii;
+ done[ii] = false;
+ }
+
+ keyvalue_qsort_float_sizet(key, arg, len);
+
+ /*
+ * The below loop rearranges each ListCell based on the sequence of
+ * indexes stored in the arg array which represents index of the elements
+ * in sorted order.
+ */
+ for (size_t ii = 0; ii < len; ii++)
+ {
+ size_t actual_index;
+ size_t sorted_index;
+
+ if (done[ii])
+ {
+ continue;
+ }
+ done[ii] = true;
+
+ actual_index = ii;
+ sorted_index = arg[ii];
+
+ while (ii != sorted_index)
+ {
+ ListCell temp = list_cells[actual_index];
+ ListCell sorted_cell = list_cells[sorted_index];
+
+ list_cells[actual_index] = sorted_cell;
+ list_cells[sorted_index] = temp;
+
+ done[sorted_index] = true;
+ actual_index = sorted_index;
+ sorted_index = arg[sorted_index];
+ }
+ }
+
+ if (cmp == NULL)
+ {
+ free_list_sort_resources(key, arg, done);
+ return;
+ }
+
+ start = 0;
+ end = 0;
+ start_found = false;
+
+ for (size_t ii = 0; ii < len; ii++)
+ {
+ if (!start_found && is_start_found(ii, len, key))
+ {
+ start = ii;
+ start_found = true;
+ }
+ else if (is_end_found(ii, len, key))
+ {
+ ListCell *start_ptr = &list_cells[start];
+
+ end = ii;
+
+ qsort(start_ptr, end - start + 1, sizeof(ListCell), (qsort_comparator) cmp);
+ start_found = false;
+ }
+ }
+
+ free_list_sort_resources(key, arg, done);
+}
+#endif
+
+/*
+ * Directs sorting to the SIMD-enabled version of list_sort if Postgres is built
+ * with x86-simd-sort support, else it directs it to list_sort.
+ */
+void
+list_sort_simd_float(List *list, list_sort_extractor_float extract, list_sort_comparator cmp)
+{
+#ifdef USE_SIMDSORT
+ list_sort_simd_float_impl(list, extract, cmp);
+#else
+ list_sort(list, cmp);
+#endif
+
+}
+
/*
* Sort a list according to the specified comparator function.
*
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index a872fc501e0..565f9948a1b 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -683,4 +683,6 @@ extern void list_sort(List *list, list_sort_comparator cmp);
extern int list_int_cmp(const ListCell *p1, const ListCell *p2);
extern int list_oid_cmp(const ListCell *p1, const ListCell *p2);
+typedef float (*list_sort_extractor_float) (const ListCell *a);
+extern void list_sort_simd_float(List *list, list_sort_extractor_float extract, list_sort_comparator cmp);
#endif /* PG_LIST_H */
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 07b2f798abd..b5eed4ae89d 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -253,6 +253,9 @@
/* Define to 1 if you have the `wldap32' library (-lwldap32). */
#undef HAVE_LIBWLDAP32
+/* Define to 1 if you have the `x86simdsortcpp' library (-lx86simdsortcpp). */
+#undef HAVE_LIBX86SIMDSORTCPP
+
/* Define to 1 if you have the `xml2' library (-lxml2). */
#undef HAVE_LIBXML2
@@ -688,6 +691,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to 1 to build with SIMDSORT support. (--with-simdsort) */
+#undef USE_SIMDSORT
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index c0d3cf0e14b..74f9c8b4c6c 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -24,6 +24,7 @@ SUBDIRS = \
test_integerset \
test_json_parser \
test_lfind \
+ test_listsort \
test_misc \
test_oat_hooks \
test_parser \
diff --git a/src/test/modules/test_listsort/.gitignore b/src/test/modules/test_listsort/.gitignore
new file mode 100644
index 00000000000..5dcb3ff9723
--- /dev/null
+++ b/src/test/modules/test_listsort/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_listsort/Makefile b/src/test/modules/test_listsort/Makefile
new file mode 100644
index 00000000000..d7845c5e763
--- /dev/null
+++ b/src/test/modules/test_listsort/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_listsort/Makefile
+
+MODULE_big = test_listsort
+OBJS = \
+ $(WIN32RES) \
+ test_listsort.o
+PGFILEDESC = "test_listsort - example of a custom sort for floating point numbers"
+
+EXTENSION = test_listsort
+DATA = test_listsort--1.0.sql
+
+REGRESS = test_listsort
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_listsort
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_listsort/README b/src/test/modules/test_listsort/README
new file mode 100644
index 00000000000..dc1161c36aa
--- /dev/null
+++ b/src/test/modules/test_listsort/README
@@ -0,0 +1,10 @@
+test_listsort is a module for stress testing the list_sort implementation based on the x86-simd-sort library.
+It generates an array of ListCells and sorts it based on the float value held by each ListCell.
+
+One function is provided:
+
+test_listsort (arg1 integer, arg2 integer, arg3 integer, arg4 boolean) returns float[]
+
+arg1 is the size of the ListCell array to be sorted.
+arg2 and arg3 are used to generate ties. This is used while generating test data.
+arg4 is a flag to select the SIMD-enabled (using the x86-simd-sort library) or qsort version of list_sort
diff --git a/src/test/modules/test_listsort/expected/test_listsort.out b/src/test/modules/test_listsort/expected/test_listsort.out
new file mode 100644
index 00000000000..6b986cbde80
--- /dev/null
+++ b/src/test/modules/test_listsort/expected/test_listsort.out
@@ -0,0 +1,13 @@
+CREATE EXTENSION test_listsort;
+SELECT test_listsort(10000, 500, 50, false);
+ test_listsort
+-------------------------------------------------------------------------------------------------------------------------------------
+ {1.8109567e-06,2.763141e-05,0.00040355558,0.000526655,0.0007317057,0.0007829778,0.0008651167,0.0008660783,0.0008709226,0.001022493}
+(1 row)
+
+SELECT test_listsort(10000, 500, 50, true);
+ test_listsort
+-------------------------------------------------------------------------------------------------------------------------------------
+ {1.8109567e-06,2.763141e-05,0.00040355558,0.000526655,0.0007317057,0.0007829778,0.0008651167,0.0008660783,0.0008709226,0.001022493}
+(1 row)
+
diff --git a/src/test/modules/test_listsort/sql/test_listsort.sql b/src/test/modules/test_listsort/sql/test_listsort.sql
new file mode 100644
index 00000000000..0cfb8acc781
--- /dev/null
+++ b/src/test/modules/test_listsort/sql/test_listsort.sql
@@ -0,0 +1,4 @@
+CREATE EXTENSION test_listsort;
+
+SELECT test_listsort(10000, 500, 50, false);
+SELECT test_listsort(10000, 500, 50, true);
diff --git a/src/test/modules/test_listsort/test_listsort--1.0.sql b/src/test/modules/test_listsort/test_listsort--1.0.sql
new file mode 100644
index 00000000000..573f6c68972
--- /dev/null
+++ b/src/test/modules/test_listsort/test_listsort--1.0.sql
@@ -0,0 +1,9 @@
+/* src/test/modules/test_listsort/test_listsort--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_listsort" to load this file. \quit
+
+CREATE FUNCTION test_listsort (arg1 integer, arg2 integer, arg3 integer, arg4 boolean)
+RETURNS float[]
+STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
\ No newline at end of file
diff --git a/src/test/modules/test_listsort/test_listsort.c b/src/test/modules/test_listsort/test_listsort.c
new file mode 100644
index 00000000000..77bc67189cf
--- /dev/null
+++ b/src/test/modules/test_listsort/test_listsort.c
@@ -0,0 +1,210 @@
+/*-------------------------------------------------------------------------
+ *
+ * test_listsort.c
+ * Simple example of list_sort implementation using x86-simd-sort library
+ *
+ * Copyright (c) 2007-2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_listsort/test_listsort.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+#include <signal.h>
+
+#include "funcapi.h"
+#include "utils/array.h"
+
+PG_MODULE_MAGIC;
+
+typedef struct SortTestData
+{
+ float value;
+ int tie_breaker;
+} SortTestData;
+
+static int
+tie_breaker(const ListCell *a, const ListCell *b)
+{
+ SortTestData *sort_test_data = (SortTestData *) a->ptr_value;
+ int val1 = sort_test_data->tie_breaker;
+
+ SortTestData *sort_test_data2 = (SortTestData *) b->ptr_value;
+ int val2 = sort_test_data2->tie_breaker;
+
+ if (val1 > val2)
+ {
+ return 1;
+ }
+ else if (val1 < val2)
+ {
+ return -1;
+ }
+ else
+ {
+ return 0;
+ }
+ return 1;
+}
+
+static int
+comparator(const ListCell *a, const ListCell *b)
+{
+ SortTestData *sort_test_data = (SortTestData *) a->ptr_value;
+ float val1 = sort_test_data->value;
+
+ SortTestData *sort_test_data2 = (SortTestData *) b->ptr_value;
+ float val2 = sort_test_data2->value;
+
+ if (val1 > val2)
+ {
+ return 1;
+ }
+ else if (val1 < val2)
+ {
+ return -1;
+ }
+ else
+ {
+ return tie_breaker(a, b);
+ }
+}
+
+static float
+get_value(const ListCell *a)
+{
+ SortTestData *sort_test_data = (SortTestData *) a->ptr_value;
+
+ return sort_test_data->value;
+}
+
+/* Creates ListCells with each ListCell having a float value.
+ * The max_random and tie_case_limit parameters are used to introduce ties.
+ */
+static ListCell **
+create_list_cell(ListCell *list_cells, int size, int max_random, int tie_case_limit)
+{
+ ListCell **list_cell_ptrs = (ListCell **) palloc(size * sizeof(ListCell *));
+
+ if (list_cell_ptrs == NULL)
+ {
+ elog(ERROR, "error allocating memory");
+ }
+ srand(42);
+
+ for (int i = 0; i < size; i++)
+ {
+ ListCell current_cell;
+ ListCell *cell = (ListCell *) palloc(sizeof(ListCell));
+ SortTestData *sort_test_data;
+ float random_float;
+ int random_number;
+ int bounded_random_number;
+
+ cell = (ListCell *) palloc(sizeof(ListCell));
+ if (cell == NULL)
+ {
+ elog(ERROR, "error allocating memory");
+ }
+
+ sort_test_data = (SortTestData *) palloc(sizeof(SortTestData));
+ if (sort_test_data == NULL)
+ {
+ elog(ERROR, "error allocating memory");
+ }
+
+ random_float = 0;
+ random_number = rand();
+ bounded_random_number = random_number % max_random;
+ if (bounded_random_number > 0 && bounded_random_number <= tie_case_limit)
+ {
+ random_float = bounded_random_number;
+ }
+ else
+ {
+ random_float = (float) random_number / (float) RAND_MAX;
+ }
+
+ sort_test_data->value = random_float;
+ sort_test_data->tie_breaker = i;
+ cell->ptr_value = sort_test_data;
+ current_cell = *cell;
+ list_cells[i] = current_cell;
+ list_cell_ptrs[i] = cell;
+ }
+
+ return list_cell_ptrs;
+}
+
+Datum test_listsort(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(test_listsort);
+Datum
+test_listsort(PG_FUNCTION_ARGS)
+{
+ int size = PG_GETARG_INT32(0);
+ int random_number = PG_GETARG_INT32(1);
+ int tie_breaker_limit = PG_GETARG_INT32(2);
+ bool call_simd_sort = PG_GETARG_BOOL(3);
+
+ ListCell *list_cells;
+ ListCell **list_cell_ptrs;
+ List *list;
+ Datum *array_elements;
+ ArrayType *result;
+
+ list_cells = (ListCell *) palloc(size * sizeof(ListCell));
+ if (list_cells == NULL)
+ {
+ elog(ERROR, "error allocating memory");
+ }
+
+ list_cell_ptrs = create_list_cell(list_cells, size, random_number, tie_breaker_limit);
+ if (list_cell_ptrs == NULL)
+ {
+ elog(ERROR, "error allocating memory");
+ }
+
+ list = (List *) palloc(sizeof(List));
+ if (list == NULL)
+ {
+ elog(ERROR, "error allocating memory");
+ }
+
+ list->elements = list_cells;
+ list->length = size;
+ list->max_length = size;
+ list->type = T_List;
+
+ if (call_simd_sort)
+ {
+ list_sort_simd_float(list, get_value, comparator);
+ }
+ else
+ {
+ list_sort(list, comparator);
+ }
+
+ array_elements = (Datum *) palloc(sizeof(Datum) * 10);
+ for (int i = 0; i < 10; i++)
+ {
+ array_elements[i] = Float4GetDatum(get_value(&list_cells[i]));
+ }
+
+ result = construct_array_builtin(array_elements, 10, FLOAT4OID);
+
+ pfree(array_elements);
+ pfree(list_cells);
+ pfree(list);
+ for (int i = 0; i < size; i++)
+ {
+ pfree(list_cell_ptrs[i]->ptr_value);
+ pfree(list_cell_ptrs[i]);
+ }
+ pfree(list_cell_ptrs);
+
+ PG_RETURN_ARRAYTYPE_P(result);
+}
diff --git a/src/test/modules/test_listsort/test_listsort.control b/src/test/modules/test_listsort/test_listsort.control
new file mode 100644
index 00000000000..8952f2aef87
--- /dev/null
+++ b/src/test/modules/test_listsort/test_listsort.control
@@ -0,0 +1,5 @@
+# test_listsort extension
+comment = 'example of a custom quick sort for sorting floating point numbers'
+default_version = '1.0'
+module_pathname = '$libdir/test_listsort'
+relocatable = true
diff --git a/src/test/regress/expected/list_sort_simd.out b/src/test/regress/expected/list_sort_simd.out
new file mode 100644
index 00000000000..8d79744cd27
--- /dev/null
+++ b/src/test/regress/expected/list_sort_simd.out
@@ -0,0 +1,87 @@
+--
+-- Tests for list sort
+--
+-- directory paths and dlsuffix are passed to us in environment variables
+\getenv libdir PG_LIBDIR
+\getenv dlsuffix PG_DLSUFFIX
+\set regresslib :libdir '/regress' :dlsuffix
+CREATE FUNCTION test_list_sort_simd_float (arg1 integer, arg2 integer)
+ RETURNS float[]
+ AS :'regresslib'
+ LANGUAGE C STRICT;
+CREATE FUNCTION test_list_sort_simd_float_random (arg1 integer, arg2 integer, arg3 integer, arg4 boolean)
+ RETURNS float[]
+ AS :'regresslib'
+ LANGUAGE C STRICT;
+SELECT test_list_sort_simd_float(100, 10);
+ test_list_sort_simd_float
+---------------------------
+ {1,2,3,4,5,6,7,8,9,10}
+(1 row)
+
+SELECT test_list_sort_simd_float(10, 5);
+ test_list_sort_simd_float
+---------------------------
+ {1,2,3,4,5,6,7,8,9,10}
+(1 row)
+
+SELECT test_list_sort_simd_float_random(100, 20, 2, true);
+ test_list_sort_simd_float_random
+------------------------------------------------------------------------------------------------------------------
+ {0.024923936,0.03346995,0.08167228,0.08531265,0.10674526,0.114986524,0.11994517,0.1355998,0.13572839,0.16894749}
+(1 row)
+
+SELECT test_list_sort_simd_float_random(2, 20, 2, true);
+ test_list_sort_simd_float_random
+----------------------------------
+ {0.03346995,0.32996422}
+(1 row)
+
+SELECT test_list_sort_simd_float_random(1000, 200, 20, true);
+ test_list_sort_simd_float_random
+--------------------------------------------------------------------------------------------------------------------------------
+ {0.0007317057,0.004217157,0.005766493,0.0061016046,0.0061097275,0.0065996465,0.0067929444,0.0072418055,0.008422677,0.00864573}
+(1 row)
+
+SELECT test_list_sort_simd_float_random(10000, 20, 2, true);
+ test_list_sort_simd_float_random
+-------------------------------------------------------------------------------------------------------------------------------------
+ {1.8109567e-06,2.763141e-05,0.00040355558,0.000526655,0.0007317057,0.0007829778,0.0008651167,0.0008660783,0.0008709226,0.001022493}
+(1 row)
+
+SELECT test_list_sort_simd_float_random(10000, 200, 50, true);
+ test_list_sort_simd_float_random
+-----------------------------------------------------------------------------------------------------------------------------------
+ {1.8109567e-06,2.763141e-05,0.000526655,0.0007317057,0.0008660783,0.0008709226,0.001022493,0.0012402055,0.0024320218,0.002435991}
+(1 row)
+
+SELECT test_list_sort_simd_float_random(10000, 2000, 500, true);
+ test_list_sort_simd_float_random
+-------------------------------------------------------------------------------------------------------------------------------------
+ {1.8109567e-06,2.763141e-05,0.00040355558,0.000526655,0.0007317057,0.0007829778,0.0008651167,0.0008660783,0.001022493,0.0012402055}
+(1 row)
+
+SELECT test_list_sort_simd_float_random(100000, 20000, 5000, true);
+ test_list_sort_simd_float_random
+------------------------------------------------------------------------------------------------------------------------------------------
+ {3.5329722e-06,2.763141e-05,6.246893e-05,7.02166e-05,7.395493e-05,9.09497e-05,0.000111196656,0.000111584086,0.00012805313,0.00015487056}
+(1 row)
+
+SELECT test_list_sort_simd_float_random(100000, 20000, 1000, true);
+ test_list_sort_simd_float_random
+------------------------------------------------------------------------------------------------------------------------------------------
+ {1.8109567e-06,3.5329722e-06,2.763141e-05,6.246893e-05,7.02166e-05,7.395493e-05,9.09497e-05,9.3645416e-05,0.000111196656,0.000111584086}
+(1 row)
+
+SELECT test_list_sort_simd_float_random(100, 20, 2, false);
+ test_list_sort_simd_float_random
+------------------------------------------------------------------------------------------------------------------
+ {0.024923936,0.03346995,0.08167228,0.08531265,0.10674526,0.114986524,0.11994517,0.1355998,0.13572839,0.16894749}
+(1 row)
+
+SELECT test_list_sort_simd_float_random(10000, 3000, 300, false);
+ test_list_sort_simd_float_random
+-------------------------------------------------------------------------------------------------------------------------------------
+ {1.8109567e-06,2.763141e-05,0.00040355558,0.000526655,0.0007317057,0.0007829778,0.0008651167,0.0008660783,0.0008709226,0.001022493}
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 1edd9e45ebb..c94aef1522b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -98,7 +98,7 @@ test: publication subscription
# Another group of parallel tests
# select_views depends on create_view
# ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock indirect_toast equivclass
+test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock indirect_toast list_sort_simd equivclass
# ----------
# Another group of parallel tests (JSON related)
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index 5c7158f72b1..db12128669c 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -544,6 +544,272 @@ test_canonicalize_path(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(path));
}
+typedef struct SortTestData
+{
+ float value;
+ int tiebreaker;
+} SortTestData;
+
+static int
+tie_breaker(const ListCell *a, const ListCell *b)
+{
+ SortTestData *sorttestdata1 = (SortTestData *) a->ptr_value;
+ int val1 = sorttestdata1->tiebreaker;
+
+ SortTestData *sorttestdata2 = (SortTestData *) b->ptr_value;
+ int val2 = sorttestdata2->tiebreaker;
+
+ if (val1 > val2)
+ {
+ return 1;
+ }
+ else if (val1 < val2)
+ {
+ return -1;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+static float
+get_value(const ListCell *a)
+{
+ SortTestData *sorttestdata = (SortTestData *) a->ptr_value;
+
+ return sorttestdata->value;
+}
+
+static int
+get_tie_breaker_value(const ListCell *a)
+{
+ SortTestData *sorttestdata = (SortTestData *) a->ptr_value;
+
+ return sorttestdata->tiebreaker;
+}
+
+static int
+custom_comparator(const ListCell *a, const ListCell *b)
+{
+ float val1 = get_value(a);
+ float val2 = get_value(b);
+
+ if (val1 > val2)
+ {
+ return 1;
+ }
+ else if (val1 < val2)
+ {
+ return -1;
+ }
+ else
+ {
+ return tie_breaker(a, b);;
+ }
+}
+
+/* Test for list_sort_simd_float starting from a list in descending order. The list is sorted in ascending order.
+ */
+Datum test_list_sort_simd_float(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(test_list_sort_simd_float);
+Datum
+test_list_sort_simd_float(PG_FUNCTION_ARGS)
+{
+ int size = PG_GETARG_INT32(0);
+
+ float currentsize = (float) size;
+ ListCell *listcells;
+ ListCell **listcellptrs;
+ List *list;
+ Datum *array_elements;
+ ArrayType *result;
+
+ listcells = (ListCell *) palloc(size * sizeof(ListCell));
+ Assert(listcells != NULL);
+
+ listcellptrs = (ListCell **) palloc(size * sizeof(ListCell *));
+ Assert(listcellptrs != NULL);
+
+ for (int i = 0; i < size; i++)
+ {
+ ListCell currentcell;
+ ListCell *cell;
+ SortTestData *sorttestdata;
+
+ cell = (ListCell *) palloc(sizeof(ListCell));
+ Assert(cell != NULL);
+
+ sorttestdata = (SortTestData *) palloc(sizeof(SortTestData));
+ Assert(sorttestdata != NULL);
+
+ sorttestdata->value = currentsize;
+ currentsize = currentsize - 1;
+ sorttestdata->tiebreaker = i;
+ cell->ptr_value = sorttestdata;
+ currentcell = *cell;
+ listcells[i] = currentcell;
+ listcellptrs[i] = cell;
+ }
+
+ list = (List *) palloc(sizeof(List));
+ list->elements = listcells;
+ list->length = size;
+ list->max_length = size;
+ list->type = T_List;
+
+ list_sort_simd_float(list, get_value, custom_comparator);
+
+ for (int i = 0; i < size; i++)
+ {
+ Assert(get_value(&listcells[i]) == (float) (i + 1));
+ }
+
+ array_elements = (Datum *) palloc(sizeof(Datum) * 10);
+ for (int i = 0; i < 10; i++)
+ {
+ array_elements[i] = Float4GetDatum(get_value(&listcells[i]));
+ }
+
+ result = construct_array_builtin(array_elements, 10, FLOAT4OID);
+
+ pfree(array_elements);
+ pfree(listcells);
+ for (int i = 0; i < size; i++)
+ {
+ pfree(listcellptrs[i]->ptr_value);
+ pfree(listcellptrs[i]);
+ }
+ pfree(listcellptrs);
+
+ PG_RETURN_ARRAYTYPE_P(result);
+}
+
+/* Creates ListCells with each ListCell having a float value.
+ * The max_random and tie_case_limit parameters are used to introduce ties.
+ */
+static ListCell **
+create_list_cell(ListCell *list_cells, int size, int max_random, int tie_case_limit)
+{
+ ListCell **list_cell_ptrs = (ListCell **) palloc(size * sizeof(ListCell *));
+
+ for (int i = 0; i < size; i++)
+ {
+ ListCell current_cell;
+ ListCell *cell = (ListCell *) palloc(sizeof(ListCell));
+
+ SortTestData *sort_test_data = (SortTestData *) palloc(sizeof(SortTestData));
+
+ float random_float = 0;
+ int random_number = rand();
+ int bounded_random_number = random_number % max_random;
+
+ if (bounded_random_number > 0 && bounded_random_number <= tie_case_limit)
+ {
+ random_float = bounded_random_number;
+ }
+ else
+ {
+ random_float = (float) random_number / (float) RAND_MAX;
+ }
+
+ sort_test_data->value = random_float;
+ sort_test_data->tiebreaker = i;
+ cell->ptr_value = sort_test_data;
+ current_cell = *cell;
+ list_cells[i] = current_cell;
+ list_cell_ptrs[i] = cell;
+ }
+
+ return list_cell_ptrs;
+}
+
+/* Test for list_sort_simd_float starting from a list in random order. The list is sorted in ascending order.
+ */
+Datum test_list_sort_simd_float_random(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(test_list_sort_simd_float_random);
+Datum
+test_list_sort_simd_float_random(PG_FUNCTION_ARGS)
+{
+ int size = PG_GETARG_INT32(0);
+ int max_random = PG_GETARG_INT32(1);
+ int tie_case_limit = PG_GETARG_INT32(2);
+ bool call_simd_sort = PG_GETARG_BOOL(3);
+
+ ListCell *list_cells;
+ ListCell **list_cell_ptrs;
+ List *list;
+ Datum *array_elements;
+ ArrayType *result;
+ int result_len;
+
+ srand(42);
+
+ list_cells = (ListCell *) palloc(size * sizeof(ListCell));
+ Assert(list_cells != NULL);
+
+ list_cell_ptrs = create_list_cell(list_cells, size, max_random, tie_case_limit);
+
+ list = (List *) palloc(sizeof(List));
+ Assert(list != NULL);
+
+ list->elements = list_cells;
+ list->length = size;
+ list->max_length = size;
+ list->type = T_List;
+
+ if (call_simd_sort)
+ {
+ list_sort_simd_float(list, get_value, custom_comparator);
+ }
+ else
+ {
+ list_sort_simd_float(list, NULL, custom_comparator);
+ }
+
+ for (int i = 0; i < size - 1; i++)
+ {
+ Assert(get_value(&list_cells[i]) <= get_value(&list_cells[i + 1]));
+ }
+
+ for (int i = 0; i < size - 1; i++)
+ {
+ if (get_value(&list_cells[i]) == get_value(&list_cells[i + 1]))
+ {
+ Assert(get_tie_breaker_value(&list_cells[i]) <= get_tie_breaker_value(&list_cells[i + 1]));
+ }
+ }
+
+ /*
+ * we choose to return an array of size 10 for larger sort sizes ( > 10).
+ * The reason behind this is to make sure the corresponding .out file does
+ * not have to show all the sorted floats especially when sort size is
+ * large. Otherwise this will make the .out file difficult to maintain.
+ */
+ result_len = (size < 10 ? size : 10);
+ array_elements = (Datum *) palloc(sizeof(Datum) * result_len);
+ for (int i = 0; i < result_len; i++)
+ {
+ array_elements[i] = Float4GetDatum(get_value(&list_cells[i]));
+ }
+
+ result = construct_array_builtin(array_elements, result_len, FLOAT4OID);
+
+ pfree(array_elements);
+ pfree(list_cells);
+ pfree(list);
+ for (int i = 0; i < size; i++)
+ {
+ pfree(list_cell_ptrs[i]->ptr_value);
+ pfree(list_cell_ptrs[i]);
+ }
+ pfree(list_cell_ptrs);
+
+ PG_RETURN_ARRAYTYPE_P(result);
+}
+
PG_FUNCTION_INFO_V1(make_tuple_indirect);
Datum
make_tuple_indirect(PG_FUNCTION_ARGS)
diff --git a/src/test/regress/sql/list_sort_simd.sql b/src/test/regress/sql/list_sort_simd.sql
new file mode 100644
index 00000000000..a2eec3633c1
--- /dev/null
+++ b/src/test/regress/sql/list_sort_simd.sql
@@ -0,0 +1,32 @@
+--
+-- Tests for list sort
+--
+
+-- directory paths and dlsuffix are passed to us in environment variables
+\getenv libdir PG_LIBDIR
+\getenv dlsuffix PG_DLSUFFIX
+
+\set regresslib :libdir '/regress' :dlsuffix
+
+CREATE FUNCTION test_list_sort_simd_float (arg1 integer, arg2 integer)
+ RETURNS float[]
+ AS :'regresslib'
+ LANGUAGE C STRICT;
+
+CREATE FUNCTION test_list_sort_simd_float_random (arg1 integer, arg2 integer, arg3 integer, arg4 boolean)
+ RETURNS float[]
+ AS :'regresslib'
+ LANGUAGE C STRICT;
+
+SELECT test_list_sort_simd_float(100, 10);
+SELECT test_list_sort_simd_float(10, 5);
+SELECT test_list_sort_simd_float_random(100, 20, 2, true);
+SELECT test_list_sort_simd_float_random(2, 20, 2, true);
+SELECT test_list_sort_simd_float_random(1000, 200, 20, true);
+SELECT test_list_sort_simd_float_random(10000, 20, 2, true);
+SELECT test_list_sort_simd_float_random(10000, 200, 50, true);
+SELECT test_list_sort_simd_float_random(10000, 2000, 500, true);
+SELECT test_list_sort_simd_float_random(100000, 20000, 5000, true);
+SELECT test_list_sort_simd_float_random(100000, 20000, 1000, true);
+SELECT test_list_sort_simd_float_random(100, 20, 2, false);
+SELECT test_list_sort_simd_float_random(10000, 3000, 300, false);
\ No newline at end of file
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9a3bee93dec..4521e56165d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2717,6 +2717,7 @@ SortShimExtra
SortState
SortSupport
SortSupportData
+SortTestData
SortTuple
SortTupleComparator
SortedPoint
--
2.25.1
On Tue, Feb 18, 2025 at 1:49 PM R, Rakshit <rakshit.r@intel.com> wrote:
Hi All,
Thank you very much for your feedback! We investigated the recommendations as we developed the current patch. Please refer to the comments below.
I'd suggest targeting pg_qsort() directly, instead of list_sort(). list_sort() is not used in very performance critical parts.
Using x86-simd-sort would require a new pg_qsort variant taking an extractor function. The new API would still have to bubble up to functions like list_sort, which internally call pg_qsort. We targeted list_sort first, as we were aware of at least one use case that benefits from SIMD sorting (pgvector). As we target more use cases (such as tuple sort), we may need to move the SIMD-enabled implementation to a common location, and a new variant of pg_qsort may be a good candidate.
I'd suggest proposing this to the pgvector project directly, since pgvector would immediately benefit.
Yes, we’ll share the performance gains on HNSW index build time with the pgvector community. However, other users of list_sort (e.g., other extensions) may benefit from the simd-sort version of list_sort as well. As it is not a pgvector-specific optimization, we propose it for PostgreSQL.
I don't think "another extension might use it someday" makes a very
strong case, particularly for something that requires a new
dependency.
If you could use this to speed up tuple sorting, that would be much more interesting for PostgreSQL itself.
Enabling x86-simd-sort for tuple sorting is in development. We observed promising results, but there is still work to do (e.g., to ensure there are no regressions in different conditions). We are planning to share another patch for tuple sort building on this one.
Note that our current implemention is highly optimized for
low-cardinality inputs. This is needed for aggregate queries. I found
this write-up of a couple scalar and vectorized sorts, and they show
this library doing very poorly on very-low cardinality inputs. I would
look into that before trying to get something in shape to share.
https://github.com/Voultapher/sort-research-rs/blob/main/writeup/intel_avx512/text.md
There is also the question of hardware support. It seems AVX-512 is
not supported well on client side, where most developers work. And
availability of any flavor is not guaranteed on server either.
Something to keep in mind.
--
John Naylor
Amazon Web Services
Note that our current implemention is highly optimized for low-cardinality inputs.
This is needed for aggregate queries. I found this write-up of a couple scalar and
vectorized sorts, and they show this library doing very poorly on very-low
cardinality inputs. I would look into that before trying to get something in shape to
share.https://github.com/Voultapher/sort-research-
rs/blob/main/writeup/intel_avx512/text.md
That write up is fairly old and those perf problems has subsequently been fixed. See https://github.com/intel/x86-simd-sort/pull/127 and https://github.com/intel/x86-simd-sort/pull/168. I still suggest measuring perf here for thoroughness.
There is also the question of hardware support. It seems AVX-512 is not
supported well on client side, where most developers work. And availability of
any flavor is not guaranteed on server either.
Something to keep in mind.
simd-sort also works on avx2 which is widely available. I would suggest benchmarking on one of the client laptops to measure the perf.
Raghuveer
Hi All,
Thank you so much for the feedback.
I don't think "another extension might use it someday" makes a very strong case,
particularly for something that requires a new dependency.
The x86-simdsort library is an optional dependency in Postgres. Also the new list sort implementation which uses the x86-simdsort library does not impact any of the existing workflows in Postgres. We have at least one use case where Pgvector gets a gain of 7 - 10 % in HNSW index build time using the SIMD sort implementation. Hence we feel that this patch could be up streamed further. Open to further discussion on the same.
Note that our current implemention is highly optimized for low-cardinality inputs.
This is needed for aggregate queries. I found this write-up of a
couple scalar and vectorized sorts, and they show this library doing
very poorly on very-low cardinality inputs. I would look into that
before trying to get something in shape to share.https://github.com/Voultapher/sort-research-
rs/blob/main/writeup/intel_avx512/text.md
We ran our extension to stress list sort with low cardinality inputs. For eg, for an array of size 100k having repeated values in the range 1-10 we still see a gain of around 20% in throughput.
We will collect more data for low cardinality inputs and with AVX2 too.
Thanks and regards
Rakshit Rajeev
On Fri, Feb 28, 2025 at 12:43 PM R, Rakshit <rakshit.r@intel.com> wrote:
I don't think "another extension might use it someday" makes a very strong case,
particularly for something that requires a new dependency.The x86-simdsort library is an optional dependency in Postgres. Also the new list sort implementation which uses the x86-simdsort library does not impact any of the existing workflows in Postgres.
"Optional" and "Does not impact" are not great selling points to get
us to take a 1500 line patch. As we told you in November, list_sort
isn't critical for us. You need to start with the user and work
backwards to the technology. Don't pick a technology and try to sell
people on using it.
We ran our extension to stress list sort with low cardinality inputs. For eg, for an array of size 100k having repeated values in the range 1-10 we still see a gain of around 20% in throughput.
We will collect more data for low cardinality inputs and with AVX2 too.
Thanks for the news, those are encouraging results.
--
John Naylor
Amazon Web Services
I don't think "another extension might use it someday" makes a very strong case,
particularly for something that requires a new dependency.The x86-simdsort library is an optional dependency in Postgres. Also the new list sort implementation which uses the x86-simdsort library does not impact any of the existing workflows in Postgres.
"Optional" and "Does not impact" are not great selling points to get
us to take a 1500 line patch. As we told you in November, list_sort
isn't critical for us. You need to start with the user and work
backwards to the technology. Don't pick a technology and try to sell
people on using it.
Agree on starting from the user problem and work towards technology. As stated upthread, the problem being addressed here is optimizing pg_vector list_sort (which relies on postgres list_sort) to speed up HNSW index construction. The results show 7-10% improvements on vector workloads using the library.
Given the same x86-simdsort library is intended to optimize 1) list_sort 2) tuple sort, it didn't make sense to duplicate the work to integrate it in pg_vector for list_sort, and then again in postgres for tuple-sort.
We're trying to tackle list_sort and tuple_sort in separate patches using the same x86-simdsort library, with the goal to optimize both. Let us know if this approach is preferred and if the list_sort patch could be reviewed and any other tests we should do, or would you rather see tuple_sort benchmarks.
- Akash Shankaran
On Sat, Mar 1, 2025 at 1:23 PM Shankaran, Akash
<akash.shankaran@intel.com> wrote:
Given the same x86-simdsort library is intended to optimize 1) list_sort 2) tuple sort, it didn't make sense to duplicate the work to integrate it in pg_vector for list_sort, and then again in postgres for tuple-sort.
Thank you for the context -- the motivation is clear now.
I imagine this kind of integration work is messy and difficult, which
is why I would be reluctant to write something that way if I have a
choice.
We're trying to tackle list_sort and tuple_sort in separate patches using the same x86-simdsort library,
with the goal to optimize both. Let us know if this approach is
preferred and if the list_sort patch could be reviewed and any other
tests we should do, or would you rather see tuple_sort benchmarks.
The approach I actually prefer to is to hear about the high-level
architecture first, before being asked to look at code. Tuple sort has
special challenges, so when you're ready to start a new thread for
that, I'll be curious about your findings.
--
John Naylor
Amazon Web Services
On 2025-Mar-03, John Naylor wrote:
I imagine this kind of integration work is messy and difficult, which
is why I would be reluctant to write something that way if I have a
choice.
I've marked this commitfest item[1]https://commitfest.postgresql.org/patch/5607/ as Returned with Feedback. Please
feel free to reopen it once you have an updated patch to share. I think
John's reservations on whether to absorb another requisite shared
library is something to strongly consider -- I notice that x86-simd-sort
is not yet packaged by Debian, for instance.
Maybe your needs *are* served better by introducing simd-sort in
pgvector after all? Alternatively: would it be too bad to reimplement
simdsort in C within Postgres rather than using a third party library?
[1]: https://commitfest.postgresql.org/patch/5607/
--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
"If you have nothing to say, maybe you need just the right tool to help you
not say it." (New York Times, about Microsoft PowerPoint)
Hi Alvaro and John,
I think John's reservations on whether to absorb another requisite shared library is something to strongly consider -- I notice that x86-simd-sort is not yet packaged by Debian, for instance.
Maybe your needs *are* served better by introducing simd-sort in pgvector after all? Alternatively: would it be too bad to reimplement simdsort in C within Postgres rather than using a third party library?
Thanks for your feedback and discussions here.
I've marked this commitfest item[1] as Returned with Feedback. Please feel free to reopen it once you have an updated patch to share.
At this time, I'd like to withdraw the commitfest item [1]https://commitfest.postgresql.org/patch/5607/. The original developer who proposed the patch is no longer at Intel, and the x86-simd-sort library's future is also uncertain. Due to ongoing changes on our end at this time, we won't be able to support this work going forward. :- (
[1]: https://commitfest.postgresql.org/patch/5607/
Akash Shankaran