On Wed, Jan 21, 2026 at 9:50 PM Neil Conway <[email protected]> wrote:

> A few suggestions:
>
> * I'm curious if we'll see better performance on large inputs if we flush
> to `line_buf` periodically (e.g., at least every few thousand bytes or so).
> Otherwise we might see poor data cache behavior if large inputs with no
> control characters get evicted before we've copied them over. See the
> approach taken in escape_json_with_len() in utils/adt/json.c
>
Hello Neil, thanks for the suggestions!
This might be true as the json code considers it, i'll take a look later.

>
> * Did you compare the approach taken in the patch with a simpler approach
> that just does
>
> if (!(vector8_has(chunk, '\\') ||
>       vector8_has(chunk, '\r') ||
>       vector8_has(chunk, '\n') /* and so on, accounting for CSV / escapec
> / quotec stuff */))
> {
>     /* skip chunk */
> }
>
> That's roughly what we do elsewhere (e.g., escape_json_with_len). It has
> the advantage of being more readable, along with potentially having fewer
> data dependencies.
>
This is true and also similar to the concerns of the very first patches
where i tried to just replace the special chars detection by something less
dependent but I missed vector8_has.
I have tried this now and here are my thoughts and results:

v5: the simplest of all, we take a chunk, detect any special character, if
it's clean then skip it all, otherwise fallback to scalar WITHOUT advancing
to the first special char found, this is mentioned just for reference and
comparison.
Numbers compared to master:
text, no special: +16.8%
text, 1/3 special: -21.3%
csv, no special: +29.5%
csv, 1/3 special: -33.5%

v5.1: Take a chunk, if its clean of specials, skip it, if not process the
whole chunk byte-by-byte, retry the same thing for next chunk if it's fully
skippable and so on...
Numbers:
text, no special: +16.5%
text, 1/3 special: -20.1% (weird) ; but overall the idea is clear maybe.
csv, no special: +29.4%
csv, 1/3 special: -4.4%

For files of 1/3 specials it's almost always terrible to consider simd
because it will always fall back to scalar without even advancing to the
first special character, even for something less than 1/3 i think it would
be same case, i attached an extremely simple analysis on probability of
having a 16 byte clean chunk depending on data we have.

Attached also is an overall comparison graph between master, v3, v5.1 (with
patch)


Regards,
Ayoub
From 40cd491a8dcc2b90615ba76f7c4b16e4491fcd04 Mon Sep 17 00:00:00 2001
From: AyoubKAZ <[email protected]>
Date: Thu, 22 Jan 2026 20:02:51 +0100
Subject: [PATCH] Simple heuristic for SIMD COPY FROM

---
 src/backend/commands/copyfromparse.c | 32 ++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 5868a7fa11f..d138dd3731c 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -71,7 +71,9 @@
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "port/pg_bitutils.h"
 #include "port/pg_bswap.h"
+#include "port/simd.h"
 #include "utils/builtins.h"
 #include "utils/rel.h"
 
@@ -1248,6 +1250,7 @@ CopyReadLineText(CopyFromState cstate, bool is_csv)
 	bool		need_data = false;
 	bool		hit_eof = false;
 	bool		result = false;
+	int 		simd_boundary;
 
 	/* CSV variables */
 	bool		in_quote = false,
@@ -1292,6 +1295,7 @@ CopyReadLineText(CopyFromState cstate, bool is_csv)
 	copy_input_buf = cstate->input_buf;
 	input_buf_ptr = cstate->input_buf_index;
 	copy_buf_len = cstate->input_buf_len;
+	simd_boundary = input_buf_ptr;
 
 	for (;;)
 	{
@@ -1315,6 +1319,7 @@ CopyReadLineText(CopyFromState cstate, bool is_csv)
 			hit_eof = cstate->input_reached_eof;
 			input_buf_ptr = cstate->input_buf_index;
 			copy_buf_len = cstate->input_buf_len;
+			simd_boundary = input_buf_ptr;
 
 			/*
 			 * If we are completely out of data, break out of the loop,
@@ -1328,6 +1333,33 @@ CopyReadLineText(CopyFromState cstate, bool is_csv)
 			need_data = false;
 		}
 
+
+#ifndef USE_NO_SIMD
+
+		if (input_buf_ptr >= simd_boundary && !last_was_esc && (copy_buf_len - input_buf_ptr) >= sizeof(Vector8))
+		{
+			Vector8 chunk;
+
+			vector8_load(&chunk, (const uint8 *) &copy_input_buf[input_buf_ptr]);
+
+			if (!(vector8_has(chunk, '\\') ||
+				  vector8_has(chunk, '\r') ||
+				  vector8_has(chunk, '\n') ||
+				  (is_csv && vector8_has(chunk, quotec)) ||
+				  (is_csv && escapec != '\0' && vector8_has(chunk, escapec))))
+			{
+				input_buf_ptr += sizeof(Vector8);
+				simd_boundary = input_buf_ptr;
+				continue;
+			}
+			else
+			{
+
+				simd_boundary = input_buf_ptr + sizeof(Vector8);
+			}
+		}
+#endif
+
 		/* OK to fetch a character */
 		prev_raw_ptr = input_buf_ptr;
 		c = copy_input_buf[input_buf_ptr++];
-- 
2.34.1

Reply via email to