CREATE OR REPLACE FUNCTION repeat_bench_sql(max_output_bytes bigint DEFAULT 268435456,
										iterations int DEFAULT 10,
										empty_source_count int DEFAULT 10000000)
RETURNS TABLE (output_mb numeric,
			   source_len int,
			   repeat_count bigint,
			   best_ms numeric,
			   ns_per_byte numeric)
LANGUAGE plpgsql AS $$
DECLARE
	source_lengths	int[] := ARRAY[1, 10, 100, 1024, 4096, 16384, 65536,
								   262144, 1048576];
	size_divisors	int[] := ARRAY[16, 4, 1];
	divisor			int;
	cur_len			int;
	cur_count		bigint;
	target_bytes	bigint;
	source			text;
	sink			text;
	started_at		timestamptz;
	elapsed_ms		numeric;
	best_elapsed	numeric;
BEGIN
	-- repeat() rejects any result larger than MaxAllocSize (1GB - 1) counting
	-- the varlena header, so asking for more than that only produces errors.
	-- Clamping here also keeps every repeat count comfortably inside int4.
	max_output_bytes := least(max_output_bytes, 1073741819);

	-- Zero-length source: no bytes are ever copied.
	source := '';
	best_elapsed := NULL;
	FOR i IN 1 .. iterations LOOP
		started_at := clock_timestamp();
		sink := repeat(source, empty_source_count);
		elapsed_ms := extract(epoch FROM clock_timestamp() - started_at) * 1000;
		IF best_elapsed IS NULL OR elapsed_ms < best_elapsed THEN
			best_elapsed := elapsed_ms;
		END IF;
	END LOOP;
	output_mb := 0.0;
	source_len := 0;
	repeat_count := empty_source_count;
	best_ms := round(best_elapsed, 4);
	ns_per_byte := NULL;		-- no bytes produced, so the ratio is undefined
	RETURN NEXT;

	FOREACH divisor IN ARRAY size_divisors LOOP
		target_bytes := greatest(max_output_bytes / divisor, 1048576);

		FOREACH cur_len IN ARRAY source_lengths LOOP
			cur_count := greatest(target_bytes / cur_len, 1);
			source := repeat('x', cur_len);

			best_elapsed := NULL;
			FOR i IN 1 .. iterations LOOP
				started_at := clock_timestamp();
				sink := repeat(source, cur_count::int);
				elapsed_ms := extract(epoch FROM clock_timestamp() - started_at) * 1000;
				IF best_elapsed IS NULL OR elapsed_ms < best_elapsed THEN
					best_elapsed := elapsed_ms;
				END IF;
			END LOOP;
			sink := NULL;

			output_mb := round((cur_len::numeric * cur_count) / 1048576, 1);
			source_len := cur_len;
			repeat_count := cur_count;
			best_ms := round(best_elapsed, 4);
			ns_per_byte := round((best_elapsed * 1000000) /
								 (cur_len::numeric * cur_count), 4);
			RETURN NEXT;
		END LOOP;
	END LOOP;
END $$;

\timing on
SELECT * FROM repeat_bench_sql();
