Files
openGauss-server/src/common/backend/utils/adt/vector.cpp
2025-04-22 14:40:28 +08:00

1507 lines
37 KiB
C++

/*
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* -------------------------------------------------------------------------
*
* vector.cpp
*
* IDENTIFICATION
* src/common/backend/utils/adt/vector.cpp
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include <cmath>
#ifdef __aarch64__
#include <arm_neon.h>
#else
#include <immintrin.h>
#endif
#include "access/datavec/bitvec.h"
#include "catalog/pg_type.h"
#include "fmgr.h"
#include "access/datavec/halfutils.h"
#include "access/datavec/halfvec.h"
#include "access/datavec/hnsw.h"
#include "access/datavec/ivfflat.h"
#include "lib/stringinfo.h"
#include "libpq/pqformat.h"
#include "port.h" /* for strtof() */
#include "access/datavec/shortest_dec.h"
#include "access/datavec/sparsevec.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/numeric.h"
#include "commands/extension.h"
#include "knl/knl_session.h"
#include "access/datavec/vector.h"
#define TYPALIGN_DOUBLE 'd'
#define TYPALIGN_INT 'i'
#define STATE_DIMS(x) (ARR_DIMS(x)[0] - 1)
#define CreateStateDatums(dim) palloc(sizeof(Datum) * ((dim) + 1))
#if defined(USE_TARGET_CLONES) && !defined(__FMA__)
#define VECTOR_TARGET_CLONES __attribute__((target_clones("default", "fma")))
#else
#define VECTOR_TARGET_CLONES
#endif
#define MarkGUCPrefixReserved(x) EmitWarningsOnPlaceholders(x)
/*
* Ensure same dimensions
*/
static inline void CheckDims(Vector *a, Vector *b)
{
if (a->dim != b->dim)
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION), errmsg("different vector dimensions %d and %d", a->dim, b->dim)));
}
/*
* Ensure expected dimensions
*/
static inline void CheckExpectedDim(int32 typmod, int dim)
{
if (typmod != -1 && typmod != dim)
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("expected %d dimensions, not %d", typmod, dim)));
}
/*
* Ensure valid dimensions
*/
static inline void CheckDim(int dim)
{
if (dim < 1)
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("vector must have at least 1 dimension")));
if (dim > VECTOR_MAX_DIM)
ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("vector cannot have more than %d dimensions", VECTOR_MAX_DIM)));
}
/*
* Ensure finite element
*/
static inline void CheckElement(float value)
{
if (isnan(value))
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("NaN not allowed in vector")));
if (isinf(value))
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("infinite value not allowed in vector")));
}
/*
* Allocate and initialize a new vector
*/
Vector *InitVector(int dim)
{
Vector *result;
int size;
size = VECTOR_SIZE(dim);
result = (Vector *)palloc0(size);
SET_VARSIZE(result, size);
result->dim = dim;
return result;
}
/*
* Check for whitespace, since array_isspace() is static
*/
static inline bool VectorIsspace(char ch)
{
if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\v' || ch == '\f') {
return true;
}
return false;
}
/*
* Check state array
*/
static float8 *CheckStateArray(ArrayType *statearray, const char *caller)
{
if (ARR_NDIM(statearray) != 1 || ARR_DIMS(statearray)[0] < 1 || ARR_HASNULL(statearray))
elog(ERROR, "%s: expected state array", caller);
return (float8 *)ARR_DATA_PTR(statearray);
}
static pg_noinline void float_overflow_error(void)
{
ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("value out of range: overflow")));
}
static pg_noinline void float_underflow_error(void)
{
ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("value out of range: underflow")));
}
/*
* Convert textual representation to internal representation
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_in);
Datum vector_in(PG_FUNCTION_ARGS)
{
char *lit = PG_GETARG_CSTRING(0);
int32 typmod = PG_GETARG_INT32(2);
float x[VECTOR_MAX_DIM];
int dim = 0;
char *pt = lit;
Vector *result;
while (VectorIsspace(*pt)) {
pt++;
}
if (*pt != '[')
ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type vector: \"%s\"", lit),
errdetail("Vector contents must start with \"[\".")));
pt++;
while (VectorIsspace(*pt)) {
pt++;
}
if (*pt == ']') {
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("vector must have at least 1 dimension")));
}
for (;;) {
float val;
char *stringEnd;
if (dim == VECTOR_MAX_DIM)
ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("vector cannot have more than %d dimensions", VECTOR_MAX_DIM)));
while (VectorIsspace(*pt)) {
pt++;
}
/* Check for empty string like float4in */
if (*pt == '\0')
ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type vector: \"%s\"", lit)));
errno = 0;
/* Use strtof like float4in to avoid a double-rounding problem */
/* Postgres sets LC_NUMERIC to C on startup */
val = strtof(pt, &stringEnd);
if (stringEnd == pt) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type vector: \"%s\"", lit)));
}
/* Check for range error like float4in */
if (errno == ERANGE && isinf(val))
ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"%s\" is out of range for type vector", pnstrdup(pt, stringEnd - pt))));
CheckElement(val);
x[dim++] = val;
pt = stringEnd;
while (VectorIsspace(*pt)) {
pt++;
}
if (*pt == ',') {
pt++;
} else if (*pt == ']') {
pt++;
break;
} else {
ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type vector: \"%s\"", lit)));
}
}
/* Only whitespace is allowed after the closing brace */
while (VectorIsspace(*pt)) {
pt++;
}
if (*pt != '\0')
ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type vector: \"%s\"", lit),
errdetail("Junk after closing right brace.")));
CheckDim(dim);
CheckExpectedDim(typmod, dim);
result = InitVector(dim);
for (int i = 0; i < dim; i++) {
result->x[i] = x[i];
}
PG_RETURN_POINTER(result);
}
#define AppendChar(ptr, c) (*(ptr)++ = (c))
#define AppendFloat(ptr, f) ((ptr) += FloatToShortestDecimalBufn((f), (ptr)))
/*
* Convert internal representation to textual representation
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_out);
Datum vector_out(PG_FUNCTION_ARGS)
{
Vector *vector = PG_GETARG_VECTOR_P(0);
int dim = vector->dim;
char *buf;
char *ptr;
/*
* Need:
*
* dim * (FLOAT_SHORTEST_DECIMAL_LEN - 1) bytes for
* FloatToShortestDecimalBufn
*
* dim - 1 bytes for separator
*
* 3 bytes for [, ], and \0
*/
buf = (char *)palloc(FLOAT_SHORTEST_DECIMAL_LEN * dim + 2);
ptr = buf;
AppendChar(ptr, '[');
for (int i = 0; i < dim; i++) {
if (i > 0) {
AppendChar(ptr, ',');
}
AppendFloat(ptr, vector->x[i]);
}
AppendChar(ptr, ']');
*ptr = '\0';
PG_FREE_IF_COPY(vector, 0);
PG_RETURN_CSTRING(buf);
}
/*
* Print vector - useful for debugging
*/
void PrintVector(char *msg, Vector *vector)
{
char *out = DatumGetPointer(DirectFunctionCall1(vector_out, PointerGetDatum(vector)));
elog(INFO, "%s = %s", msg, out);
pfree(out);
}
/*
* Convert type modifier
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_typmod_in);
Datum vector_typmod_in(PG_FUNCTION_ARGS)
{
ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0);
int32 *tl;
int n;
tl = ArrayGetIntegerTypmods(ta, &n);
if (n != 1) {
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid type modifier")));
}
if (*tl < 1) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("dimensions for type vector must be at least 1")));
}
if (*tl > VECTOR_MAX_DIM) {
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("dimensions for type vector cannot exceed %d", VECTOR_MAX_DIM)));
}
PG_RETURN_INT32(*tl);
}
/*
* Convert external binary representation to internal representation
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_recv);
Datum vector_recv(PG_FUNCTION_ARGS)
{
StringInfo buf = (StringInfo)PG_GETARG_POINTER(0);
int32 typmod = PG_GETARG_INT32(2);
Vector *result;
int16 dim;
int16 unused;
dim = pq_getmsgint(buf, sizeof(int16));
unused = pq_getmsgint(buf, sizeof(int16));
CheckDim(dim);
CheckExpectedDim(typmod, dim);
if (unused != 0) {
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("expected unused to be 0, not %d", unused)));
}
result = InitVector(dim);
for (int i = 0; i < dim; i++) {
result->x[i] = pq_getmsgfloat4(buf);
CheckElement(result->x[i]);
}
PG_RETURN_POINTER(result);
}
/*
* Convert internal representation to the external binary representation
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_send);
Datum vector_send(PG_FUNCTION_ARGS)
{
Vector *vec = PG_GETARG_VECTOR_P(0);
StringInfoData buf;
pq_begintypsend(&buf);
pq_sendint(&buf, vec->dim, sizeof(int16));
pq_sendint(&buf, vec->unused, sizeof(int16));
for (int i = 0; i < vec->dim; i++)
pq_sendfloat4(&buf, vec->x[i]);
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}
/*
* Convert vector to vector
* This is needed to check the type modifier
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector);
Datum vector(PG_FUNCTION_ARGS)
{
Vector *vec = PG_GETARG_VECTOR_P(0);
int32 typmod = PG_GETARG_INT32(1);
CheckExpectedDim(typmod, vec->dim);
PG_RETURN_POINTER(vec);
}
/*
* Convert array to vector
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(array_to_vector);
Datum array_to_vector(PG_FUNCTION_ARGS)
{
ArrayType *array = PG_GETARG_ARRAYTYPE_P(0);
int32 typmod = PG_GETARG_INT32(1);
Vector *result;
int16 typlen;
bool typbyval;
char typalign;
Datum *elemsp;
int nelemsp;
if (ARR_NDIM(array) > 1) {
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("array must be 1-D")));
}
if (ARR_HASNULL(array) && array_contains_nulls(array)) {
ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("array must not contain nulls")));
}
get_typlenbyvalalign(ARR_ELEMTYPE(array), &typlen, &typbyval, &typalign);
deconstruct_array(array, ARR_ELEMTYPE(array), typlen, typbyval, typalign, &elemsp, NULL, &nelemsp);
CheckDim(nelemsp);
CheckExpectedDim(typmod, nelemsp);
result = InitVector(nelemsp);
if (ARR_ELEMTYPE(array) == INT4OID) {
for (int i = 0; i < nelemsp; i++)
result->x[i] = DatumGetInt32(elemsp[i]);
} else if (ARR_ELEMTYPE(array) == FLOAT8OID) {
for (int i = 0; i < nelemsp; i++)
result->x[i] = DatumGetFloat8(elemsp[i]);
} else if (ARR_ELEMTYPE(array) == FLOAT4OID) {
for (int i = 0; i < nelemsp; i++)
result->x[i] = DatumGetFloat4(elemsp[i]);
} else if (ARR_ELEMTYPE(array) == NUMERICOID) {
for (int i = 0; i < nelemsp; i++)
result->x[i] = DatumGetFloat4(DirectFunctionCall1(numeric_float4, elemsp[i]));
} else {
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("unsupported array type")));
}
/*
* Free allocation from deconstruct_array. Do not free individual elements
* when pass-by-reference since they point to original array.
*/
pfree(elemsp);
/* Check elements */
for (int i = 0; i < result->dim; i++) {
CheckElement(result->x[i]);
}
PG_RETURN_POINTER(result);
}
/*
* Convert vector to float4[]
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_to_float4);
Datum vector_to_float4(PG_FUNCTION_ARGS)
{
Vector *vec = PG_GETARG_VECTOR_P(0);
Datum *datums;
ArrayType *result;
datums = (Datum *)palloc(sizeof(Datum) * vec->dim);
for (int i = 0; i < vec->dim; i++) {
datums[i] = Float4GetDatum(vec->x[i]);
}
/* Use TYPALIGN_INT for float4 */
result = construct_array(datums, vec->dim, FLOAT4OID, sizeof(float4), true, TYPALIGN_INT);
pfree(datums);
PG_RETURN_POINTER(result);
}
/*
* Convert half vector to vector
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(halfvec_to_vector);
Datum halfvec_to_vector(PG_FUNCTION_ARGS)
{
HalfVector *vec = PG_GETARG_HALFVEC_P(0);
int32 typmod = PG_GETARG_INT32(1);
Vector *result;
CheckDim(vec->dim);
CheckExpectedDim(typmod, vec->dim);
result = InitVector(vec->dim);
for (int i = 0; i < vec->dim; i++) {
result->x[i] = HalfToFloat4(vec->x[i]);
}
PG_RETURN_POINTER(result);
}
inline void prefetch_L1(const void *address)
{
#if defined(__SSE2__)
_mm_prefetch((const char*)address, _MM_HINT_T0);
#elif defined(__aarch64__)
asm volatile("prfm PLDL1KEEP, [%0]" : : "r"(address));
#else
__builtin_prefetch(address, 0, 3); // L3 cache
#endif
}
#ifdef __aarch64__
VECTOR_TARGET_CLONES static float
VectorL2SquaredDistance(int size, float *a, float *b)
{
float32x4_t sum = vdupq_n_f32(0.0f);
int prefetch_len = 8;
int batch_num = 4;
int i;
for (i = 0; i < size - (size % batch_num); i += batch_num) {
prefetch_L1(a + i + prefetch_len);
prefetch_L1(b + i + prefetch_len);
float32x4_t va = vld1q_f32(a + i);
float32x4_t vb = vld1q_f32(b + i);
float32x4_t diff = vsubq_f32(va, vb);
sum = vfmaq_f32(sum, diff, diff);
}
float scalar_sum = 0.0f;
if (size % batch_num > 0) {
int remaining_size = size % batch_num;
for (int j = 0; j < remaining_size; ++j) {
prefetch_L1(a + i + j);
prefetch_L1(b + i + j);
float value_a = a[i + j];
float value_b = b[i + j];
float diff = value_a - value_b;
float sq_diff = diff * diff;
scalar_sum += sq_diff;
}
}
sum = vpaddq_f32(sum, sum);
sum = vpaddq_f32(sum, sum);
float res = vgetq_lane_f32(sum, 0);
return res + scalar_sum;
}
#elif defined(__x86_64__)
static inline __m128 masked_read(int d, const float *x)
{
__attribute__((__aligned__(16))) float buf[4];
Assert(0 <= d && d < 4); /* reads 0 <= d < 4 floats as __m128 */
memset((void*)buf, 0, sizeof(buf));
switch (d) {
case 3:
buf[2] = x[2];
case 2:
buf[1] = x[1];
case 1:
buf[0] = x[0];
default:
break;
}
return _mm_load_ps(buf);
}
VECTOR_TARGET_CLONES static float
VectorL2SquaredDistance(int dim, float *ax, float *bx)
{
float* x = (float*)ax;
float* y = (float*)bx;
size_t d = (size_t)dim;
int batch_num1 = 8;
int batch_num2 = 4;
__m256 msum1 = _mm256_setzero_ps();
while (d >= batch_num1) {
__m256 mx = _mm256_loadu_ps(x);
x += batch_num1;
__m256 my = _mm256_loadu_ps(y);
y += batch_num1;
const __m256 a_m_b1 = mx - my;
msum1 += a_m_b1 * a_m_b1;
d -= batch_num1;
}
__m128 msum2 = _mm256_extractf128_ps(msum1, 1);
msum2 += _mm256_extractf128_ps(msum1, 0);
if (d >= batch_num2) {
__m128 mx = _mm_loadu_ps(x);
x += batch_num2;
__m128 my = _mm_loadu_ps(y);
y += batch_num2;
const __m128 a_m_b1 = mx - my;
msum2 += a_m_b1 * a_m_b1;
d -= batch_num2;
}
if (d > 0) {
__m128 mx = masked_read(d, x);
__m128 my = masked_read(d, y);
__m128 a_m_b1 = mx - my;
msum2 += a_m_b1 * a_m_b1;
}
msum2 = _mm_hadd_ps(msum2, msum2);
msum2 = _mm_hadd_ps(msum2, msum2);
return _mm_cvtss_f32(msum2);
}
#else
VECTOR_TARGET_CLONES static float VectorL2SquaredDistance(int dim, float *ax, float *bx)
{
float distance = 0.0;
/* Auto-vectorized */
for (int i = 0; i < dim; i++) {
float diff = ax[i] - bx[i];
distance += diff * diff;
}
return distance;
}
#endif
/*
* Get the L2 distance between vectors
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(l2_distance);
Datum l2_distance(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
CheckDims(a, b);
PG_RETURN_FLOAT8(sqrt((double)VectorL2SquaredDistance(a->dim, a->x, b->x)));
}
/*
* Get the L2 squared distance between vectors
* This saves a sqrt calculation
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_l2_squared_distance);
Datum vector_l2_squared_distance(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
CheckDims(a, b);
PG_RETURN_FLOAT8((double)VectorL2SquaredDistance(a->dim, a->x, b->x));
}
#ifdef __aarch64__
VECTOR_TARGET_CLONES static float
VectorInnerProduct(int dim, float *ax, float *bx)
{
float dis = 0.0f;
float32x4_t sum = vdupq_n_f32(0.0f);
float *pta = ax;
float *ptb = bx;
int i = 0;
int prefetch_len = 8;
int batch_num = 4;
for (; i + batch_num <= dim; i += batch_num) {
prefetch_L1(pta + prefetch_len);
prefetch_L1(ptb + prefetch_len);
float32x4_t sub_a = vld1q_f32(pta);
float32x4_t sub_b = vld1q_f32(ptb);
sum = vmlaq_f32(sum, sub_a, sub_b);
pta += batch_num;
ptb += batch_num;
}
dis = vaddvq_f32(sum);
for (; i < dim; ++i) {
dis += ax[i] * bx[i];
}
return dis;
}
#else
VECTOR_TARGET_CLONES static float VectorInnerProduct(int dim, float *ax, float *bx)
{
float distance = 0.0;
/* Auto-vectorized */
for (int i = 0; i < dim; i++) {
distance += ax[i] * bx[i];
}
return distance;
}
#endif
/*
* Get the inner product of two vectors
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(inner_product);
Datum inner_product(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
CheckDims(a, b);
PG_RETURN_FLOAT8((double)VectorInnerProduct(a->dim, a->x, b->x));
}
/*
* Get the negative inner product of two vectors
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_negative_inner_product);
Datum vector_negative_inner_product(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
CheckDims(a, b);
PG_RETURN_FLOAT8((double)-VectorInnerProduct(a->dim, a->x, b->x));
}
VECTOR_TARGET_CLONES static double VectorCosineSimilarity(int dim, float *ax, float *bx)
{
float similarity = 0.0;
float norma = 0.0;
float normb = 0.0;
/* Auto-vectorized */
for (int i = 0; i < dim; i++) {
similarity += ax[i] * bx[i];
norma += ax[i] * ax[i];
normb += bx[i] * bx[i];
}
/* Use sqrt(a * b) over sqrt(a) * sqrt(b) */
return static_cast<double>(similarity) / sqrt(static_cast<double>(norma) * static_cast<double>(normb));
}
/*
* Get the cosine distance between two vectors
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(cosine_distance);
Datum cosine_distance(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
double similarity;
CheckDims(a, b);
similarity = VectorCosineSimilarity(a->dim, a->x, b->x);
#ifdef _MSC_VER
/* /fp:fast may not propagate NaN */
if (isnan(similarity)) {
PG_RETURN_FLOAT8(NAN);
}
#endif
/* Keep in range */
if (similarity > 1) {
similarity = 1.0;
} else if (similarity < -1) {
similarity = -1.0;
}
PG_RETURN_FLOAT8(1.0 - similarity);
}
/*
* Get the distance for spherical k-means
* Currently uses angular distance since needs to satisfy triangle inequality
* Assumes inputs are unit vectors (skips norm)
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_spherical_distance);
Datum vector_spherical_distance(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
double distance;
CheckDims(a, b);
distance = (double)VectorInnerProduct(a->dim, a->x, b->x);
/* Prevent NaN with acos with loss of precision */
if (distance > 1) {
distance = 1;
} else if (distance < -1) {
distance = -1;
}
PG_RETURN_FLOAT8(acos(distance) / M_PI);
}
/* Does not require FMA, but keep logic simple */
VECTOR_TARGET_CLONES static float VectorL1Distance(int dim, float *ax, float *bx)
{
float distance = 0.0;
/* Auto-vectorized */
for (int i = 0; i < dim; i++) {
distance += fabsf(ax[i] - bx[i]);
}
return distance;
}
/*
* Get the L1 distance between two vectors
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(l1_distance);
Datum l1_distance(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
CheckDims(a, b);
PG_RETURN_FLOAT8((double)VectorL1Distance(a->dim, a->x, b->x));
}
/*
* Get the dimensions of a vector
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_dims);
Datum vector_dims(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
PG_RETURN_INT32(a->dim);
}
/*
* Get the L2 norm of a vector
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_norm);
Datum vector_norm(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
float *ax = a->x;
double norm = 0.0;
/* Auto-vectorized */
for (int i = 0; i < a->dim; i++) {
norm += (double)ax[i] * (double)ax[i];
}
PG_RETURN_FLOAT8(sqrt(norm));
}
/*
* Normalize a vector with the L2 norm
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(l2_normalize);
Datum l2_normalize(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
float *ax = a->x;
double norm = 0;
Vector *result;
float *rx;
result = InitVector(a->dim);
rx = result->x;
/* Auto-vectorized */
for (int i = 0; i < a->dim; i++) {
norm += (double)ax[i] * (double)ax[i];
}
norm = sqrt(norm);
/* Return zero vector for zero norm */
if (norm > 0) {
for (int i = 0; i < a->dim; i++) {
rx[i] = ax[i] / norm;
}
/* Check for overflow */
for (int i = 0; i < a->dim; i++) {
if (isinf(rx[i])) {
float_overflow_error();
}
}
}
PG_RETURN_POINTER(result);
}
/*
* Add vectors
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_add);
Datum vector_add(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
float *ax = a->x;
float *bx = b->x;
Vector *result;
float *rx;
CheckDims(a, b);
result = InitVector(a->dim);
rx = result->x;
/* Auto-vectorized */
for (int i = 0, imax = a->dim; i < imax; i++) {
rx[i] = ax[i] + bx[i];
}
/* Check for overflow */
for (int i = 0, imax = a->dim; i < imax; i++) {
if (isinf(rx[i])) {
float_overflow_error();
}
}
PG_RETURN_POINTER(result);
}
/*
* Subtract vectors
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_sub);
Datum vector_sub(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
float *ax = a->x;
float *bx = b->x;
Vector *result;
float *rx;
CheckDims(a, b);
result = InitVector(a->dim);
rx = result->x;
/* Auto-vectorized */
for (int i = 0, imax = a->dim; i < imax; i++) {
rx[i] = ax[i] - bx[i];
}
/* Check for overflow */
for (int i = 0, imax = a->dim; i < imax; i++) {
if (isinf(rx[i])) {
float_overflow_error();
}
}
PG_RETURN_POINTER(result);
}
/*
* Multiply vectors
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_mul);
Datum vector_mul(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
float *ax = a->x;
float *bx = b->x;
Vector *result;
float *rx;
CheckDims(a, b);
result = InitVector(a->dim);
rx = result->x;
/* Auto-vectorized */
for (int i = 0, imax = a->dim; i < imax; i++) {
rx[i] = ax[i] * bx[i];
}
/* Check for overflow and underflow */
for (int i = 0, imax = a->dim; i < imax; i++) {
if (isinf(rx[i])) {
float_overflow_error();
}
if (rx[i] == 0 && !(ax[i] == 0 || bx[i] == 0)) {
float_underflow_error();
}
}
PG_RETURN_POINTER(result);
}
/*
* Concatenate vectors
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_concat);
Datum vector_concat(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
Vector *result;
int dim = a->dim + b->dim;
CheckDim(dim);
result = InitVector(dim);
for (int i = 0; i < a->dim; i++) {
result->x[i] = a->x[i];
}
for (int i = 0; i < b->dim; i++) {
result->x[i + a->dim] = b->x[i];
}
PG_RETURN_POINTER(result);
}
/*
* Quantize a vector
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(binary_quantize);
Datum binary_quantize(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
float *ax = a->x;
VarBit *result = InitBitVector(a->dim);
unsigned char *rx = VARBITS(result);
for (int i = 0; i < a->dim; i++) {
rx[i / 8] |= (ax[i] > 0) << (7 - (i % 8));
}
PG_RETURN_VARBIT_P(result);
}
/*
* Get a subvector
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(subvector);
Datum subvector(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
int32 start = PG_GETARG_INT32(1);
int32 count = PG_GETARG_INT32(2);
int32 end;
float *ax = a->x;
Vector *result;
int dim;
if (count < 1) {
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("vector must have at least 1 dimension")));
}
/*
* Check if (start + count > a->dim), avoiding integer overflow. a->dim
* and count are both positive, so a->dim - count won't overflow.
*/
if (start > a->dim - count) {
end = a->dim + 1;
} else {
end = start + count;
}
/* Indexing starts at 1, like substring */
if (start < 1) {
start = 1;
} else if (start > a->dim) {
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("vector must have at least 1 dimension")));
}
dim = end - start;
CheckDim(dim);
result = InitVector(dim);
for (int i = 0; i < dim; i++) {
result->x[i] = ax[start - 1 + i];
}
PG_RETURN_POINTER(result);
}
/*
* Internal helper to compare vectors
*/
int vector_cmp_internal(Vector *a, Vector *b)
{
int dim = Min(a->dim, b->dim);
/* Check values before dimensions to be consistent with Postgres arrays */
for (int i = 0; i < dim; i++) {
if (a->x[i] < b->x[i]) {
return -1;
}
if (a->x[i] > b->x[i]) {
return 1;
}
}
if (a->dim < b->dim) {
return -1;
}
if (a->dim > b->dim) {
return 1;
}
return 0;
}
/*
* Less than
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_lt);
Datum vector_lt(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
PG_RETURN_BOOL(vector_cmp_internal(a, b) < 0);
}
/*
* Less than or equal
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_le);
Datum vector_le(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
PG_RETURN_BOOL(vector_cmp_internal(a, b) <= 0);
}
/*
* Equal
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_eq);
Datum vector_eq(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
PG_RETURN_BOOL(vector_cmp_internal(a, b) == 0);
}
/*
* Not equal
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_ne);
Datum vector_ne(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
PG_RETURN_BOOL(vector_cmp_internal(a, b) != 0);
}
/*
* Greater than or equal
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_ge);
Datum vector_ge(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
PG_RETURN_BOOL(vector_cmp_internal(a, b) >= 0);
}
/*
* Greater than
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_gt);
Datum vector_gt(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
PG_RETURN_BOOL(vector_cmp_internal(a, b) > 0);
}
/*
* Compare vectors
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_cmp);
Datum vector_cmp(PG_FUNCTION_ARGS)
{
Vector *a = PG_GETARG_VECTOR_P(0);
Vector *b = PG_GETARG_VECTOR_P(1);
PG_RETURN_INT32(vector_cmp_internal(a, b));
}
/*
* Accumulate vectors
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_accum);
Datum vector_accum(PG_FUNCTION_ARGS)
{
ArrayType *statearray = PG_GETARG_ARRAYTYPE_P(0);
Vector *newval = PG_GETARG_VECTOR_P(1);
float8 *statevalues;
int16 dim;
bool newarr;
float8 n;
Datum *statedatums;
float *x = newval->x;
ArrayType *result;
/* Check array before using */
statevalues = CheckStateArray(statearray, "vector_accum");
dim = STATE_DIMS(statearray);
newarr = dim == 0;
if (newarr) {
dim = newval->dim;
} else {
CheckExpectedDim(dim, newval->dim);
}
n = statevalues[0] + 1.0;
statedatums = (Datum *)CreateStateDatums(dim);
statedatums[0] = Float8GetDatum(n);
if (newarr) {
for (int i = 0; i < dim; i++) {
statedatums[i + 1] = Float8GetDatum((double)x[i]);
}
} else {
for (int i = 0; i < dim; i++) {
double v = statevalues[i + 1] + x[i];
/* Check for overflow */
if (isinf(v)) {
float_overflow_error();
}
statedatums[i + 1] = Float8GetDatum(v);
}
}
/* Use float8 array like float4_accum */
result = construct_array(statedatums, dim + 1, FLOAT8OID, sizeof(float8), FLOAT8PASSBYVAL, TYPALIGN_DOUBLE);
pfree(statedatums);
PG_RETURN_ARRAYTYPE_P(result);
}
/*
* Combine vectors or half vectors (also used for halfvec_combine)
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_combine);
Datum vector_combine(PG_FUNCTION_ARGS)
{
/* Must also update parameters of halfvec_combine if modifying */
ArrayType *statearray1 = PG_GETARG_ARRAYTYPE_P(0);
ArrayType *statearray2 = PG_GETARG_ARRAYTYPE_P(1);
float8 *statevalues1;
float8 *statevalues2;
float8 n;
float8 n1;
float8 n2;
int16 dim;
Datum *statedatums;
ArrayType *result;
/* Check arrays before using */
statevalues1 = CheckStateArray(statearray1, "vector_combine");
statevalues2 = CheckStateArray(statearray2, "vector_combine");
n1 = statevalues1[0];
n2 = statevalues2[0];
if (n1 == 0.0) {
n = n2;
dim = STATE_DIMS(statearray2);
statedatums = (Datum *)CreateStateDatums(dim);
for (int i = 1; i <= dim; i++)
statedatums[i] = Float8GetDatum(statevalues2[i]);
} else if (n2 == 0.0) {
n = n1;
dim = STATE_DIMS(statearray1);
statedatums = (Datum *)CreateStateDatums(dim);
for (int i = 1; i <= dim; i++)
statedatums[i] = Float8GetDatum(statevalues1[i]);
} else {
n = n1 + n2;
dim = STATE_DIMS(statearray1);
CheckExpectedDim(dim, STATE_DIMS(statearray2));
statedatums = (Datum *)CreateStateDatums(dim);
for (int i = 1; i <= dim; i++) {
double v = statevalues1[i] + statevalues2[i];
/* Check for overflow */
if (isinf(v))
float_overflow_error();
statedatums[i] = Float8GetDatum(v);
}
}
statedatums[0] = Float8GetDatum(n);
result = construct_array(statedatums, dim + 1, FLOAT8OID, sizeof(float8), FLOAT8PASSBYVAL, TYPALIGN_DOUBLE);
pfree(statedatums);
PG_RETURN_ARRAYTYPE_P(result);
}
/*
* Average vectors
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_avg);
Datum vector_avg(PG_FUNCTION_ARGS)
{
ArrayType *statearray = PG_GETARG_ARRAYTYPE_P(0);
float8 *statevalues;
float8 n;
uint16 dim;
Vector *result;
/* Check array before using */
statevalues = CheckStateArray(statearray, "vector_avg");
n = statevalues[0];
/* SQL defines AVG of no values to be NULL */
if (n == 0.0) {
PG_RETURN_NULL();
}
/* Create vector */
dim = STATE_DIMS(statearray);
CheckDim(dim);
result = InitVector(dim);
for (int i = 0; i < dim; i++) {
result->x[i] = statevalues[i + 1] / n;
CheckElement(result->x[i]);
}
PG_RETURN_POINTER(result);
}
/*
* Convert sparse vector to dense vector
*/
PGDLLEXPORT PG_FUNCTION_INFO_V1(sparsevec_to_vector);
Datum sparsevec_to_vector(PG_FUNCTION_ARGS)
{
SparseVector *svec = PG_GETARG_SPARSEVEC_P(0);
int32 typmod = PG_GETARG_INT32(1);
Vector *result;
int dim = svec->dim;
float *values = SPARSEVEC_VALUES(svec);
CheckDim(dim);
CheckExpectedDim(typmod, dim);
result = InitVector(dim);
for (int i = 0; i < svec->nnz; i++) {
result->x[svec->indices[i]] = values[i];
}
PG_RETURN_POINTER(result);
}
/*
* WAL-log a range of blocks in a relation.
*
* An image of all pages with block numbers 'startblk' <= X < 'endblk' is
* written to the WAL. If the range is large, this is done in multiple WAL
* records.
*
* If all page follows the standard page layout, with a PageHeader and unused
* space between pd_lower and pd_upper, set 'page_std' to true. That allows
* the unused space to be left out from the WAL records, making them smaller.
*
* NOTE: This function acquires exclusive-locks on the pages. Typically, this
* is used on a newly-built relation, and the caller is holding a
* AccessExclusiveLock on it, so no other backend can be accessing it at the
* same time. If that's not the case, you must ensure that this does not
* cause a deadlock through some other means.
*/
void LogNewpageRange(Relation rel, ForkNumber forknum, BlockNumber startblk, BlockNumber endblk, bool page_std)
{
int flags;
BlockNumber blkno;
flags = REGBUF_FORCE_IMAGE;
if (page_std) {
flags |= REGBUF_STANDARD;
}
/*
* Iterate over all the pages in the range. They are collected into
* batches of XLR_MAX_BLOCK_ID pages, and a single WAL-record is written
* for each batch.
*/
XLogEnsureRecordSpace(XLR_MAX_BLOCK_ID - 1, 0);
blkno = startblk;
while (blkno < endblk) {
Buffer bufpack[XLR_MAX_BLOCK_ID];
XLogRecPtr recptr;
int nbufs;
int i;
CHECK_FOR_INTERRUPTS();
/* Collect a batch of blocks. */
nbufs = 0;
while (nbufs < XLR_MAX_BLOCK_ID && blkno < endblk) {
Buffer buf = ReadBufferExtended(rel, forknum, blkno, RBM_NORMAL, NULL);
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
/*
* Completely empty pages are not WAL-logged. Writing a WAL record
* would change the LSN, and we don't want that. We want the page
* to stay empty.
*/
if (!PageIsNew(BufferGetPage(buf))) {
bufpack[nbufs++] = buf;
} else {
UnlockReleaseBuffer(buf);
}
blkno++;
}
/* Nothing more to do if all remaining blocks were empty. */
if (nbufs == 0) {
break;
}
/* Write WAL record for this batch. */
XLogBeginInsert();
START_CRIT_SECTION();
for (i = 0; i < nbufs; i++) {
MarkBufferDirty(bufpack[i]);
XLogRegisterBuffer(i, bufpack[i], flags);
}
recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI);
for (i = 0; i < nbufs; i++) {
PageSetLSN(BufferGetPage(bufpack[i]), recptr);
UnlockReleaseBuffer(bufpack[i]);
}
END_CRIT_SECTION();
}
}
int PlanCreateIndexWorkers(Relation heapRelation, IndexInfo *indexInfo)
{
int parallelWorkers = RelationGetParallelWorkers(heapRelation, 0);
int maxHashbucketIndexWorker = 32;
if (parallelWorkers != 0) {
parallelWorkers = Min(maxHashbucketIndexWorker, parallelWorkers);
}
if (indexInfo->ii_Concurrent && indexInfo->ii_ParallelWorkers > 0) {
ereport(NOTICE, (errmsg("switch off parallel mode when concurrently flag is set")));
parallelWorkers = 0;
}
if (heapRelation->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && indexInfo->ii_ParallelWorkers > 0) {
ereport(NOTICE, (errmsg("switch off parallel mode for global temp table")));
parallelWorkers = 0;
}
/* disable parallel building index for system table */
if (IsCatalogRelation(heapRelation)) {
parallelWorkers = 0;
}
return parallelWorkers;
}