On Sun, Jul 4, 2021 at 12:27 AM Thomas Munro <thomas.mu...@gmail.com> wrote:
>
> Since you are experimenting with tuplesort and likely thinking similar
> thoughts, here's a patch I've been using to explore that area.  I've
> seen it get, for example, ~1.18x speedup for simple index builds in
> favourable winds (YMMV, early hacking results only).  Currently, it
> kicks in when the leading column is of type int4, int8, timestamp,
> timestamptz, date or text + friends (when abbreviatable, currently
> that means "C" and ICU collations only), while increasing the
> executable by only 8.5kB (Clang, amd64, -O2, no debug).
>
> These types are handled with just three specialisations.  Their custom
> "fast" comparators all boiled down to comparisons of datum bits,
> varying only in signedness and width, so I tried throwing them away
> and using 3 new common routines.  Then I extended
> tuplesort_sort_memtuples()'s pre-existing specialisation dispatch to
> recognise qualifying users of those and select 3 corresponding sort
> specialisations.

I got around to getting a benchmark together to serve as a starting point.
I based it off something I got from the archives, but don't remember where
(I seem to remember Tomas Vondra wrote the original, but not sure). To
start I just used types that were there already -- int, text, numeric. The
latter two won't be helped by this patch, but I wanted to keep something
like that so we can see what kind of noise variation there is. I'll
probably cut text out in the future and just keep numeric for that purpose.

I've attached both the script and a crude spreadsheet. I'll try to figure
out something nicer for future tests, and maybe some graphs. The
"comparison" sheet has the results side by side (min of five). There are 6
distributions of values:
- random
- sorted
- "almost sorted"
- reversed
- organ pipe (first half ascending, second half descending)
- rotated (sorted but then put the smallest at the end)
- random 0s/1s

I included both "select a" and "select *" to make sure we have the recent
datum sort optimization represented. The results look pretty good for ints
-- about the same speed up master gets going from tuple sorts to datum
sorts, and those got faster in turn also.

Next I think I'll run microbenchmarks on int64s with the test harness you
attached earlier, and experiment with the qsort parameters a bit.

I'm also attaching your tuplesort patch so others can see what exactly I'm
comparing.

--
John Naylor
EDB: http://www.enterprisedb.com
set -e

ROWS=$1
WORK_MEM=$2


function log {
	echo `date +%s` [`date +'%Y-%m-%d %H:%M:%S'`] $1
}

function create_tables {

	./inst/bin/psql > /dev/null  <<EOF
-- tables for source data
DROP TABLE IF EXISTS data_int;
CREATE TABLE data_int (a INT);
INSERT INTO  data_int SELECT i FROM generate_series(1, $ROWS) s(i);

DROP TABLE IF EXISTS data_flt;
CREATE TABLE data_flt (a FLOAT);
INSERT INTO  data_flt SELECT 100000 * random() FROM generate_series(1, $ROWS) s(i);

-- tables used for the actual testing
CREATE TABLE IF NOT EXISTS int_test         (a INT, b TEXT);
CREATE TABLE IF NOT EXISTS txt_test         (a TEXT, b TEXT);
CREATE TABLE IF NOT EXISTS num_test         (a NUMERIC, b TEXT);
EOF

}

function truncate_tables {

	log "truncating tables"

	./inst/bin/psql > /dev/null  <<EOF
TRUNCATE TABLE int_test;
TRUNCATE TABLE txt_test;
TRUNCATE TABLE num_test;
EOF

}

function vacuum_analyze {

	log "analyzing"

	./inst/bin/psql > /dev/null  <<EOF
VACUUM ANALYZE;
CHECKPOINT;
EOF

}

function prewarm {
	log "prewarming buffers"

	./inst/bin/psql > /dev/null  <<EOF
SELECT pg_prewarm(oid) FROM pg_class WHERE oid > 16384 AND relkind = 'r';
EOF

}

################# load test tables

function load_random {

	truncate_tables

	log "loading random"

	./inst/bin/psql > /dev/null  <<EOF
SET work_mem = '$WORK_MEM';

-- this needs randomization
INSERT INTO int_test SELECT a, md5(a::text) FROM data_int ORDER BY random();

-- these already are random
INSERT INTO txt_test SELECT md5(a::text), md5((a+1)::text) FROM data_int;
INSERT INTO num_test SELECT a, md5(a::text) FROM data_flt;
EOF

	prewarm

	vacuum_analyze

}

function load_sorted {

	truncate_tables

	log "loading sorted"

	./inst/bin/psql > /dev/null  <<EOF
SET work_mem = '$WORK_MEM';

INSERT INTO int_test SELECT a, md5(a::text) FROM data_int ORDER BY 1;
INSERT INTO txt_test SELECT md5(a::text), md5((a+1)::text) FROM data_int ORDER BY 1;
INSERT INTO num_test SELECT a, md5(a::text) FROM data_flt ORDER BY 1;
EOF

	prewarm

	vacuum_analyze

}

function load_almost_sorted {

	truncate_tables

	log "loading almost sorted"

	./inst/bin/psql > /dev/null  <<EOF
SET work_mem = '$WORK_MEM';

INSERT INTO int_test SELECT a, b FROM (
    SELECT a, md5(a::text) AS b, rank() OVER (ORDER BY a) AS r FROM data_int
) foo ORDER BY r + (100 * random());

INSERT INTO txt_test SELECT a, b FROM (
    SELECT md5(a::text) AS a, md5(((a+1))::text) AS b, rank() OVER (ORDER BY md5(a::text)) AS r FROM data_int
) foo ORDER BY r + (100 * random());

INSERT INTO num_test SELECT a, b FROM (
    SELECT a, md5(a::text) AS b, rank() OVER (ORDER BY a) AS r FROM data_flt
) foo ORDER BY r + (100 * random());
EOF

	prewarm

	vacuum_analyze

}

function load_reversed {

	truncate_tables

	log "loading reversed"

	./inst/bin/psql > /dev/null  <<EOF
SET work_mem = '$WORK_MEM';

INSERT INTO int_test SELECT a, md5(a::text) FROM data_int ORDER BY 1 DESC;
INSERT INTO txt_test SELECT md5(a::text), md5((a+1)::text) FROM data_int ORDER BY 1 DESC;
INSERT INTO num_test SELECT a, md5(a::text) FROM data_flt ORDER BY 1 DESC;
EOF

	prewarm

	vacuum_analyze

}

function load_organ_pipe {

	truncate_tables

	log "loading organ pipe"

	./inst/bin/psql > /dev/null  <<EOF
SET work_mem = '$WORK_MEM';

WITH c AS (SELECT count(*)/2 AS half from data_int)
INSERT INTO int_test
(SELECT a, md5(a::text) FROM data_int ORDER BY 1 ASC  LIMIT (SELECT half from c))
UNION ALL
(SELECT a, md5(a::text) FROM data_int ORDER BY 1 DESC LIMIT (SELECT half from c));

WITH c AS (SELECT count(*)/2 AS half from data_int)
INSERT INTO txt_test
(SELECT md5(a::text), md5((a+1)::text) FROM data_int ORDER BY 1 ASC  LIMIT (SELECT half from c))
UNION ALL
(SELECT md5(a::text), md5((a+1)::text) FROM data_int ORDER BY 1 DESC LIMIT (SELECT half from c));

WITH c AS (SELECT count(*)/2 AS half from num_test)
INSERT INTO num_test
(SELECT a, md5(a::text) FROM data_int ORDER BY 1 ASC  LIMIT (SELECT half from c))
UNION ALL
(SELECT a, md5(a::text) FROM data_int ORDER BY 1 DESC LIMIT (SELECT half from c));
EOF

	prewarm

	vacuum_analyze

}

function load_rotated {

	truncate_tables

	log "loading rotated"

	./inst/bin/psql > /dev/null  <<EOF
SET work_mem = '$WORK_MEM';

INSERT INTO int_test SELECT a, md5(a::text) FROM data_int;
INSERT INTO int_test VALUES (0, 'b');

INSERT INTO txt_test SELECT md5(a::text), md5((a+1)::text) FROM data_int ORDER BY 1;
INSERT INTO txt_test VALUES ('a', 'b');

INSERT INTO num_test SELECT a + 1.0 FROM data_flt ORDER BY 1;
INSERT INTO num_test VALUES (0.0, 'b');
EOF

	prewarm

	vacuum_analyze

}

function load_0_1 {

	truncate_tables

	log "loading 0 1"

	./inst/bin/psql > /dev/null  <<EOF
SET work_mem = '$WORK_MEM';

INSERT INTO int_test SELECT round(a), md5(a::text) FROM data_int;
INSERT INTO txt_test SELECT 
	case when a > 0.5 
	then 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
	else 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
	end
FROM data_int;
INSERT INTO num_test SELECT round(a), md5(a::text) FROM data_int;
EOF

	prewarm

	vacuum_analyze

}

################# run

function run_query {

	times=""
	group=$1
	wmem=$2
	workers=$3
	query=$4

	echo "--" `date` [`date +%s`] >> explains.log
	echo "-- $group rows=$ROWS work_mem=$wmem workers=$workers" >> explains.log

	./inst/bin/psql >> explains.log 2>&1 <<EOF
SET trace_sort=on;
SET work_mem='$wmem';
SET max_parallel_workers_per_gather=$workers;
explain $query
EOF

	s=`date +%s`

	for i in `seq 1 5`; do

		/usr/bin/time -f '%e' -o 'query.time' ./inst/bin/psql > /dev/null <<EOF
\pset pager off
\o /dev/null
SET trace_sort=on;
SET work_mem='$wmem';
SET max_parallel_workers_per_gather=$workers;
COPY ($query) TO '/dev/null'
EOF

		x=`cat query.time`
		times="$times\t$x"

	done

	e=`date +%s`

	echo -e "$group\t$ROWS\t$wmem\t$workers\t$s\t$e\t$query\t$times" >> results.csv
}

function run_index {

        times=""
        group=$1
        wmem=$2
        workers=$3
        query=$4

        times=""

	s=`date +%s`

    for i in `seq 1 5`; do

        /usr/bin/time -f '%e' -o 'query.time' ./inst/bin/psql > /dev/null <<EOF
\pset pager off
\o /dev/null
SET maintenance_work_mem='$wmem';
$query
EOF

                x=`cat query.time`
                times="$times\t$x"

        done

	e=`date +%s`

        echo -e "$group\t$ROWS\t$wmem\t$workers\t$s\t$e\t$query\t$times" >> results.csv
}

function run_queries {

#	for wm in '1MB' '8MB' '32MB' '128MB' '512MB' '1GB'; do
	for wm in $WORK_MEM; do

		log "running queries work_mem=$wm noparallel"

		run_query $1 $wm 0 'SELECT a FROM int_test ORDER BY 1 OFFSET '$ROWS''
		run_query $1 $wm 0 'SELECT * FROM int_test ORDER BY 1 OFFSET '$ROWS''
#		run_query $1 $wm 0 'SELECT a FROM int_test ORDER BY 1 DESC OFFSET '$ROWS''
#		run_query $1 $wm 0 'SELECT * FROM int_test ORDER BY 1 DESC OFFSET '$ROWS''
		run_index $1 $wm 0 'CREATE INDEX x ON int_test (a); DROP INDEX x'

		run_query $1 $wm 0 'SELECT a FROM txt_test ORDER BY 1 OFFSET '$ROWS''
		run_query $1 $wm 0 'SELECT * FROM txt_test ORDER BY 1 OFFSET '$ROWS''
#		run_query $1 $wm 0 'SELECT a FROM txt_test ORDER BY 1 DESC OFFSET '$ROWS''
#		run_query $1 $wm 0 'SELECT * FROM txt_test ORDER BY 1 DESC OFFSET '$ROWS''
		run_index $1 $wm 0 'CREATE INDEX x ON txt_test (a); DROP INDEX x'

		run_query $1 $wm 0 'SELECT * FROM num_test ORDER BY 1 OFFSET '$ROWS''
#		run_query $1 $wm 0 'SELECT * FROM num_test ORDER BY 1 DESC OFFSET '$ROWS''
		run_index $1 $wm 0 'CREATE INDEX x ON num_test (a); DROP INDEX x'

	done

}

create_tables

log "sort benchmark $ROWS"

load_random
run_queries "random"

load_sorted
run_queries "sorted"

load_almost_sorted
run_queries "almost_sorted"

load_reversed
run_queries "reversed"

load_organ_pipe
run_queries "organ_pipe"

load_rotated
run_queries "rotated"

load_0_1
run_queries "0_1"

Attachment: test-acc-tuplesort-20210729.ods
Description: application/vnd.oasis.opendocument.spreadsheet

Attachment: 0001-WIP-Accelerate-tuple-sorting-for-common-types.patch
Description: Binary data

Reply via email to