Tại sao?
tại sao phiên bản C lại nhanh hơn nhiều?
Mảng PostgreSQL tự nó là một cấu trúc dữ liệu khá kém hiệu quả. Nó có thể chứa bất kỳ kiểu dữ liệu và nó có khả năng đa chiều, vì vậy không thể thực hiện được nhiều tối ưu hóa. Tuy nhiên, như bạn đã thấy, có thể làm việc với cùng một mảng nhanh hơn nhiều trong C.
Đó là bởi vì truy cập mảng trong C có thể tránh được rất nhiều công việc lặp đi lặp lại liên quan đến truy cập mảng PL / PgSQL. Chỉ cần xem qua src/backend/utils/adt/arrayfuncs.c
, array_ref
. Bây giờ, hãy xem cách nó được gọi từ src/backend/executor/execQual.c
trong ExecEvalArrayRef
. Điều này chạy cho từng quyền truy cập mảng riêng lẻ từ PL / PgSQL, như bạn có thể thấy bằng cách đính kèm gdb vào pid được tìm thấy từ select pg_backend_pid()
, đặt điểm ngắt tại ExecEvalArrayRef
, tiếp tục và chạy chức năng của bạn.
Quan trọng hơn, trong PL / PgSQL mọi câu lệnh bạn thực thi đều được chạy qua máy thực thi truy vấn. Điều này làm cho các tuyên bố nhỏ, rẻ tiền khá chậm thậm chí cho phép thực tế là chúng đã được chuẩn bị trước. Một cái gì đó như:
a := b + c
thực sự được thực thi bởi PL / PgSQL giống như:
SELECT b + c INTO a;
Bạn có thể quan sát điều này nếu bạn tăng mức gỡ lỗi đủ cao, đính kèm trình gỡ lỗi và ngắt ở điểm thích hợp hoặc sử dụng auto_explain
mô-đun với phân tích câu lệnh lồng nhau. Để cung cấp cho bạn ý tưởng về mức chi phí mà điều này áp đặt khi bạn đang chạy rất nhiều câu lệnh đơn giản nhỏ (như truy cập mảng), hãy xem ví dụ về dấu vết này và ghi chú của tôi về nó.
Ngoài ra còn có chi phí khởi động đáng kể cho mỗi lệnh gọi hàm PL / PgSQL. Nó không lớn, nhưng đủ để tăng lên khi nó được sử dụng như một tập hợp.
Cách tiếp cận nhanh hơn trong C
Trong trường hợp của bạn, tôi có thể sẽ làm điều đó trong C, như bạn đã làm, nhưng tôi sẽ tránh sao chép mảng khi được gọi dưới dạng tổng hợp. Bạn có thể kiểm tra xem nó có đang được gọi trong ngữ cảnh tổng hợp hay không:
if (AggCheckCallContext(fcinfo, NULL))
và nếu vậy, hãy sử dụng giá trị ban đầu làm trình giữ chỗ có thể thay đổi, sửa đổi nó rồi trả lại thay vì cấp phát giá trị mới. Tôi sẽ viết một bản demo để xác minh rằng điều này có thể xảy ra với các mảng trong thời gian ngắn ... (cập nhật) hoặc không lâu nữa, tôi đã quên cách làm việc tuyệt đối với mảng PostgreSQL trong C. Chúng ta bắt đầu:
// append to contrib/intarray/_int_op.c
PG_FUNCTION_INFO_V1(add_intarray_cols);
Datum add_intarray_cols(PG_FUNCTION_ARGS);
Datum
add_intarray_cols(PG_FUNCTION_ARGS)
{
ArrayType *a,
*b;
int i, n;
int *da,
*db;
if (PG_ARGISNULL(1))
ereport(ERROR, (errmsg("Second operand must be non-null")));
b = PG_GETARG_ARRAYTYPE_P(1);
CHECKARRVALID(b);
if (AggCheckCallContext(fcinfo, NULL))
{
// Called in aggregate context...
if (PG_ARGISNULL(0))
// ... for the first time in a run, so the state in the 1st
// argument is null. Create a state-holder array by copying the
// second input array and return it.
PG_RETURN_POINTER(copy_intArrayType(b));
else
// ... for a later invocation in the same run, so we'll modify
// the state array directly.
a = PG_GETARG_ARRAYTYPE_P(0);
}
else
{
// Not in aggregate context
if (PG_ARGISNULL(0))
ereport(ERROR, (errmsg("First operand must be non-null")));
// Copy 'a' for our result. We'll then add 'b' to it.
a = PG_GETARG_ARRAYTYPE_P_COPY(0);
CHECKARRVALID(a);
}
// This requirement could probably be lifted pretty easily:
if (ARR_NDIM(a) != 1 || ARR_NDIM(b) != 1)
ereport(ERROR, (errmsg("One-dimesional arrays are required")));
// ... as could this by assuming the un-even ends are zero, but it'd be a
// little ickier.
n = (ARR_DIMS(a))[0];
if (n != (ARR_DIMS(b))[0])
ereport(ERROR, (errmsg("Arrays are of different lengths")));
da = ARRPTR(a);
db = ARRPTR(b);
for (i = 0; i < n; i++)
{
// Fails to check for integer overflow. You should add that.
*da = *da + *db;
da++;
db++;
}
PG_RETURN_POINTER(a);
}
và nối nó vào contrib/intarray/intarray--1.0.sql
:
CREATE FUNCTION add_intarray_cols(_int4, _int4) RETURNS _int4
AS 'MODULE_PATHNAME'
LANGUAGE C IMMUTABLE;
CREATE AGGREGATE sum_intarray_cols(_int4) (sfunc = add_intarray_cols, stype=_int4);
(chính xác hơn là bạn tạo intarray--1.1.sql
và intarray--1.0--1.1.sql
và cập nhật intarray.control
. Đây chỉ là một vụ hack nhanh.)
Sử dụng:
make USE_PGXS=1
make USE_PGXS=1 install
để biên dịch và cài đặt.
Hiện tại DROP EXTENSION intarray;
(nếu bạn đã có) và CREATE EXTENSION intarray;
.
Bây giờ bạn sẽ có hàm tổng hợp sum_intarray_cols
có sẵn cho bạn (như sum(int4[])
của bạn , cũng như add_intarray_cols
hai toán hạng (như array_add
của bạn ).
Bằng cách chuyên về mảng số nguyên, toàn bộ sự phức tạp sẽ biến mất. Trong trường hợp tổng hợp, chúng ta tránh được một loạt các sao chép, vì chúng ta có thể sửa đổi một cách an toàn mảng "trạng thái" (đối số đầu tiên) tại chỗ. Để giữ cho mọi thứ nhất quán, trong trường hợp lệnh gọi không tổng hợp, chúng tôi nhận được một bản sao của đối số đầu tiên để chúng tôi vẫn có thể làm việc với nó tại chỗ và trả lại nó.
Cách tiếp cận này có thể được tổng quát hóa để hỗ trợ bất kỳ loại dữ liệu nào bằng cách sử dụng bộ đệm fmgr để tìm kiếm hàm thêm cho (các) loại quan tâm, v.v. Tôi không đặc biệt quan tâm đến việc đó, vì vậy nếu bạn cần (giả sử, tổng các cột của NUMERIC
mảng) thì ... chúc bạn vui vẻ.
Tương tự, nếu bạn cần xử lý các độ dài mảng khác nhau, bạn có thể tìm ra những việc cần làm từ những điều trên.