mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 20:15:46 +08:00
Compare commits
13 Commits
hnsw-fast-
...
half
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46eda44530 | ||
|
|
02f55399eb | ||
|
|
a679b584e9 | ||
|
|
006e5c7aca | ||
|
|
ba3730d2a5 | ||
|
|
7aea658b26 | ||
|
|
7620089901 | ||
|
|
544e303d74 | ||
|
|
d09aa9f873 | ||
|
|
95eff595f0 | ||
|
|
4b630d4f27 | ||
|
|
3b2198d52c | ||
|
|
422667f6c6 |
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -20,8 +20,6 @@ jobs:
|
|||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
- postgres: 12
|
- postgres: 12
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
- postgres: 11
|
|
||||||
os: ubuntu-20.04
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: ankane/setup-postgres@v1
|
- uses: ankane/setup-postgres@v1
|
||||||
@@ -62,7 +60,7 @@ jobs:
|
|||||||
wget -q https://github.com/postgres/postgres/archive/refs/tags/REL_14_5.tar.gz
|
wget -q https://github.com/postgres/postgres/archive/refs/tags/REL_14_5.tar.gz
|
||||||
tar xf REL_14_5.tar.gz
|
tar xf REL_14_5.tar.gz
|
||||||
- run: make prove_installcheck PROVE_FLAGS="-I ./postgres-REL_14_5/src/test/perl" PERL5LIB="/Users/runner/perl5/lib/perl5"
|
- run: make prove_installcheck PROVE_FLAGS="-I ./postgres-REL_14_5/src/test/perl" PERL5LIB="/Users/runner/perl5/lib/perl5"
|
||||||
- run: make clean && /usr/local/opt/llvm@15/bin/scan-build --status-bugs make PG_CFLAGS="-DUSE_ASSERT_CHECKING"
|
- run: make clean && /usr/local/opt/llvm@15/bin/scan-build --status-bugs make
|
||||||
windows:
|
windows:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
if: ${{ !startsWith(github.ref_name, 'mac') }}
|
if: ${{ !startsWith(github.ref_name, 'mac') }}
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
## 0.5.2 (unreleased)
|
## 0.5.2 (unreleased)
|
||||||
|
|
||||||
- Improved performance of HNSW
|
- Added support for on-disk parallel index builds for HNSW
|
||||||
- Added support for parallel index builds for HNSW
|
|
||||||
- Reduced memory usage for HNSW index builds
|
|
||||||
- Reduced WAL generation for HNSW index builds
|
|
||||||
- Fixed `invalid memory alloc request size` error with HNSW index build
|
|
||||||
|
|
||||||
## 0.5.1 (2023-10-10)
|
## 0.5.1 (2023-10-10)
|
||||||
|
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -3,8 +3,8 @@ EXTVERSION = 0.5.1
|
|||||||
|
|
||||||
MODULE_big = vector
|
MODULE_big = vector
|
||||||
DATA = $(wildcard sql/*--*.sql)
|
DATA = $(wildcard sql/*--*.sql)
|
||||||
OBJS = src/hnsw.o src/hnswbuild.o src/hnswinsert.o src/hnswscan.o src/hnswutils.o src/hnswvacuum.o src/ivfbuild.o src/ivfflat.o src/ivfinsert.o src/ivfkmeans.o src/ivfscan.o src/ivfutils.o src/ivfvacuum.o src/vector.o
|
OBJS = src/half.o src/hnsw.o src/hnswbuild.o src/hnswinsert.o src/hnswscan.o src/hnswutils.o src/hnswvacuum.o src/ivfbuild.o src/ivfflat.o src/ivfinsert.o src/ivfkmeans.o src/ivfscan.o src/ivfutils.o src/ivfvacuum.o src/vector.o
|
||||||
HEADERS = src/vector.h
|
HEADERS = src/half.h src/vector.h
|
||||||
|
|
||||||
TESTS = $(wildcard test/sql/*.sql)
|
TESTS = $(wildcard test/sql/*.sql)
|
||||||
REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS))
|
REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS))
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
EXTENSION = vector
|
EXTENSION = vector
|
||||||
EXTVERSION = 0.5.1
|
EXTVERSION = 0.5.1
|
||||||
|
|
||||||
OBJS = src\hnsw.obj src\hnswbuild.obj src\hnswinsert.obj src\hnswscan.obj src\hnswutils.obj src\hnswvacuum.obj src\ivfbuild.obj src\ivfflat.obj src\ivfinsert.obj src\ivfkmeans.obj src\ivfscan.obj src\ivfutils.obj src\ivfvacuum.obj src\vector.obj
|
OBJS = src\half.obj src\hnsw.obj src\hnswbuild.obj src\hnswinsert.obj src\hnswscan.obj src\hnswutils.obj src\hnswvacuum.obj src\ivfbuild.obj src\ivfflat.obj src\ivfinsert.obj src\ivfkmeans.obj src\ivfscan.obj src\ivfutils.obj src\ivfvacuum.obj src\vector.obj
|
||||||
HEADERS = src\vector.h
|
HEADERS = src\half.h src\vector.h
|
||||||
|
|
||||||
REGRESS = btree cast copy functions input ivfflat_cosine ivfflat_ip ivfflat_l2 ivfflat_options ivfflat_unlogged
|
REGRESS = btree cast copy functions input ivfflat_cosine ivfflat_ip ivfflat_l2 ivfflat_options ivfflat_unlogged
|
||||||
REGRESS_OPTS = --inputdir=test --load-extension=$(EXTENSION)
|
REGRESS_OPTS = --inputdir=test --load-extension=$(EXTENSION)
|
||||||
|
|||||||
181
README.md
181
README.md
@@ -161,97 +161,8 @@ You can add an index to use approximate nearest neighbor search, which trades so
|
|||||||
|
|
||||||
Supported index types are:
|
Supported index types are:
|
||||||
|
|
||||||
- [HNSW](#hnsw) - added in 0.5.0
|
|
||||||
- [IVFFlat](#ivfflat)
|
- [IVFFlat](#ivfflat)
|
||||||
|
- [HNSW](#hnsw) - added in 0.5.0
|
||||||
## HNSW
|
|
||||||
|
|
||||||
An HNSW index creates a multilayer graph. It has better query performance than IVFFlat (in terms of speed-recall tradeoff), but has slower build times and uses more memory. Also, an index can be created without any data in the table since there isn’t a training step like IVFFlat.
|
|
||||||
|
|
||||||
Add an index for each distance function you want to use.
|
|
||||||
|
|
||||||
L2 distance
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);
|
|
||||||
```
|
|
||||||
|
|
||||||
Inner product
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE INDEX ON items USING hnsw (embedding vector_ip_ops);
|
|
||||||
```
|
|
||||||
|
|
||||||
Cosine distance
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);
|
|
||||||
```
|
|
||||||
|
|
||||||
Vectors with up to 2,000 dimensions can be indexed.
|
|
||||||
|
|
||||||
### Index Options
|
|
||||||
|
|
||||||
Specify HNSW parameters
|
|
||||||
|
|
||||||
- `m` - the max number of connections per layer (16 by default)
|
|
||||||
- `ef_construction` - the size of the dynamic candidate list for constructing the graph (64 by default)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WITH (m = 16, ef_construction = 64);
|
|
||||||
```
|
|
||||||
|
|
||||||
A higher value of `ef_construction` provides better recall at the cost of index build time / insert speed.
|
|
||||||
|
|
||||||
### Query Options
|
|
||||||
|
|
||||||
Specify the size of the dynamic candidate list for search (40 by default)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SET hnsw.ef_search = 100;
|
|
||||||
```
|
|
||||||
|
|
||||||
A higher value provides better recall at the cost of speed.
|
|
||||||
|
|
||||||
Use `SET LOCAL` inside a transaction to set it for a single query
|
|
||||||
|
|
||||||
```sql
|
|
||||||
BEGIN;
|
|
||||||
SET LOCAL hnsw.ef_search = 100;
|
|
||||||
SELECT ...
|
|
||||||
COMMIT;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Index Build Time
|
|
||||||
|
|
||||||
Indexes build significantly faster when the graph fits into `maintenance_work_mem`
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SET maintenance_work_mem = '8GB';
|
|
||||||
```
|
|
||||||
|
|
||||||
A notice is shown when the graph no longer fits
|
|
||||||
|
|
||||||
```text
|
|
||||||
NOTICE: hnsw graph no longer fits into maintenance_work_mem after 100000 tuples
|
|
||||||
DETAIL: Building will take significantly more time.
|
|
||||||
HINT: Increase maintenance_work_mem to speed up builds.
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: Do not set `maintenance_work_mem` so high that it exhausts the memory on the server
|
|
||||||
|
|
||||||
### Indexing Progress
|
|
||||||
|
|
||||||
Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING) with Postgres 12+
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
|
|
||||||
```
|
|
||||||
|
|
||||||
The phases for HNSW are:
|
|
||||||
|
|
||||||
1. `initializing`
|
|
||||||
2. `loading tuples`
|
|
||||||
|
|
||||||
## IVFFlat
|
## IVFFlat
|
||||||
|
|
||||||
@@ -321,6 +232,77 @@ The phases for IVFFlat are:
|
|||||||
|
|
||||||
Note: `%` is only populated during the `loading tuples` phase
|
Note: `%` is only populated during the `loading tuples` phase
|
||||||
|
|
||||||
|
## HNSW
|
||||||
|
|
||||||
|
An HNSW index creates a multilayer graph. It has slower build times and uses more memory than IVFFlat, but has better query performance (in terms of speed-recall tradeoff). There’s no training step like IVFFlat, so the index can be created without any data in the table.
|
||||||
|
|
||||||
|
Add an index for each distance function you want to use.
|
||||||
|
|
||||||
|
L2 distance
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);
|
||||||
|
```
|
||||||
|
|
||||||
|
Inner product
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON items USING hnsw (embedding vector_ip_ops);
|
||||||
|
```
|
||||||
|
|
||||||
|
Cosine distance
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);
|
||||||
|
```
|
||||||
|
|
||||||
|
Vectors with up to 2,000 dimensions can be indexed.
|
||||||
|
|
||||||
|
### Index Options
|
||||||
|
|
||||||
|
Specify HNSW parameters
|
||||||
|
|
||||||
|
- `m` - the max number of connections per layer (16 by default)
|
||||||
|
- `ef_construction` - the size of the dynamic candidate list for constructing the graph (64 by default)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WITH (m = 16, ef_construction = 64);
|
||||||
|
```
|
||||||
|
|
||||||
|
A higher value of `ef_construction` provides better recall at the cost of index build time / insert speed.
|
||||||
|
|
||||||
|
### Query Options
|
||||||
|
|
||||||
|
Specify the size of the dynamic candidate list for search (40 by default)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SET hnsw.ef_search = 100;
|
||||||
|
```
|
||||||
|
|
||||||
|
A higher value provides better recall at the cost of speed.
|
||||||
|
|
||||||
|
Use `SET LOCAL` inside a transaction to set it for a single query
|
||||||
|
|
||||||
|
```sql
|
||||||
|
BEGIN;
|
||||||
|
SET LOCAL hnsw.ef_search = 100;
|
||||||
|
SELECT ...
|
||||||
|
COMMIT;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Indexing Progress
|
||||||
|
|
||||||
|
Check [indexing progress](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING) with Postgres 12+
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
|
||||||
|
```
|
||||||
|
|
||||||
|
The phases for HNSW are:
|
||||||
|
|
||||||
|
1. `initializing`
|
||||||
|
2. `loading tuples`
|
||||||
|
|
||||||
## Filtering
|
## Filtering
|
||||||
|
|
||||||
There are a few ways to index nearest neighbor queries with a `WHERE` clause
|
There are a few ways to index nearest neighbor queries with a `WHERE` clause
|
||||||
@@ -338,7 +320,8 @@ CREATE INDEX ON items (category_id);
|
|||||||
Or a [partial index](https://www.postgresql.org/docs/current/indexes-partial.html) on the vector column for approximate search
|
Or a [partial index](https://www.postgresql.org/docs/current/indexes-partial.html) on the vector column for approximate search
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WHERE (category_id = 123);
|
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100)
|
||||||
|
WHERE (category_id = 123);
|
||||||
```
|
```
|
||||||
|
|
||||||
Use [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) for approximate search on many different values of the `WHERE` columns
|
Use [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) for approximate search on many different values of the `WHERE` columns
|
||||||
@@ -588,10 +571,10 @@ If compilation fails with `fatal error: postgres.h: No such file or directory`,
|
|||||||
For Ubuntu and Debian, use:
|
For Ubuntu and Debian, use:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo apt install postgresql-server-dev-16
|
sudo apt install postgresql-server-dev-15
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: Replace `16` with your Postgres server version
|
Note: Replace `15` with your Postgres server version
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
@@ -606,7 +589,7 @@ Note: The exact path will vary depending on your Visual Studio version and editi
|
|||||||
Then use `nmake` to build:
|
Then use `nmake` to build:
|
||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
set "PGROOT=C:\Program Files\PostgreSQL\16"
|
set "PGROOT=C:\Program Files\PostgreSQL\15"
|
||||||
git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git
|
git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git
|
||||||
cd pgvector
|
cd pgvector
|
||||||
nmake /F Makefile.win
|
nmake /F Makefile.win
|
||||||
@@ -656,22 +639,22 @@ pgxn install vector
|
|||||||
Debian and Ubuntu packages are available from the [PostgreSQL APT Repository](https://wiki.postgresql.org/wiki/Apt). Follow the [setup instructions](https://wiki.postgresql.org/wiki/Apt#Quickstart) and run:
|
Debian and Ubuntu packages are available from the [PostgreSQL APT Repository](https://wiki.postgresql.org/wiki/Apt). Follow the [setup instructions](https://wiki.postgresql.org/wiki/Apt#Quickstart) and run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo apt install postgresql-16-pgvector
|
sudo apt install postgresql-15-pgvector
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: Replace `16` with your Postgres server version
|
Note: Replace `15` with your Postgres server version
|
||||||
|
|
||||||
### Yum
|
### Yum
|
||||||
|
|
||||||
RPM packages are available from the [PostgreSQL Yum Repository](https://yum.postgresql.org/). Follow the [setup instructions](https://www.postgresql.org/download/linux/redhat/) for your distribution and run:
|
RPM packages are available from the [PostgreSQL Yum Repository](https://yum.postgresql.org/). Follow the [setup instructions](https://www.postgresql.org/download/linux/redhat/) for your distribution and run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo yum install pgvector_16
|
sudo yum install pgvector_15
|
||||||
# or
|
# or
|
||||||
sudo dnf install pgvector_16
|
sudo dnf install pgvector_15
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: Replace `16` with your Postgres server version
|
Note: Replace `15` with your Postgres server version
|
||||||
|
|
||||||
### conda-forge
|
### conda-forge
|
||||||
|
|
||||||
|
|||||||
101
sql/vector--0.5.1--0.6.0.sql
Normal file
101
sql/vector--0.5.1--0.6.0.sql
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||||
|
\echo Use "ALTER EXTENSION vector UPDATE TO '0.6.0'" to load this file. \quit
|
||||||
|
|
||||||
|
CREATE TYPE half;
|
||||||
|
|
||||||
|
CREATE FUNCTION half_in(cstring, oid, integer) RETURNS half
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION half_out(half) RETURNS cstring
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION half_recv(internal, oid, integer) RETURNS half
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION half_send(half) RETURNS bytea
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE TYPE half (
|
||||||
|
INPUT = half_in,
|
||||||
|
OUTPUT = half_out,
|
||||||
|
RECEIVE = half_recv,
|
||||||
|
SEND = half_send,
|
||||||
|
INTERNALLENGTH = 2,
|
||||||
|
PASSEDBYVALUE,
|
||||||
|
ALIGNMENT = int2
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE FUNCTION l2_distance(half[], half[]) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME', 'half_l2_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION inner_product(half[], half[]) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME', 'half_inner_product' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION cosine_distance(half[], half[]) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME', 'half_cosine_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION l1_distance(half[], half[]) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME', 'half_l1_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION half_l2_squared_distance(half[], half[]) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION half_negative_inner_product(half[], half[]) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION float4_to_half(real, integer, boolean) RETURNS half
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION half_to_float4(half, integer, boolean) RETURNS real
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION float8_to_half(float8, integer, boolean) RETURNS half
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION half_to_float8(half, integer, boolean) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION integer_to_half(integer, integer, boolean) RETURNS half
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION numeric_to_half(numeric, integer, boolean) RETURNS half
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION half_to_numeric(half, integer, boolean) RETURNS numeric
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE CAST (real AS half)
|
||||||
|
WITH FUNCTION float4_to_half(real, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE CAST (half AS real)
|
||||||
|
WITH FUNCTION half_to_float4(half, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE CAST (float8 AS half)
|
||||||
|
WITH FUNCTION float8_to_half(float8, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE CAST (half AS float8)
|
||||||
|
WITH FUNCTION half_to_float8(half, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE CAST (integer AS half)
|
||||||
|
WITH FUNCTION integer_to_half(integer, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE CAST (numeric AS half)
|
||||||
|
WITH FUNCTION numeric_to_half(numeric, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE CAST (half AS numeric)
|
||||||
|
WITH FUNCTION half_to_numeric(half, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE OPERATOR <-> (
|
||||||
|
LEFTARG = half[], RIGHTARG = half[], PROCEDURE = l2_distance,
|
||||||
|
COMMUTATOR = '<->'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR <#> (
|
||||||
|
LEFTARG = half[], RIGHTARG = half[], PROCEDURE = half_negative_inner_product,
|
||||||
|
COMMUTATOR = '<#>'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR <=> (
|
||||||
|
LEFTARG = half[], RIGHTARG = half[], PROCEDURE = cosine_distance,
|
||||||
|
COMMUTATOR = '<=>'
|
||||||
|
);
|
||||||
111
sql/vector.sql
111
sql/vector.sql
@@ -290,3 +290,114 @@ CREATE OPERATOR CLASS vector_cosine_ops
|
|||||||
OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops,
|
OPERATOR 1 <=> (vector, vector) FOR ORDER BY float_ops,
|
||||||
FUNCTION 1 vector_negative_inner_product(vector, vector),
|
FUNCTION 1 vector_negative_inner_product(vector, vector),
|
||||||
FUNCTION 2 vector_norm(vector);
|
FUNCTION 2 vector_norm(vector);
|
||||||
|
|
||||||
|
-- half type
|
||||||
|
|
||||||
|
CREATE TYPE half;
|
||||||
|
|
||||||
|
CREATE FUNCTION half_in(cstring, oid, integer) RETURNS half
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION half_out(half) RETURNS cstring
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION half_recv(internal, oid, integer) RETURNS half
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION half_send(half) RETURNS bytea
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE TYPE half (
|
||||||
|
INPUT = half_in,
|
||||||
|
OUTPUT = half_out,
|
||||||
|
RECEIVE = half_recv,
|
||||||
|
SEND = half_send,
|
||||||
|
INTERNALLENGTH = 2,
|
||||||
|
PASSEDBYVALUE,
|
||||||
|
ALIGNMENT = int2
|
||||||
|
);
|
||||||
|
|
||||||
|
-- half functions
|
||||||
|
|
||||||
|
CREATE FUNCTION l2_distance(half[], half[]) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME', 'half_l2_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION inner_product(half[], half[]) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME', 'half_inner_product' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION cosine_distance(half[], half[]) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME', 'half_cosine_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION l1_distance(half[], half[]) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME', 'half_l1_distance' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
-- half private functions
|
||||||
|
|
||||||
|
CREATE FUNCTION half_l2_squared_distance(half[], half[]) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION half_negative_inner_product(half[], half[]) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
-- half cast functions
|
||||||
|
|
||||||
|
CREATE FUNCTION float4_to_half(real, integer, boolean) RETURNS half
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION half_to_float4(half, integer, boolean) RETURNS real
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION float8_to_half(float8, integer, boolean) RETURNS half
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION half_to_float8(half, integer, boolean) RETURNS float8
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION integer_to_half(integer, integer, boolean) RETURNS half
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION numeric_to_half(numeric, integer, boolean) RETURNS half
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
CREATE FUNCTION half_to_numeric(half, integer, boolean) RETURNS numeric
|
||||||
|
AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||||
|
|
||||||
|
-- half casts
|
||||||
|
|
||||||
|
CREATE CAST (real AS half)
|
||||||
|
WITH FUNCTION float4_to_half(real, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE CAST (half AS real)
|
||||||
|
WITH FUNCTION half_to_float4(half, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE CAST (float8 AS half)
|
||||||
|
WITH FUNCTION float8_to_half(float8, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE CAST (half AS float8)
|
||||||
|
WITH FUNCTION half_to_float8(half, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE CAST (integer AS half)
|
||||||
|
WITH FUNCTION integer_to_half(integer, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE CAST (numeric AS half)
|
||||||
|
WITH FUNCTION numeric_to_half(numeric, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
CREATE CAST (half AS numeric)
|
||||||
|
WITH FUNCTION half_to_numeric(half, integer, boolean) AS IMPLICIT;
|
||||||
|
|
||||||
|
-- half operators
|
||||||
|
|
||||||
|
CREATE OPERATOR <-> (
|
||||||
|
LEFTARG = half[], RIGHTARG = half[], PROCEDURE = l2_distance,
|
||||||
|
COMMUTATOR = '<->'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR <#> (
|
||||||
|
LEFTARG = half[], RIGHTARG = half[], PROCEDURE = half_negative_inner_product,
|
||||||
|
COMMUTATOR = '<#>'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OPERATOR <=> (
|
||||||
|
LEFTARG = half[], RIGHTARG = half[], PROCEDURE = cosine_distance,
|
||||||
|
COMMUTATOR = '<=>'
|
||||||
|
);
|
||||||
|
|||||||
694
src/half.c
Normal file
694
src/half.c
Normal file
@@ -0,0 +1,694 @@
|
|||||||
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "common/shortest_dec.h"
|
||||||
|
#include "fmgr.h"
|
||||||
|
#include "half.h"
|
||||||
|
#include "lib/stringinfo.h"
|
||||||
|
#include "libpq/pqformat.h"
|
||||||
|
#include "utils/array.h"
|
||||||
|
#include "utils/builtins.h"
|
||||||
|
#include "utils/float.h"
|
||||||
|
#include "utils/numeric.h"
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM < 120003
|
||||||
|
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")));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if array is a vector
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
CheckArrayIsVector(ArrayType *array)
|
||||||
|
{
|
||||||
|
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")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if dimensions are the same
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
CheckDims(ArrayType *a, ArrayType *b)
|
||||||
|
{
|
||||||
|
int dima;
|
||||||
|
int dimb;
|
||||||
|
|
||||||
|
CheckArrayIsVector(a);
|
||||||
|
CheckArrayIsVector(b);
|
||||||
|
|
||||||
|
dima = ARR_DIMS(a)[0];
|
||||||
|
dimb = ARR_DIMS(b)[0];
|
||||||
|
|
||||||
|
if (dima != dimb)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||||
|
errmsg("different dimensions %d and %d", dima, dimb)));
|
||||||
|
|
||||||
|
return dima;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the datum representation for a half
|
||||||
|
*/
|
||||||
|
static inline Datum
|
||||||
|
HalfGetDatum(half X)
|
||||||
|
{
|
||||||
|
union
|
||||||
|
{
|
||||||
|
half value;
|
||||||
|
int16 retval;
|
||||||
|
} myunion;
|
||||||
|
|
||||||
|
myunion.value = X;
|
||||||
|
return Int16GetDatum(myunion.retval);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the half value of a datum
|
||||||
|
*/
|
||||||
|
static inline half
|
||||||
|
DatumGetHalf(Datum X)
|
||||||
|
{
|
||||||
|
union
|
||||||
|
{
|
||||||
|
int16 value;
|
||||||
|
half retval;
|
||||||
|
} myunion;
|
||||||
|
|
||||||
|
myunion.value = DatumGetInt16(X);
|
||||||
|
return myunion.retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get a half from a message buffer
|
||||||
|
*/
|
||||||
|
static half
|
||||||
|
pq_getmsghalf(StringInfo msg)
|
||||||
|
{
|
||||||
|
union
|
||||||
|
{
|
||||||
|
half h;
|
||||||
|
uint16 i;
|
||||||
|
} swap;
|
||||||
|
|
||||||
|
swap.i = pq_getmsgint(msg, 2);
|
||||||
|
return swap.h;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Append a half to a StringInfo buffer
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
pq_sendhalf(StringInfo buf, half h)
|
||||||
|
{
|
||||||
|
union
|
||||||
|
{
|
||||||
|
half h;
|
||||||
|
uint16 i;
|
||||||
|
} swap;
|
||||||
|
|
||||||
|
swap.h = h;
|
||||||
|
pq_sendint16(buf, swap.i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert a half to a float4
|
||||||
|
*/
|
||||||
|
static float
|
||||||
|
HalfToFloat4(half num)
|
||||||
|
{
|
||||||
|
#ifdef FLT16_SUPPORT
|
||||||
|
return (float) num;
|
||||||
|
#else
|
||||||
|
/* TODO Improve performance */
|
||||||
|
/* TODO Check endianness */
|
||||||
|
uint16 bin = *((uint16 *) &num);
|
||||||
|
uint32 exponent = (bin & 0x7C00) >> 10;
|
||||||
|
uint32 mantissa = bin & 0x03FF;
|
||||||
|
|
||||||
|
/* Sign */
|
||||||
|
uint32 result = (bin & 0x8000) << 16;
|
||||||
|
|
||||||
|
if (exponent == 31)
|
||||||
|
{
|
||||||
|
if (mantissa == 0)
|
||||||
|
{
|
||||||
|
/* Infinite */
|
||||||
|
result |= 0x7F800000;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* NaN */
|
||||||
|
result |= 0x7FC00000;
|
||||||
|
result |= mantissa << 13;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (exponent == 0)
|
||||||
|
{
|
||||||
|
/* Subnormal */
|
||||||
|
if (mantissa != 0)
|
||||||
|
{
|
||||||
|
exponent = -14;
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
mantissa <<= 1;
|
||||||
|
exponent -= 1;
|
||||||
|
|
||||||
|
if ((mantissa >> 10) % 2 == 1)
|
||||||
|
{
|
||||||
|
mantissa &= 0x03ff;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result |= (exponent + 127) << 23;
|
||||||
|
result |= mantissa << 13;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Normal */
|
||||||
|
result |= (exponent - 15 + 127) << 23;
|
||||||
|
result |= mantissa << 13;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *((float *) &result);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert a float4 to a half
|
||||||
|
*/
|
||||||
|
static half
|
||||||
|
Float4ToHalfUnchecked(float num)
|
||||||
|
{
|
||||||
|
#ifdef FLT16_SUPPORT
|
||||||
|
return (_Float16) num;
|
||||||
|
#else
|
||||||
|
/* TODO Improve performance */
|
||||||
|
/* TODO Check endianness */
|
||||||
|
uint32 bin = *((uint32 *) &num);
|
||||||
|
int exponent = (bin & 0x7F800000) >> 23;
|
||||||
|
int mantissa = bin & 0x007FFFFF;
|
||||||
|
|
||||||
|
/* Sign */
|
||||||
|
uint16 result = (bin & 0x80000000) >> 16;
|
||||||
|
|
||||||
|
if (isinf(num))
|
||||||
|
{
|
||||||
|
/* Infinite */
|
||||||
|
result |= 0x7C00;
|
||||||
|
}
|
||||||
|
else if (isnan(num))
|
||||||
|
{
|
||||||
|
/* NaN */
|
||||||
|
result |= 0x7E00;
|
||||||
|
result |= mantissa >> 13;
|
||||||
|
}
|
||||||
|
else if (exponent > 98)
|
||||||
|
{
|
||||||
|
int m;
|
||||||
|
int gr;
|
||||||
|
int s;
|
||||||
|
|
||||||
|
exponent -= 127;
|
||||||
|
s = mantissa & 0x00000FFF;
|
||||||
|
|
||||||
|
/* Subnormal */
|
||||||
|
if (exponent < -14)
|
||||||
|
{
|
||||||
|
int diff = -exponent - 14;
|
||||||
|
|
||||||
|
mantissa >>= diff;
|
||||||
|
mantissa += 1 << (23 - diff);
|
||||||
|
s |= mantissa & 0x00000FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
m = mantissa >> 13;
|
||||||
|
|
||||||
|
/* Round */
|
||||||
|
gr = (mantissa >> 12) % 4;
|
||||||
|
if (gr == 3 || (gr == 1 && s != 0))
|
||||||
|
m += 1;
|
||||||
|
|
||||||
|
if (m == 1024)
|
||||||
|
{
|
||||||
|
m = 0;
|
||||||
|
exponent += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exponent > 15)
|
||||||
|
{
|
||||||
|
/* Infinite */
|
||||||
|
result |= 0x7C00;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (exponent >= -14)
|
||||||
|
result |= (exponent + 15) << 10;
|
||||||
|
|
||||||
|
result |= m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *((half *) & result);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if half is infinite
|
||||||
|
*/
|
||||||
|
static inline bool
|
||||||
|
HalfIsInf(half num)
|
||||||
|
{
|
||||||
|
#ifdef FLT16_SUPPORT
|
||||||
|
return isinf(num);
|
||||||
|
#else
|
||||||
|
return (num << 1) == 0xF800;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if half is zero
|
||||||
|
*/
|
||||||
|
static inline bool
|
||||||
|
HalfIsZero(half num)
|
||||||
|
{
|
||||||
|
#ifdef FLT16_SUPPORT
|
||||||
|
return num == 0;
|
||||||
|
#else
|
||||||
|
return (num << 1) == 0x0000;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert a float4 to a half
|
||||||
|
*/
|
||||||
|
static half
|
||||||
|
Float4ToHalf(float num)
|
||||||
|
{
|
||||||
|
half result = Float4ToHalfUnchecked(num);
|
||||||
|
|
||||||
|
if (unlikely(HalfIsInf(result)) && !isinf(num))
|
||||||
|
float_overflow_error();
|
||||||
|
if (unlikely(HalfIsZero(result)) && num != 0.0)
|
||||||
|
float_underflow_error();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert a float8 to a half
|
||||||
|
*/
|
||||||
|
static half
|
||||||
|
Float8ToHalf(double num)
|
||||||
|
{
|
||||||
|
/* TODO Convert directly for greater accuracy */
|
||||||
|
half result = Float4ToHalfUnchecked((float) num);
|
||||||
|
|
||||||
|
if (unlikely(HalfIsInf(result)) && !isinf(num))
|
||||||
|
float_overflow_error();
|
||||||
|
if (unlikely(HalfIsZero(result)) && num != 0.0)
|
||||||
|
float_underflow_error();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert textual representation to internal representation
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_in);
|
||||||
|
Datum
|
||||||
|
half_in(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
char *num = PG_GETARG_CSTRING(0);
|
||||||
|
char *orig_num;
|
||||||
|
float val;
|
||||||
|
char *endptr;
|
||||||
|
|
||||||
|
orig_num = num;
|
||||||
|
|
||||||
|
/* Skip leading whitespace */
|
||||||
|
while (*num != '\0' && isspace((unsigned char) *num))
|
||||||
|
num++;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check for an empty-string input to begin with, to avoid the vagaries of
|
||||||
|
* strtof() on different platforms.
|
||||||
|
*/
|
||||||
|
if (*num == '\0')
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||||
|
errmsg("invalid input syntax for type %s: \"%s\"",
|
||||||
|
"half", orig_num)));
|
||||||
|
|
||||||
|
val = strtof(num, &endptr);
|
||||||
|
|
||||||
|
if (val < -HALF_MAX || val > HALF_MAX)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||||
|
errmsg("\"%s\" is out of range for type %s",
|
||||||
|
orig_num, "half")));
|
||||||
|
|
||||||
|
/* Skip trailing whitespace */
|
||||||
|
while (*endptr != '\0' && isspace((unsigned char) *endptr))
|
||||||
|
endptr++;
|
||||||
|
|
||||||
|
/* If there is any junk left at the end of the string, bail out */
|
||||||
|
if (*endptr != '\0')
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
||||||
|
errmsg("invalid input syntax for type %s: \"%s\"",
|
||||||
|
"half", orig_num)));
|
||||||
|
|
||||||
|
PG_RETURN_HALF(Float4ToHalf(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert internal representation to textual representation
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_out);
|
||||||
|
Datum
|
||||||
|
half_out(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
float num = HalfToFloat4(PG_GETARG_HALF(0));
|
||||||
|
char *ascii = (char *) palloc(32);
|
||||||
|
int ndig = FLT_DIG + extra_float_digits;
|
||||||
|
|
||||||
|
if (extra_float_digits > 0)
|
||||||
|
{
|
||||||
|
float_to_shortest_decimal_buf(num, ascii);
|
||||||
|
PG_RETURN_CSTRING(ascii);
|
||||||
|
}
|
||||||
|
|
||||||
|
(void) pg_strfromd(ascii, 32, ndig, num);
|
||||||
|
PG_RETURN_CSTRING(ascii);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert external binary representation to internal representation
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_recv);
|
||||||
|
Datum
|
||||||
|
half_recv(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
|
||||||
|
|
||||||
|
PG_RETURN_HALF(pq_getmsghalf(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert internal representation to the external binary representation
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_send);
|
||||||
|
Datum
|
||||||
|
half_send(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
half arg1 = PG_GETARG_HALF(0);
|
||||||
|
StringInfoData buf;
|
||||||
|
|
||||||
|
pq_begintypsend(&buf);
|
||||||
|
pq_sendhalf(&buf, arg1);
|
||||||
|
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert integer to half
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(integer_to_half);
|
||||||
|
Datum
|
||||||
|
integer_to_half(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
int32 i = PG_GETARG_INT32(0);
|
||||||
|
|
||||||
|
/* TODO Figure out correct error */
|
||||||
|
float f = (float) i;
|
||||||
|
half h = Float4ToHalf(f);
|
||||||
|
|
||||||
|
PG_RETURN_HALF(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert numeric to half
|
||||||
|
*
|
||||||
|
* TODO Improve error message if out of range
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(numeric_to_half);
|
||||||
|
Datum
|
||||||
|
numeric_to_half(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
Numeric num = PG_GETARG_NUMERIC(0);
|
||||||
|
float f = DatumGetFloat4(DirectFunctionCall1(numeric_float4, NumericGetDatum(num)));
|
||||||
|
half h = Float4ToHalf(f);
|
||||||
|
|
||||||
|
PG_RETURN_HALF(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert half to numeric
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_to_numeric);
|
||||||
|
Datum
|
||||||
|
half_to_numeric(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
half h = PG_GETARG_HALF(0);
|
||||||
|
float f = HalfToFloat4(h);
|
||||||
|
Numeric num = DatumGetNumeric(DirectFunctionCall1(float4_numeric, Float4GetDatum(f)));
|
||||||
|
|
||||||
|
PG_RETURN_NUMERIC(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert float4 to half
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(float4_to_half);
|
||||||
|
Datum
|
||||||
|
float4_to_half(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
float f = PG_GETARG_FLOAT4(0);
|
||||||
|
half h = Float4ToHalf(f);
|
||||||
|
|
||||||
|
PG_RETURN_HALF(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert half to float4
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_to_float4);
|
||||||
|
Datum
|
||||||
|
half_to_float4(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
half h = PG_GETARG_HALF(0);
|
||||||
|
float f = HalfToFloat4(h);
|
||||||
|
|
||||||
|
PG_RETURN_FLOAT4(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert float8 to half
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(float8_to_half);
|
||||||
|
Datum
|
||||||
|
float8_to_half(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
float8 d = PG_GETARG_FLOAT8(0);
|
||||||
|
half h = Float8ToHalf(d);
|
||||||
|
|
||||||
|
PG_RETURN_HALF(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert half to float8
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_to_float8);
|
||||||
|
Datum
|
||||||
|
half_to_float8(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
half h = PG_GETARG_HALF(0);
|
||||||
|
float f = HalfToFloat4(h);
|
||||||
|
|
||||||
|
PG_RETURN_FLOAT8((double) f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the L2 distance between half arrays
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_l2_distance);
|
||||||
|
Datum
|
||||||
|
half_l2_distance(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
ArrayType *a = PG_GETARG_ARRAYTYPE_P(0);
|
||||||
|
ArrayType *b = PG_GETARG_ARRAYTYPE_P(1);
|
||||||
|
half *ax = (half *) ARR_DATA_PTR(a);
|
||||||
|
half *bx = (half *) ARR_DATA_PTR(b);
|
||||||
|
float distance = 0.0;
|
||||||
|
int dim = CheckDims(a, b);
|
||||||
|
|
||||||
|
/* Auto-vectorized */
|
||||||
|
for (int i = 0; i < dim; i++)
|
||||||
|
{
|
||||||
|
float diff = HalfToFloat4(ax[i]) - HalfToFloat4(bx[i]);
|
||||||
|
|
||||||
|
distance += diff * diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
PG_RETURN_FLOAT8(sqrt((double) distance));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the L2 squared distance between half arrays
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_l2_squared_distance);
|
||||||
|
Datum
|
||||||
|
half_l2_squared_distance(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
ArrayType *a = PG_GETARG_ARRAYTYPE_P(0);
|
||||||
|
ArrayType *b = PG_GETARG_ARRAYTYPE_P(1);
|
||||||
|
half *ax = (half *) ARR_DATA_PTR(a);
|
||||||
|
half *bx = (half *) ARR_DATA_PTR(b);
|
||||||
|
float distance = 0.0;
|
||||||
|
int dim = CheckDims(a, b);
|
||||||
|
|
||||||
|
/* Auto-vectorized */
|
||||||
|
for (int i = 0; i < dim; i++)
|
||||||
|
{
|
||||||
|
float diff = HalfToFloat4(ax[i]) - HalfToFloat4(bx[i]);
|
||||||
|
|
||||||
|
distance += diff * diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
PG_RETURN_FLOAT8((double) distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the inner product of two half arrays
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_inner_product);
|
||||||
|
Datum
|
||||||
|
half_inner_product(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
ArrayType *a = PG_GETARG_ARRAYTYPE_P(0);
|
||||||
|
ArrayType *b = PG_GETARG_ARRAYTYPE_P(1);
|
||||||
|
half *ax = (half *) ARR_DATA_PTR(a);
|
||||||
|
half *bx = (half *) ARR_DATA_PTR(b);
|
||||||
|
float distance = 0.0;
|
||||||
|
int dim = CheckDims(a, b);
|
||||||
|
|
||||||
|
/* Auto-vectorized */
|
||||||
|
for (int i = 0; i < dim; i++)
|
||||||
|
distance += HalfToFloat4(ax[i]) * HalfToFloat4(bx[i]);
|
||||||
|
|
||||||
|
PG_RETURN_FLOAT8((double) distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the negative inner product of two half arrays
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_negative_inner_product);
|
||||||
|
Datum
|
||||||
|
half_negative_inner_product(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
ArrayType *a = PG_GETARG_ARRAYTYPE_P(0);
|
||||||
|
ArrayType *b = PG_GETARG_ARRAYTYPE_P(1);
|
||||||
|
half *ax = (half *) ARR_DATA_PTR(a);
|
||||||
|
half *bx = (half *) ARR_DATA_PTR(b);
|
||||||
|
float distance = 0.0;
|
||||||
|
int dim = CheckDims(a, b);
|
||||||
|
|
||||||
|
/* Auto-vectorized */
|
||||||
|
for (int i = 0; i < dim; i++)
|
||||||
|
distance += HalfToFloat4(ax[i]) * HalfToFloat4(bx[i]);
|
||||||
|
|
||||||
|
PG_RETURN_FLOAT8((double) distance * -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the cosine distance between two half arrays
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_cosine_distance);
|
||||||
|
Datum
|
||||||
|
half_cosine_distance(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
ArrayType *a = PG_GETARG_ARRAYTYPE_P(0);
|
||||||
|
ArrayType *b = PG_GETARG_ARRAYTYPE_P(1);
|
||||||
|
half *ax = (half *) ARR_DATA_PTR(a);
|
||||||
|
half *bx = (half *) ARR_DATA_PTR(b);
|
||||||
|
float distance = 0.0;
|
||||||
|
float norma = 0.0;
|
||||||
|
float normb = 0.0;
|
||||||
|
double similarity;
|
||||||
|
int dim = CheckDims(a, b);
|
||||||
|
|
||||||
|
/* Auto-vectorized */
|
||||||
|
for (int i = 0; i < dim; i++)
|
||||||
|
{
|
||||||
|
float axi = HalfToFloat4(ax[i]);
|
||||||
|
float bxi = HalfToFloat4(bx[i]);
|
||||||
|
|
||||||
|
distance += axi * bxi;
|
||||||
|
norma += axi * axi;
|
||||||
|
normb += bxi * bxi;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Use sqrt(a * b) over sqrt(a) * sqrt(b) */
|
||||||
|
similarity = (double) distance / sqrt((double) norma * (double) normb);
|
||||||
|
|
||||||
|
#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;
|
||||||
|
else if (similarity < -1)
|
||||||
|
similarity = -1;
|
||||||
|
|
||||||
|
PG_RETURN_FLOAT8(1 - similarity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the L1 distance between two half arrays
|
||||||
|
*/
|
||||||
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(half_l1_distance);
|
||||||
|
Datum
|
||||||
|
half_l1_distance(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
ArrayType *a = PG_GETARG_ARRAYTYPE_P(0);
|
||||||
|
ArrayType *b = PG_GETARG_ARRAYTYPE_P(1);
|
||||||
|
half *ax = (half *) ARR_DATA_PTR(a);
|
||||||
|
half *bx = (half *) ARR_DATA_PTR(b);
|
||||||
|
float distance = 0.0;
|
||||||
|
int dim = CheckDims(a, b);
|
||||||
|
|
||||||
|
/* Auto-vectorized */
|
||||||
|
for (int i = 0; i < dim; i++)
|
||||||
|
distance += fabsf(HalfToFloat4(ax[i]) - HalfToFloat4(bx[i]));
|
||||||
|
|
||||||
|
PG_RETURN_FLOAT8((double) distance);
|
||||||
|
}
|
||||||
23
src/half.h
Normal file
23
src/half.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef HALF_H
|
||||||
|
#define HALF_H
|
||||||
|
|
||||||
|
#define __STDC_WANT_IEC_60559_TYPES_EXT__
|
||||||
|
|
||||||
|
#include <float.h>
|
||||||
|
|
||||||
|
#ifdef __FLT16_MAX__
|
||||||
|
#define FLT16_SUPPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef FLT16_SUPPORT
|
||||||
|
#define half _Float16
|
||||||
|
#define HALF_MAX FLT16_MAX
|
||||||
|
#else
|
||||||
|
#define half uint16
|
||||||
|
#define HALF_MAX 65504
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define PG_GETARG_HALF(n) DatumGetHalf(PG_GETARG_DATUM(n))
|
||||||
|
#define PG_RETURN_HALF(x) return HalfGetDatum(x)
|
||||||
|
|
||||||
|
#endif
|
||||||
37
src/hnsw.c
37
src/hnsw.c
@@ -14,45 +14,15 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
int hnsw_ef_search;
|
int hnsw_ef_search;
|
||||||
int hnsw_lock_tranche_id;
|
bool hnsw_enable_parallel_build;
|
||||||
static relopt_kind hnsw_relopt_kind;
|
static relopt_kind hnsw_relopt_kind;
|
||||||
|
|
||||||
/*
|
|
||||||
* Assign a tranche ID for our LWLocks. This only needs to be done by one
|
|
||||||
* backend, as the tranche ID is remembered in shared memory.
|
|
||||||
*
|
|
||||||
* This shared memory area is very small, so we just allocate it from the
|
|
||||||
* "slop" that PostgreSQL reserves for small allocations like this. If
|
|
||||||
* this grows bigger, we should use a shmem_request_hook and
|
|
||||||
* RequestAddinShmemSpace() to pre-reserve space for this.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
HnswInitLockTranche(void)
|
|
||||||
{
|
|
||||||
int *tranche_ids;
|
|
||||||
bool found;
|
|
||||||
|
|
||||||
LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
|
|
||||||
tranche_ids = ShmemInitStruct("hnsw LWLock ids",
|
|
||||||
sizeof(int) * 1,
|
|
||||||
&found);
|
|
||||||
if (!found)
|
|
||||||
tranche_ids[0] = LWLockNewTrancheId();
|
|
||||||
hnsw_lock_tranche_id = tranche_ids[0];
|
|
||||||
LWLockRelease(AddinShmemInitLock);
|
|
||||||
|
|
||||||
/* Per-backend registration of the tranche ID */
|
|
||||||
LWLockRegisterTranche(hnsw_lock_tranche_id, "HnswBuild");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize index options and variables
|
* Initialize index options and variables
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
HnswInit(void)
|
HnswInit(void)
|
||||||
{
|
{
|
||||||
HnswInitLockTranche();
|
|
||||||
|
|
||||||
hnsw_relopt_kind = add_reloption_kind();
|
hnsw_relopt_kind = add_reloption_kind();
|
||||||
add_int_reloption(hnsw_relopt_kind, "m", "Max number of connections",
|
add_int_reloption(hnsw_relopt_kind, "m", "Max number of connections",
|
||||||
HNSW_DEFAULT_M, HNSW_MIN_M, HNSW_MAX_M
|
HNSW_DEFAULT_M, HNSW_MIN_M, HNSW_MAX_M
|
||||||
@@ -70,6 +40,11 @@ HnswInit(void)
|
|||||||
DefineCustomIntVariable("hnsw.ef_search", "Sets the size of the dynamic candidate list for search",
|
DefineCustomIntVariable("hnsw.ef_search", "Sets the size of the dynamic candidate list for search",
|
||||||
"Valid range is 1..1000.", &hnsw_ef_search,
|
"Valid range is 1..1000.", &hnsw_ef_search,
|
||||||
HNSW_DEFAULT_EF_SEARCH, HNSW_MIN_EF_SEARCH, HNSW_MAX_EF_SEARCH, PGC_USERSET, 0, NULL, NULL, NULL);
|
HNSW_DEFAULT_EF_SEARCH, HNSW_MIN_EF_SEARCH, HNSW_MAX_EF_SEARCH, PGC_USERSET, 0, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
/* Behind a variable for now since can be slower than building in memory */
|
||||||
|
DefineCustomBoolVariable("hnsw.enable_parallel_build", "Enables or disables building indexes in parallel",
|
||||||
|
NULL, &hnsw_enable_parallel_build,
|
||||||
|
false, PGC_USERSET, 0, NULL, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
170
src/hnsw.h
170
src/hnsw.h
@@ -8,7 +8,6 @@
|
|||||||
#include "access/reloptions.h"
|
#include "access/reloptions.h"
|
||||||
#include "nodes/execnodes.h"
|
#include "nodes/execnodes.h"
|
||||||
#include "port.h" /* for random() */
|
#include "port.h" /* for random() */
|
||||||
#include "utils/relptr.h"
|
|
||||||
#include "utils/sampling.h"
|
#include "utils/sampling.h"
|
||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
|
|
||||||
@@ -73,10 +72,8 @@
|
|||||||
|
|
||||||
#if PG_VERSION_NUM >= 150000
|
#if PG_VERSION_NUM >= 150000
|
||||||
#define RandomDouble() pg_prng_double(&pg_global_prng_state)
|
#define RandomDouble() pg_prng_double(&pg_global_prng_state)
|
||||||
#define SeedRandom(seed) pg_prng_seed(&pg_global_prng_state, seed)
|
|
||||||
#else
|
#else
|
||||||
#define RandomDouble() (((double) random()) / MAX_RANDOM_VALUE)
|
#define RandomDouble() (((double) random()) / MAX_RANDOM_VALUE)
|
||||||
#define SeedRandom(seed) srandom(seed)
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 130000
|
#if PG_VERSION_NUM < 130000
|
||||||
@@ -94,66 +91,32 @@
|
|||||||
#define HnswGetMl(m) (1 / log(m))
|
#define HnswGetMl(m) (1 / log(m))
|
||||||
|
|
||||||
/* Ensure fits on page and in uint8 */
|
/* Ensure fits on page and in uint8 */
|
||||||
#define HnswGetMaxLevel(m) Min(((BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData)) - offsetof(HnswNeighborTupleData, indextids) - sizeof(ItemIdData)) / (sizeof(ItemPointerData)) / (m)) - 2, 255)
|
#define HnswGetMaxLevel(m) Min(((BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(HnswPageOpaqueData)) - offsetof(HnswNeighborTupleData, indextids) - sizeof(ItemIdData)) / (sizeof(ItemPointerData)) / m) - 2, 255)
|
||||||
|
|
||||||
#define HnswGetValue(base, element) PointerGetDatum(HnswPtrAccess(base, (element)->value))
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 140005
|
|
||||||
#define relptr_offset(rp) ((rp).relptr_off - 1)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Pointer macros */
|
|
||||||
#define HnswPtrAccess(base, hp) ((base) == NULL ? (hp).ptr : relptr_access(base, (hp).relptr))
|
|
||||||
#define HnswPtrStore(base, hp, value) ((base) == NULL ? (void) ((hp).ptr = (value)) : (void) relptr_store(base, (hp).relptr, value))
|
|
||||||
#define HnswPtrIsNull(base, hp) ((base) == NULL ? (hp).ptr == NULL : relptr_is_null((hp).relptr))
|
|
||||||
#define HnswPtrEqual(base, hp1, hp2) ((base) == NULL ? (hp1).ptr == (hp2).ptr : relptr_offset((hp1).relptr) == relptr_offset((hp2).relptr))
|
|
||||||
|
|
||||||
/* For code paths dedicated to each type */
|
|
||||||
#define HnswPtrPointer(hp) (hp).ptr
|
|
||||||
#define HnswPtrOffset(hp) relptr_offset((hp).relptr)
|
|
||||||
|
|
||||||
/* Variables */
|
/* Variables */
|
||||||
extern int hnsw_ef_search;
|
extern int hnsw_ef_search;
|
||||||
|
extern bool hnsw_enable_parallel_build;
|
||||||
|
|
||||||
/* This is initialized when the module is loaded */
|
|
||||||
extern int hnsw_lock_tranche_id;
|
|
||||||
|
|
||||||
typedef struct HnswElementData HnswElementData;
|
|
||||||
typedef struct HnswNeighborArray HnswNeighborArray;
|
typedef struct HnswNeighborArray HnswNeighborArray;
|
||||||
|
|
||||||
#define HnswPtrDeclare(type, relptrtype, ptrtype) \
|
|
||||||
relptr_declare(type, relptrtype); \
|
|
||||||
typedef union { type *ptr; relptrtype relptr; } ptrtype;
|
|
||||||
|
|
||||||
/* Pointers that can be absolute or relative */
|
|
||||||
/* Use char for DatumPtr so works with Pointer */
|
|
||||||
HnswPtrDeclare(HnswElementData, HnswElementRelptr, HnswElementPtr);
|
|
||||||
HnswPtrDeclare(HnswNeighborArray, HnswNeighborArrayRelptr, HnswNeighborArrayPtr);
|
|
||||||
HnswPtrDeclare(HnswNeighborArrayPtr, HnswNeighborsRelptr, HnswNeighborsPtr);
|
|
||||||
HnswPtrDeclare(char, DatumRelptr, DatumPtr);
|
|
||||||
|
|
||||||
typedef struct HnswElementData
|
typedef struct HnswElementData
|
||||||
{
|
{
|
||||||
HnswElementPtr next;
|
List *heaptids;
|
||||||
ItemPointerData heaptids[HNSW_HEAPTIDS];
|
|
||||||
uint8 heaptidsLength;
|
|
||||||
uint8 level;
|
uint8 level;
|
||||||
uint8 deleted;
|
uint8 deleted;
|
||||||
uint32 hash;
|
HnswNeighborArray *neighbors;
|
||||||
HnswNeighborsPtr neighbors;
|
|
||||||
BlockNumber blkno;
|
BlockNumber blkno;
|
||||||
OffsetNumber offno;
|
OffsetNumber offno;
|
||||||
OffsetNumber neighborOffno;
|
OffsetNumber neighborOffno;
|
||||||
BlockNumber neighborPage;
|
BlockNumber neighborPage;
|
||||||
DatumPtr value;
|
Datum value;
|
||||||
LWLock lock;
|
|
||||||
} HnswElementData;
|
} HnswElementData;
|
||||||
|
|
||||||
typedef HnswElementData * HnswElement;
|
typedef HnswElementData * HnswElement;
|
||||||
|
|
||||||
typedef struct HnswCandidate
|
typedef struct HnswCandidate
|
||||||
{
|
{
|
||||||
HnswElementPtr element;
|
HnswElement element;
|
||||||
float distance;
|
float distance;
|
||||||
bool closer;
|
bool closer;
|
||||||
} HnswCandidate;
|
} HnswCandidate;
|
||||||
@@ -162,7 +125,7 @@ typedef struct HnswNeighborArray
|
|||||||
{
|
{
|
||||||
int length;
|
int length;
|
||||||
bool closerSet;
|
bool closerSet;
|
||||||
HnswCandidate items[FLEXIBLE_ARRAY_MEMBER];
|
HnswCandidate *items;
|
||||||
} HnswNeighborArray;
|
} HnswNeighborArray;
|
||||||
|
|
||||||
typedef struct HnswPairingHeapNode
|
typedef struct HnswPairingHeapNode
|
||||||
@@ -179,27 +142,6 @@ typedef struct HnswOptions
|
|||||||
int efConstruction; /* size of dynamic candidate list */
|
int efConstruction; /* size of dynamic candidate list */
|
||||||
} HnswOptions;
|
} HnswOptions;
|
||||||
|
|
||||||
typedef struct HnswGraph
|
|
||||||
{
|
|
||||||
/* Graph state */
|
|
||||||
slock_t lock;
|
|
||||||
HnswElementPtr head;
|
|
||||||
double indtuples;
|
|
||||||
|
|
||||||
/* Entry state */
|
|
||||||
LWLock entryLock;
|
|
||||||
HnswElementPtr entryPoint;
|
|
||||||
|
|
||||||
/* Allocations state */
|
|
||||||
LWLock allocatorLock;
|
|
||||||
long memoryUsed;
|
|
||||||
long memoryTotal;
|
|
||||||
|
|
||||||
/* Flushed state */
|
|
||||||
LWLock flushLock;
|
|
||||||
bool flushed;
|
|
||||||
} HnswGraph;
|
|
||||||
|
|
||||||
typedef struct HnswSpool
|
typedef struct HnswSpool
|
||||||
{
|
{
|
||||||
Relation heap;
|
Relation heap;
|
||||||
@@ -213,7 +155,6 @@ typedef struct HnswShared
|
|||||||
Oid indexrelid;
|
Oid indexrelid;
|
||||||
bool isconcurrent;
|
bool isconcurrent;
|
||||||
int scantuplesortstates;
|
int scantuplesortstates;
|
||||||
dsa_pointer dsaptr;
|
|
||||||
|
|
||||||
/* Worker progress */
|
/* Worker progress */
|
||||||
ConditionVariable workersdonecv;
|
ConditionVariable workersdonecv;
|
||||||
@@ -224,7 +165,7 @@ typedef struct HnswShared
|
|||||||
/* Mutable state */
|
/* Mutable state */
|
||||||
int nparticipantsdone;
|
int nparticipantsdone;
|
||||||
double reltuples;
|
double reltuples;
|
||||||
HnswGraph graphData;
|
double indtuples;
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 120000
|
#if PG_VERSION_NUM < 120000
|
||||||
ParallelHeapScanDescData heapdesc; /* must come last */
|
ParallelHeapScanDescData heapdesc; /* must come last */
|
||||||
@@ -242,15 +183,8 @@ typedef struct HnswLeader
|
|||||||
int nparticipanttuplesorts;
|
int nparticipanttuplesorts;
|
||||||
HnswShared *hnswshared;
|
HnswShared *hnswshared;
|
||||||
Snapshot snapshot;
|
Snapshot snapshot;
|
||||||
char *hnswarea;
|
|
||||||
} HnswLeader;
|
} HnswLeader;
|
||||||
|
|
||||||
typedef struct HnswAllocator
|
|
||||||
{
|
|
||||||
void *(*alloc) (Size size, void *state);
|
|
||||||
void *state;
|
|
||||||
} HnswAllocator;
|
|
||||||
|
|
||||||
typedef struct HnswBuildState
|
typedef struct HnswBuildState
|
||||||
{
|
{
|
||||||
/* Info */
|
/* Info */
|
||||||
@@ -274,21 +208,20 @@ typedef struct HnswBuildState
|
|||||||
Oid collation;
|
Oid collation;
|
||||||
|
|
||||||
/* Variables */
|
/* Variables */
|
||||||
HnswGraph graphData;
|
List *elements;
|
||||||
HnswGraph *graph;
|
HnswElement entryPoint;
|
||||||
double ml;
|
double ml;
|
||||||
int maxLevel;
|
int maxLevel;
|
||||||
|
long memoryLeft;
|
||||||
|
bool flushed;
|
||||||
Vector *normvec;
|
Vector *normvec;
|
||||||
|
|
||||||
/* Memory */
|
/* Memory */
|
||||||
MemoryContext graphCtx;
|
|
||||||
MemoryContext tmpCtx;
|
MemoryContext tmpCtx;
|
||||||
HnswAllocator allocator;
|
|
||||||
|
|
||||||
/* Parallel builds */
|
/* Parallel builds */
|
||||||
HnswLeader *hnswleader;
|
HnswLeader *hnswleader;
|
||||||
HnswShared *hnswshared;
|
HnswShared *hnswshared;
|
||||||
char *hnswarea;
|
|
||||||
} HnswBuildState;
|
} HnswBuildState;
|
||||||
|
|
||||||
typedef struct HnswMetaPageData
|
typedef struct HnswMetaPageData
|
||||||
@@ -370,7 +303,7 @@ typedef struct HnswVacuumState
|
|||||||
Oid collation;
|
Oid collation;
|
||||||
|
|
||||||
/* Variables */
|
/* Variables */
|
||||||
struct tidhash_hash *deleted;
|
HTAB *deleted;
|
||||||
BufferAccessStrategy bas;
|
BufferAccessStrategy bas;
|
||||||
HnswNeighborTuple ntup;
|
HnswNeighborTuple ntup;
|
||||||
HnswElementData highestPoint;
|
HnswElementData highestPoint;
|
||||||
@@ -384,27 +317,30 @@ int HnswGetM(Relation index);
|
|||||||
int HnswGetEfConstruction(Relation index);
|
int HnswGetEfConstruction(Relation index);
|
||||||
FmgrInfo *HnswOptionalProcInfo(Relation index, uint16 procnum);
|
FmgrInfo *HnswOptionalProcInfo(Relation index, uint16 procnum);
|
||||||
bool HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
|
bool HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
|
||||||
|
void HnswCommitBuffer(Buffer buf, GenericXLogState *state);
|
||||||
Buffer HnswNewBuffer(Relation index, ForkNumber forkNum);
|
Buffer HnswNewBuffer(Relation index, ForkNumber forkNum);
|
||||||
void HnswInitPage(Buffer buf, Page page);
|
void HnswInitPage(Buffer buf, Page page);
|
||||||
|
void HnswInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state);
|
||||||
void HnswInit(void);
|
void HnswInit(void);
|
||||||
List *HnswSearchLayer(char *base, Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, int m, bool inserting, HnswElement skipElement);
|
List *HnswSearchLayer(Datum q, List *ep, int ef, int lc, Relation index, FmgrInfo *procinfo, Oid collation, int m, bool inserting, HnswElement skipElement);
|
||||||
HnswElement HnswGetEntryPoint(Relation index);
|
HnswElement HnswGetEntryPoint(Relation index);
|
||||||
void HnswGetMetaPageInfo(Relation index, int *m, HnswElement * entryPoint);
|
void HnswGetMetaPageInfo(Relation index, int *m, HnswElement * entryPoint);
|
||||||
void *HnswAlloc(HnswAllocator * allocator, Size size);
|
HnswElement HnswInitElement(ItemPointer tid, int m, double ml, int maxLevel);
|
||||||
HnswElement HnswInitElement(char *base, ItemPointer tid, int m, double ml, int maxLevel, HnswAllocator * alloc);
|
void HnswFreeElement(HnswElement element);
|
||||||
HnswElement HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno);
|
HnswElement HnswInitElementFromBlock(BlockNumber blkno, OffsetNumber offno);
|
||||||
void HnswInsertElement(char *base, HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing);
|
void HnswInsertElement(HnswElement element, HnswElement entryPoint, Relation index, FmgrInfo *procinfo, Oid collation, int m, int efConstruction, bool existing);
|
||||||
HnswCandidate *HnswEntryCandidate(char *base, HnswElement em, Datum q, Relation rel, FmgrInfo *procinfo, Oid collation, bool loadVec);
|
HnswElement HnswFindDuplicate(HnswElement e);
|
||||||
void HnswUpdateMetaPage(Relation index, int updateEntry, HnswElement entryPoint, BlockNumber insertPage, ForkNumber forkNum, bool building);
|
HnswCandidate *HnswEntryCandidate(HnswElement em, Datum q, Relation rel, FmgrInfo *procinfo, Oid collation, bool loadVec);
|
||||||
void HnswSetNeighborTuple(char *base, HnswNeighborTuple ntup, HnswElement e, int m);
|
void HnswUpdateMetaPage(Relation index, int updateEntry, HnswElement entryPoint, BlockNumber insertPage, ForkNumber forkNum);
|
||||||
|
void HnswSetNeighborTuple(HnswNeighborTuple ntup, HnswElement e, int m);
|
||||||
void HnswAddHeapTid(HnswElement element, ItemPointer heaptid);
|
void HnswAddHeapTid(HnswElement element, ItemPointer heaptid);
|
||||||
void HnswInitNeighbors(char *base, HnswElement element, int m, HnswAllocator * alloc);
|
void HnswInitNeighbors(HnswElement element, int m);
|
||||||
bool HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel, bool building);
|
bool HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel);
|
||||||
void HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting, bool building);
|
void HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting);
|
||||||
void HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool loadHeaptids, bool loadVec);
|
void HnswLoadElementFromTuple(HnswElement element, HnswElementTuple etup, bool loadHeaptids, bool loadVec);
|
||||||
void HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec);
|
void HnswLoadElement(HnswElement element, float *distance, Datum *q, Relation index, FmgrInfo *procinfo, Oid collation, bool loadVec);
|
||||||
void HnswSetElementTuple(char *base, HnswElementTuple etup, HnswElement element);
|
void HnswSetElementTuple(HnswElementTuple etup, HnswElement element);
|
||||||
void HnswUpdateConnection(char *base, HnswElement element, HnswCandidate * hc, int m, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation);
|
void HnswUpdateConnection(HnswElement element, HnswCandidate * hc, int m, int lc, int *updateIdx, Relation index, FmgrInfo *procinfo, Oid collation);
|
||||||
void HnswLoadNeighbors(HnswElement element, Relation index, int m);
|
void HnswLoadNeighbors(HnswElement element, Relation index, int m);
|
||||||
PGDLLEXPORT void HnswParallelBuildMain(dsm_segment *seg, shm_toc *toc);
|
PGDLLEXPORT void HnswParallelBuildMain(dsm_segment *seg, shm_toc *toc);
|
||||||
|
|
||||||
@@ -424,54 +360,4 @@ void hnswrescan(IndexScanDesc scan, ScanKey keys, int nkeys, ScanKey orderbys,
|
|||||||
bool hnswgettuple(IndexScanDesc scan, ScanDirection dir);
|
bool hnswgettuple(IndexScanDesc scan, ScanDirection dir);
|
||||||
void hnswendscan(IndexScanDesc scan);
|
void hnswendscan(IndexScanDesc scan);
|
||||||
|
|
||||||
static inline HnswNeighborArray *
|
|
||||||
HnswGetNeighbors(char *base, HnswElement element, int lc)
|
|
||||||
{
|
|
||||||
HnswNeighborArrayPtr *neighborList = HnswPtrAccess(base, element->neighbors);
|
|
||||||
|
|
||||||
Assert(element->level >= lc);
|
|
||||||
|
|
||||||
return HnswPtrAccess(base, neighborList[lc]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hash tables */
|
|
||||||
typedef struct TidHashEntry
|
|
||||||
{
|
|
||||||
ItemPointerData tid;
|
|
||||||
char status;
|
|
||||||
} TidHashEntry;
|
|
||||||
|
|
||||||
#define SH_PREFIX tidhash
|
|
||||||
#define SH_ELEMENT_TYPE TidHashEntry
|
|
||||||
#define SH_KEY_TYPE ItemPointerData
|
|
||||||
#define SH_SCOPE extern
|
|
||||||
#define SH_DECLARE
|
|
||||||
#include "lib/simplehash.h"
|
|
||||||
|
|
||||||
typedef struct PointerHashEntry
|
|
||||||
{
|
|
||||||
uintptr_t ptr;
|
|
||||||
char status;
|
|
||||||
} PointerHashEntry;
|
|
||||||
|
|
||||||
#define SH_PREFIX pointerhash
|
|
||||||
#define SH_ELEMENT_TYPE PointerHashEntry
|
|
||||||
#define SH_KEY_TYPE uintptr_t
|
|
||||||
#define SH_SCOPE extern
|
|
||||||
#define SH_DECLARE
|
|
||||||
#include "lib/simplehash.h"
|
|
||||||
|
|
||||||
typedef struct OffsetHashEntry
|
|
||||||
{
|
|
||||||
Size offset;
|
|
||||||
char status;
|
|
||||||
} OffsetHashEntry;
|
|
||||||
|
|
||||||
#define SH_PREFIX offsethash
|
|
||||||
#define SH_ELEMENT_TYPE OffsetHashEntry
|
|
||||||
#define SH_KEY_TYPE Size
|
|
||||||
#define SH_SCOPE extern
|
|
||||||
#define SH_DECLARE
|
|
||||||
#include "lib/simplehash.h"
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
608
src/hnswbuild.c
608
src/hnswbuild.c
@@ -54,12 +54,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define PARALLEL_KEY_HNSW_SHARED UINT64CONST(0xA000000000000001)
|
#define PARALLEL_KEY_HNSW_SHARED UINT64CONST(0xA000000000000001)
|
||||||
#define PARALLEL_KEY_HNSW_AREA UINT64CONST(0xA000000000000002)
|
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xA000000000000002)
|
||||||
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xA000000000000003)
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 130000
|
|
||||||
#define GENERATIONCHUNK_RAWSIZE (SIZEOF_SIZE_T + SIZEOF_VOID_P * 2)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create the metapage
|
* Create the metapage
|
||||||
@@ -71,11 +66,11 @@ CreateMetaPage(HnswBuildState * buildstate)
|
|||||||
ForkNumber forkNum = buildstate->forkNum;
|
ForkNumber forkNum = buildstate->forkNum;
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
|
GenericXLogState *state;
|
||||||
HnswMetaPage metap;
|
HnswMetaPage metap;
|
||||||
|
|
||||||
buf = HnswNewBuffer(index, forkNum);
|
buf = HnswNewBuffer(index, forkNum);
|
||||||
page = BufferGetPage(buf);
|
HnswInitRegisterPage(index, &buf, &page, &state);
|
||||||
HnswInitPage(buf, page);
|
|
||||||
|
|
||||||
/* Set metapage data */
|
/* Set metapage data */
|
||||||
metap = HnswPageGetMeta(page);
|
metap = HnswPageGetMeta(page);
|
||||||
@@ -91,15 +86,14 @@ CreateMetaPage(HnswBuildState * buildstate)
|
|||||||
((PageHeader) page)->pd_lower =
|
((PageHeader) page)->pd_lower =
|
||||||
((char *) metap + sizeof(HnswMetaPageData)) - (char *) page;
|
((char *) metap + sizeof(HnswMetaPageData)) - (char *) page;
|
||||||
|
|
||||||
MarkBufferDirty(buf);
|
HnswCommitBuffer(buf, state);
|
||||||
UnlockReleaseBuffer(buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add a new page
|
* Add a new page
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
HnswBuildAppendPage(Relation index, Buffer *buf, Page *page, ForkNumber forkNum)
|
HnswBuildAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum)
|
||||||
{
|
{
|
||||||
/* Add a new page */
|
/* Add a new page */
|
||||||
Buffer newbuf = HnswNewBuffer(index, forkNum);
|
Buffer newbuf = HnswNewBuffer(index, forkNum);
|
||||||
@@ -108,7 +102,7 @@ HnswBuildAppendPage(Relation index, Buffer *buf, Page *page, ForkNumber forkNum)
|
|||||||
HnswPageGetOpaque(*page)->nextblkno = BufferGetBlockNumber(newbuf);
|
HnswPageGetOpaque(*page)->nextblkno = BufferGetBlockNumber(newbuf);
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
MarkBufferDirty(*buf);
|
GenericXLogFinish(*state);
|
||||||
UnlockReleaseBuffer(*buf);
|
UnlockReleaseBuffer(*buf);
|
||||||
|
|
||||||
/* Can take a while, so ensure we can interrupt */
|
/* Can take a while, so ensure we can interrupt */
|
||||||
@@ -119,7 +113,8 @@ HnswBuildAppendPage(Relation index, Buffer *buf, Page *page, ForkNumber forkNum)
|
|||||||
|
|
||||||
/* Prepare new page */
|
/* Prepare new page */
|
||||||
*buf = newbuf;
|
*buf = newbuf;
|
||||||
*page = BufferGetPage(*buf);
|
*state = GenericXLogStart(index);
|
||||||
|
*page = GenericXLogRegisterBuffer(*state, *buf, GENERIC_XLOG_FULL_IMAGE);
|
||||||
HnswInitPage(*buf, *page);
|
HnswInitPage(*buf, *page);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,11 +131,10 @@ CreateElementPages(HnswBuildState * buildstate)
|
|||||||
HnswElementTuple etup;
|
HnswElementTuple etup;
|
||||||
HnswNeighborTuple ntup;
|
HnswNeighborTuple ntup;
|
||||||
BlockNumber insertPage;
|
BlockNumber insertPage;
|
||||||
HnswElement entryPoint;
|
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
HnswElementPtr iter = buildstate->graph->head;
|
GenericXLogState *state;
|
||||||
char *base = buildstate->hnswarea;
|
ListCell *lc;
|
||||||
|
|
||||||
/* Calculate sizes */
|
/* Calculate sizes */
|
||||||
etupAllocSize = BLCKSZ;
|
etupAllocSize = BLCKSZ;
|
||||||
@@ -152,25 +146,22 @@ CreateElementPages(HnswBuildState * buildstate)
|
|||||||
|
|
||||||
/* Prepare first page */
|
/* Prepare first page */
|
||||||
buf = HnswNewBuffer(index, forkNum);
|
buf = HnswNewBuffer(index, forkNum);
|
||||||
page = BufferGetPage(buf);
|
state = GenericXLogStart(index);
|
||||||
|
page = GenericXLogRegisterBuffer(state, buf, GENERIC_XLOG_FULL_IMAGE);
|
||||||
HnswInitPage(buf, page);
|
HnswInitPage(buf, page);
|
||||||
|
|
||||||
while (!HnswPtrIsNull(base, iter))
|
foreach(lc, buildstate->elements)
|
||||||
{
|
{
|
||||||
HnswElement element = HnswPtrAccess(base, iter);
|
HnswElement element = lfirst(lc);
|
||||||
Size etupSize;
|
Size etupSize;
|
||||||
Size ntupSize;
|
Size ntupSize;
|
||||||
Size combinedSize;
|
Size combinedSize;
|
||||||
void *valuePtr = HnswPtrAccess(base, element->value);
|
|
||||||
|
|
||||||
/* Update iterator */
|
|
||||||
iter = element->next;
|
|
||||||
|
|
||||||
/* Zero memory for each element */
|
/* Zero memory for each element */
|
||||||
MemSet(etup, 0, etupAllocSize);
|
MemSet(etup, 0, etupAllocSize);
|
||||||
|
|
||||||
/* Calculate sizes */
|
/* Calculate sizes */
|
||||||
etupSize = HNSW_ELEMENT_TUPLE_SIZE(VARSIZE_ANY(valuePtr));
|
etupSize = HNSW_ELEMENT_TUPLE_SIZE(VARSIZE_ANY(DatumGetPointer(element->value)));
|
||||||
ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, buildstate->m);
|
ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(element->level, buildstate->m);
|
||||||
combinedSize = etupSize + ntupSize + sizeof(ItemIdData);
|
combinedSize = etupSize + ntupSize + sizeof(ItemIdData);
|
||||||
|
|
||||||
@@ -178,11 +169,11 @@ CreateElementPages(HnswBuildState * buildstate)
|
|||||||
if (etupSize > etupAllocSize)
|
if (etupSize > etupAllocSize)
|
||||||
elog(ERROR, "index tuple too large");
|
elog(ERROR, "index tuple too large");
|
||||||
|
|
||||||
HnswSetElementTuple(base, etup, element);
|
HnswSetElementTuple(etup, element);
|
||||||
|
|
||||||
/* Keep element and neighbors on the same page if possible */
|
/* Keep element and neighbors on the same page if possible */
|
||||||
if (PageGetFreeSpace(page) < etupSize || (combinedSize <= maxSize && PageGetFreeSpace(page) < combinedSize))
|
if (PageGetFreeSpace(page) < etupSize || (combinedSize <= maxSize && PageGetFreeSpace(page) < combinedSize))
|
||||||
HnswBuildAppendPage(index, &buf, &page, forkNum);
|
HnswBuildAppendPage(index, &buf, &page, &state, forkNum);
|
||||||
|
|
||||||
/* Calculate offsets */
|
/* Calculate offsets */
|
||||||
element->blkno = BufferGetBlockNumber(buf);
|
element->blkno = BufferGetBlockNumber(buf);
|
||||||
@@ -206,7 +197,7 @@ CreateElementPages(HnswBuildState * buildstate)
|
|||||||
|
|
||||||
/* Add new page if needed */
|
/* Add new page if needed */
|
||||||
if (PageGetFreeSpace(page) < ntupSize)
|
if (PageGetFreeSpace(page) < ntupSize)
|
||||||
HnswBuildAppendPage(index, &buf, &page, forkNum);
|
HnswBuildAppendPage(index, &buf, &page, &state, forkNum);
|
||||||
|
|
||||||
/* Add placeholder for neighbors */
|
/* Add placeholder for neighbors */
|
||||||
if (PageAddItem(page, (Item) ntup, ntupSize, InvalidOffsetNumber, false, false) != element->neighborOffno)
|
if (PageAddItem(page, (Item) ntup, ntupSize, InvalidOffsetNumber, false, false) != element->neighborOffno)
|
||||||
@@ -216,11 +207,10 @@ CreateElementPages(HnswBuildState * buildstate)
|
|||||||
insertPage = BufferGetBlockNumber(buf);
|
insertPage = BufferGetBlockNumber(buf);
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
MarkBufferDirty(buf);
|
GenericXLogFinish(state);
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
|
|
||||||
entryPoint = HnswPtrAccess(base, buildstate->graph->entryPoint);
|
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_ALWAYS, buildstate->entryPoint, insertPage, forkNum);
|
||||||
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_ALWAYS, entryPoint, insertPage, forkNum, true);
|
|
||||||
|
|
||||||
pfree(etup);
|
pfree(etup);
|
||||||
pfree(ntup);
|
pfree(ntup);
|
||||||
@@ -235,191 +225,82 @@ CreateNeighborPages(HnswBuildState * buildstate)
|
|||||||
Relation index = buildstate->index;
|
Relation index = buildstate->index;
|
||||||
ForkNumber forkNum = buildstate->forkNum;
|
ForkNumber forkNum = buildstate->forkNum;
|
||||||
int m = buildstate->m;
|
int m = buildstate->m;
|
||||||
HnswElementPtr iter = buildstate->graph->head;
|
ListCell *lc;
|
||||||
char *base = buildstate->hnswarea;
|
|
||||||
HnswNeighborTuple ntup;
|
HnswNeighborTuple ntup;
|
||||||
|
|
||||||
/* Allocate once */
|
/* Allocate once */
|
||||||
ntup = palloc0(BLCKSZ);
|
ntup = palloc0(BLCKSZ);
|
||||||
|
|
||||||
while (!HnswPtrIsNull(base, iter))
|
foreach(lc, buildstate->elements)
|
||||||
{
|
{
|
||||||
HnswElement e = HnswPtrAccess(base, iter);
|
HnswElement e = lfirst(lc);
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
|
GenericXLogState *state;
|
||||||
Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(e->level, m);
|
Size ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(e->level, m);
|
||||||
|
|
||||||
/* Update iterator */
|
|
||||||
iter = e->next;
|
|
||||||
|
|
||||||
/* Can take a while, so ensure we can interrupt */
|
/* Can take a while, so ensure we can interrupt */
|
||||||
/* Needs to be called when no buffer locks are held */
|
/* Needs to be called when no buffer locks are held */
|
||||||
CHECK_FOR_INTERRUPTS();
|
CHECK_FOR_INTERRUPTS();
|
||||||
|
|
||||||
buf = ReadBufferExtended(index, forkNum, e->neighborPage, RBM_NORMAL, NULL);
|
buf = ReadBufferExtended(index, forkNum, e->neighborPage, RBM_NORMAL, NULL);
|
||||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||||
page = BufferGetPage(buf);
|
state = GenericXLogStart(index);
|
||||||
|
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||||
|
|
||||||
HnswSetNeighborTuple(base, ntup, e, m);
|
HnswSetNeighborTuple(ntup, e, m);
|
||||||
|
|
||||||
if (!PageIndexTupleOverwrite(page, e->neighborOffno, (Item) ntup, ntupSize))
|
if (!PageIndexTupleOverwrite(page, e->neighborOffno, (Item) ntup, ntupSize))
|
||||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
MarkBufferDirty(buf);
|
GenericXLogFinish(state);
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
pfree(ntup);
|
pfree(ntup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free elements
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
FreeElements(HnswBuildState * buildstate)
|
||||||
|
{
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
|
foreach(lc, buildstate->elements)
|
||||||
|
HnswFreeElement(lfirst(lc));
|
||||||
|
|
||||||
|
list_free(buildstate->elements);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Flush pages
|
* Flush pages
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
FlushPages(HnswBuildState * buildstate)
|
FlushPages(HnswBuildState * buildstate)
|
||||||
{
|
{
|
||||||
#ifdef HNSW_MEMORY
|
|
||||||
elog(INFO, "memory: %zu MB", buildstate->graph->memoryUsed / (1024 * 1024));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
CreateMetaPage(buildstate);
|
CreateMetaPage(buildstate);
|
||||||
CreateElementPages(buildstate);
|
CreateElementPages(buildstate);
|
||||||
CreateNeighborPages(buildstate);
|
CreateNeighborPages(buildstate);
|
||||||
|
|
||||||
buildstate->graph->flushed = true;
|
buildstate->flushed = true;
|
||||||
MemoryContextReset(buildstate->graphCtx);
|
FreeElements(buildstate);
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Add a heap TID to an existing element
|
|
||||||
*/
|
|
||||||
static bool
|
|
||||||
HnswAddDuplicateInMemory(HnswElement element, HnswElement dup)
|
|
||||||
{
|
|
||||||
LWLockAcquire(&dup->lock, LW_EXCLUSIVE);
|
|
||||||
|
|
||||||
if (dup->heaptidsLength == HNSW_HEAPTIDS)
|
|
||||||
{
|
|
||||||
LWLockRelease(&dup->lock);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
HnswAddHeapTid(dup, &element->heaptids[0]);
|
|
||||||
|
|
||||||
LWLockRelease(&dup->lock);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Find duplicate element
|
|
||||||
*/
|
|
||||||
static bool
|
|
||||||
HnswFindDuplicateInMemory(char *base, HnswElement element)
|
|
||||||
{
|
|
||||||
HnswNeighborArray *neighbors = HnswGetNeighbors(base, element, 0);
|
|
||||||
|
|
||||||
for (int i = 0; i < neighbors->length; i++)
|
|
||||||
{
|
|
||||||
HnswCandidate *neighbor = &neighbors->items[i];
|
|
||||||
HnswElement neighborElement = HnswPtrAccess(base, neighbor->element);
|
|
||||||
Datum value = HnswGetValue(base, element);
|
|
||||||
Datum neighborValue = HnswGetValue(base, neighborElement);
|
|
||||||
|
|
||||||
/* Exit early since ordered by distance */
|
|
||||||
if (!datumIsEqual(value, neighborValue, false, -1))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/* Check for space */
|
|
||||||
if (HnswAddDuplicateInMemory(element, neighborElement))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Add to element and neighbor pages
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
WriteNewElementPagesInMemory(char *base, HnswGraph * graph, HnswElement element)
|
|
||||||
{
|
|
||||||
SpinLockAcquire(&graph->lock);
|
|
||||||
element->next = graph->head;
|
|
||||||
HnswPtrStore(base, graph->head, element);
|
|
||||||
SpinLockRelease(&graph->lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Update neighbors
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
HnswUpdateNeighborPagesInMemory(char *base, FmgrInfo *procinfo, Oid collation, HnswElement e, int m)
|
|
||||||
{
|
|
||||||
for (int lc = e->level; lc >= 0; lc--)
|
|
||||||
{
|
|
||||||
int lm = HnswGetLayerM(m, lc);
|
|
||||||
HnswNeighborArray *neighbors = HnswGetNeighbors(base, e, lc);
|
|
||||||
|
|
||||||
for (int i = 0; i < neighbors->length; i++)
|
|
||||||
{
|
|
||||||
HnswCandidate *hc = &neighbors->items[i];
|
|
||||||
HnswElement neighborElement = HnswPtrAccess(base, hc->element);
|
|
||||||
|
|
||||||
/* Keep scan-build happy on Mac x86-64 */
|
|
||||||
Assert(neighborElement);
|
|
||||||
|
|
||||||
/* Use element for lock instead of hc since hc can be replaced */
|
|
||||||
LWLockAcquire(&neighborElement->lock, LW_EXCLUSIVE);
|
|
||||||
HnswUpdateConnection(base, e, hc, lm, lc, NULL, NULL, procinfo, collation);
|
|
||||||
LWLockRelease(&neighborElement->lock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Write changes in memory
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
WriteElementInMemory(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement element, int m, int efConstruction, HnswElement entryPoint, HnswBuildState * buildstate, HnswGraph * graph, bool updateEntryPoint)
|
|
||||||
{
|
|
||||||
char *base = buildstate->hnswarea;
|
|
||||||
|
|
||||||
/* Try to add to existing page */
|
|
||||||
if (HnswFindDuplicateInMemory(base, element))
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* Write element and neighbor tuples */
|
|
||||||
WriteNewElementPagesInMemory(base, graph, element);
|
|
||||||
|
|
||||||
/* Update neighbors */
|
|
||||||
HnswUpdateNeighborPagesInMemory(base, procinfo, collation, element, m);
|
|
||||||
|
|
||||||
/* Update entry point if needed (already have lock) */
|
|
||||||
if (updateEntryPoint)
|
|
||||||
HnswPtrStore(base, graph->entryPoint, element);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Insert tuple
|
* Insert tuple
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, HnswBuildState * buildstate)
|
InsertTuple(Relation index, Datum *values, HnswElement element, HnswBuildState * buildstate, HnswElement * dup, MemoryContext outerCtx)
|
||||||
{
|
{
|
||||||
FmgrInfo *procinfo = buildstate->procinfo;
|
FmgrInfo *procinfo = buildstate->procinfo;
|
||||||
Oid collation = buildstate->collation;
|
Oid collation = buildstate->collation;
|
||||||
HnswGraph *graph = buildstate->graph;
|
HnswElement entryPoint = buildstate->entryPoint;
|
||||||
HnswElement entryPoint;
|
|
||||||
int efConstruction = buildstate->efConstruction;
|
int efConstruction = buildstate->efConstruction;
|
||||||
int m = buildstate->m;
|
int m = buildstate->m;
|
||||||
HnswElement element;
|
MemoryContext oldCtx;
|
||||||
HnswAllocator *allocator = &buildstate->allocator;
|
|
||||||
Size valueSize;
|
|
||||||
Pointer valuePtr;
|
|
||||||
bool updateEntryPoint;
|
|
||||||
LWLock *flushLock = &graph->flushLock;
|
|
||||||
char *base = buildstate->hnswarea;
|
|
||||||
|
|
||||||
/* Detoast once for all calls */
|
/* Detoast once for all calls */
|
||||||
Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
Datum value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
||||||
@@ -431,82 +312,52 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, Hn
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get datum size */
|
/* Copy value to element so accessible outside of memory context */
|
||||||
valueSize = VARSIZE_ANY(DatumGetPointer(value));
|
oldCtx = MemoryContextSwitchTo(outerCtx);
|
||||||
|
element->value = datumCopy(value, false, -1);
|
||||||
/* Ensure graph not flushed when inserting */
|
MemoryContextSwitchTo(oldCtx);
|
||||||
LWLockAcquire(flushLock, LW_SHARED);
|
|
||||||
|
|
||||||
if (graph->flushed)
|
|
||||||
{
|
|
||||||
LWLockRelease(flushLock);
|
|
||||||
|
|
||||||
return HnswInsertTupleOnDisk(index, value, values, isnull, heaptid, buildstate->heap, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get lock for allocator */
|
|
||||||
LWLockAcquire(&graph->allocatorLock, LW_EXCLUSIVE);
|
|
||||||
|
|
||||||
/* Flush pages if needed */
|
|
||||||
if (graph->memoryUsed >= graph->memoryTotal)
|
|
||||||
{
|
|
||||||
LWLockRelease(&graph->allocatorLock);
|
|
||||||
|
|
||||||
LWLockRelease(flushLock);
|
|
||||||
LWLockAcquire(flushLock, LW_EXCLUSIVE);
|
|
||||||
|
|
||||||
if (!graph->flushed)
|
|
||||||
{
|
|
||||||
ereport(NOTICE,
|
|
||||||
(errmsg("hnsw graph no longer fits into maintenance_work_mem after " INT64_FORMAT " tuples", (int64) graph->indtuples),
|
|
||||||
errdetail("Building will take significantly more time."),
|
|
||||||
errhint("Increase maintenance_work_mem to speed up builds.")));
|
|
||||||
|
|
||||||
FlushPages(buildstate);
|
|
||||||
}
|
|
||||||
|
|
||||||
LWLockRelease(flushLock);
|
|
||||||
|
|
||||||
return HnswInsertTupleOnDisk(index, value, values, isnull, heaptid, buildstate->heap, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create an element */
|
|
||||||
element = HnswInitElement(base, heaptid, buildstate->m, buildstate->ml, buildstate->maxLevel, allocator);
|
|
||||||
valuePtr = HnswAlloc(allocator, valueSize);
|
|
||||||
|
|
||||||
/* Release allocator lock */
|
|
||||||
LWLockRelease(&graph->allocatorLock);
|
|
||||||
|
|
||||||
/* Copy datum */
|
|
||||||
memcpy(valuePtr, DatumGetPointer(value), valueSize);
|
|
||||||
HnswPtrStore(base, element->value, valuePtr);
|
|
||||||
|
|
||||||
/* Create element lock */
|
|
||||||
LWLockInitialize(&element->lock, hnsw_lock_tranche_id);
|
|
||||||
|
|
||||||
/* Get entry point */
|
|
||||||
LWLockAcquire(&graph->entryLock, LW_EXCLUSIVE);
|
|
||||||
entryPoint = HnswPtrAccess(base, graph->entryPoint);
|
|
||||||
updateEntryPoint = entryPoint == NULL || element->level > entryPoint->level;
|
|
||||||
|
|
||||||
/* Release lock if not updating entry point */
|
|
||||||
if (!updateEntryPoint)
|
|
||||||
LWLockRelease(&graph->entryLock);
|
|
||||||
|
|
||||||
/* Insert element in graph */
|
/* Insert element in graph */
|
||||||
HnswInsertElement(base, element, entryPoint, NULL, procinfo, collation, m, efConstruction, false);
|
HnswInsertElement(element, entryPoint, NULL, procinfo, collation, m, efConstruction, false);
|
||||||
|
|
||||||
/* Write to memory */
|
/* Look for duplicate */
|
||||||
WriteElementInMemory(index, procinfo, collation, element, m, efConstruction, entryPoint, buildstate, graph, updateEntryPoint);
|
*dup = HnswFindDuplicate(element);
|
||||||
|
|
||||||
/* Release lock if needed */
|
/* Update neighbors if needed */
|
||||||
if (updateEntryPoint)
|
if (*dup == NULL)
|
||||||
LWLockRelease(&graph->entryLock);
|
{
|
||||||
|
for (int lc = element->level; lc >= 0; lc--)
|
||||||
|
{
|
||||||
|
int lm = HnswGetLayerM(m, lc);
|
||||||
|
HnswNeighborArray *neighbors = &element->neighbors[lc];
|
||||||
|
|
||||||
/* Release flush lock */
|
for (int i = 0; i < neighbors->length; i++)
|
||||||
LWLockRelease(flushLock);
|
HnswUpdateConnection(element, &neighbors->items[i], lm, lc, NULL, NULL, procinfo, collation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
/* Update entry point if needed */
|
||||||
|
if (*dup == NULL && (entryPoint == NULL || element->level > entryPoint->level))
|
||||||
|
buildstate->entryPoint = element;
|
||||||
|
|
||||||
|
UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++buildstate->indtuples);
|
||||||
|
|
||||||
|
return *dup == NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the memory used by an element
|
||||||
|
*/
|
||||||
|
static long
|
||||||
|
HnswElementMemory(HnswElement e, int m)
|
||||||
|
{
|
||||||
|
long elementSize = sizeof(HnswElementData);
|
||||||
|
|
||||||
|
elementSize += sizeof(HnswNeighborArray) * (e->level + 1);
|
||||||
|
elementSize += sizeof(HnswCandidate) * (m * (e->level + 2));
|
||||||
|
elementSize += sizeof(ItemPointerData);
|
||||||
|
elementSize += VARSIZE_ANY(DatumGetPointer(e->value));
|
||||||
|
return elementSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -517,8 +368,10 @@ BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
|
|||||||
bool *isnull, bool tupleIsAlive, void *state)
|
bool *isnull, bool tupleIsAlive, void *state)
|
||||||
{
|
{
|
||||||
HnswBuildState *buildstate = (HnswBuildState *) state;
|
HnswBuildState *buildstate = (HnswBuildState *) state;
|
||||||
HnswGraph *graph = buildstate->graph;
|
|
||||||
MemoryContext oldCtx;
|
MemoryContext oldCtx;
|
||||||
|
HnswElement element;
|
||||||
|
HnswElement dup = NULL;
|
||||||
|
bool inserted;
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 130000
|
#if PG_VERSION_NUM < 130000
|
||||||
ItemPointer tid = &hup->t_self;
|
ItemPointer tid = &hup->t_self;
|
||||||
@@ -528,80 +381,69 @@ BuildCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
|
|||||||
if (isnull[0])
|
if (isnull[0])
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* Use memory context */
|
if (buildstate->memoryLeft <= 0)
|
||||||
|
{
|
||||||
|
if (!buildstate->flushed)
|
||||||
|
{
|
||||||
|
ereport(NOTICE,
|
||||||
|
(errmsg("hnsw graph no longer fits into maintenance_work_mem after " INT64_FORMAT " tuples", (int64) buildstate->indtuples),
|
||||||
|
errdetail("Building will take significantly more time."),
|
||||||
|
errhint("Increase maintenance_work_mem to speed up builds.")));
|
||||||
|
|
||||||
|
FlushPages(buildstate);
|
||||||
|
}
|
||||||
|
|
||||||
|
oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
|
||||||
|
|
||||||
|
if (HnswInsertTuple(buildstate->index, values, isnull, tid, buildstate->heap))
|
||||||
|
{
|
||||||
|
if (buildstate->hnswshared)
|
||||||
|
{
|
||||||
|
HnswShared *hnswshared = buildstate->hnswshared;
|
||||||
|
|
||||||
|
SpinLockAcquire(&hnswshared->mutex);
|
||||||
|
UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++hnswshared->indtuples);
|
||||||
|
SpinLockRelease(&hnswshared->mutex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++buildstate->indtuples);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset memory context */
|
||||||
|
MemoryContextSwitchTo(oldCtx);
|
||||||
|
MemoryContextReset(buildstate->tmpCtx);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate necessary memory outside of memory context */
|
||||||
|
element = HnswInitElement(tid, buildstate->m, buildstate->ml, buildstate->maxLevel);
|
||||||
|
|
||||||
|
/* Use memory context since detoast can allocate */
|
||||||
oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
|
oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
|
||||||
|
|
||||||
/* Insert tuple */
|
/* Insert tuple */
|
||||||
if (InsertTuple(index, values, isnull, tid, buildstate))
|
inserted = InsertTuple(index, values, element, buildstate, &dup, oldCtx);
|
||||||
{
|
|
||||||
/* Update progress */
|
|
||||||
SpinLockAcquire(&graph->lock);
|
|
||||||
UpdateProgress(PROGRESS_CREATEIDX_TUPLES_DONE, ++graph->indtuples);
|
|
||||||
SpinLockRelease(&graph->lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reset memory context */
|
/* Reset memory context */
|
||||||
MemoryContextSwitchTo(oldCtx);
|
MemoryContextSwitchTo(oldCtx);
|
||||||
MemoryContextReset(buildstate->tmpCtx);
|
MemoryContextReset(buildstate->tmpCtx);
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/* Add outside memory context */
|
||||||
* Initialize the graph
|
if (dup != NULL)
|
||||||
*/
|
{
|
||||||
static void
|
HnswAddHeapTid(dup, tid);
|
||||||
InitGraph(HnswGraph * graph, char *base, long memoryTotal)
|
buildstate->memoryLeft -= sizeof(ItemPointerData);
|
||||||
{
|
}
|
||||||
HnswPtrStore(base, graph->head, (HnswElement) NULL);
|
|
||||||
HnswPtrStore(base, graph->entryPoint, (HnswElement) NULL);
|
|
||||||
graph->memoryUsed = 0;
|
|
||||||
graph->memoryTotal = memoryTotal;
|
|
||||||
graph->flushed = false;
|
|
||||||
graph->indtuples = 0;
|
|
||||||
SpinLockInit(&graph->lock);
|
|
||||||
LWLockInitialize(&graph->entryLock, hnsw_lock_tranche_id);
|
|
||||||
LWLockInitialize(&graph->allocatorLock, hnsw_lock_tranche_id);
|
|
||||||
LWLockInitialize(&graph->flushLock, hnsw_lock_tranche_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/* Add to buildstate or free */
|
||||||
* Initialize an allocator
|
if (inserted)
|
||||||
*/
|
{
|
||||||
static void
|
buildstate->elements = lappend(buildstate->elements, element);
|
||||||
InitAllocator(HnswAllocator * allocator, void *(*alloc) (Size size, void *state), void *state)
|
buildstate->memoryLeft -= HnswElementMemory(element, buildstate->m);
|
||||||
{
|
}
|
||||||
allocator->alloc = alloc;
|
else
|
||||||
allocator->state = state;
|
HnswFreeElement(element);
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Memory context allocator
|
|
||||||
*/
|
|
||||||
static void *
|
|
||||||
HnswMemoryContextAlloc(Size size, void *state)
|
|
||||||
{
|
|
||||||
HnswBuildState *buildstate = (HnswBuildState *) state;
|
|
||||||
void *chunk = MemoryContextAlloc(buildstate->graphCtx, size);
|
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= 130000
|
|
||||||
buildstate->graphData.memoryUsed = MemoryContextMemAllocated(buildstate->graphCtx, false);
|
|
||||||
#else
|
|
||||||
buildstate->graphData.memoryUsed += MAXALIGN(size);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Shared memory allocator
|
|
||||||
*/
|
|
||||||
static void *
|
|
||||||
HnswSharedMemoryAlloc(Size size, void *state)
|
|
||||||
{
|
|
||||||
HnswBuildState *buildstate = (HnswBuildState *) state;
|
|
||||||
void *chunk = buildstate->hnswarea + buildstate->graph->memoryUsed;
|
|
||||||
|
|
||||||
buildstate->graph->memoryUsed += MAXALIGN(size);
|
|
||||||
return chunk;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -637,29 +479,22 @@ InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, Index
|
|||||||
buildstate->normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
|
buildstate->normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
|
||||||
buildstate->collation = index->rd_indcollation[0];
|
buildstate->collation = index->rd_indcollation[0];
|
||||||
|
|
||||||
InitGraph(&buildstate->graphData, NULL, maintenance_work_mem * 1024L);
|
buildstate->elements = NIL;
|
||||||
buildstate->graph = &buildstate->graphData;
|
buildstate->entryPoint = NULL;
|
||||||
buildstate->ml = HnswGetMl(buildstate->m);
|
buildstate->ml = HnswGetMl(buildstate->m);
|
||||||
buildstate->maxLevel = HnswGetMaxLevel(buildstate->m);
|
buildstate->maxLevel = HnswGetMaxLevel(buildstate->m);
|
||||||
|
buildstate->memoryLeft = maintenance_work_mem * 1024L;
|
||||||
|
buildstate->flushed = false;
|
||||||
|
|
||||||
/* Reuse for each tuple */
|
/* Reuse for each tuple */
|
||||||
buildstate->normvec = InitVector(buildstate->dimensions);
|
buildstate->normvec = InitVector(buildstate->dimensions);
|
||||||
|
|
||||||
buildstate->graphCtx = GenerationContextCreate(CurrentMemoryContext,
|
|
||||||
"Hnsw build graph context",
|
|
||||||
#if PG_VERSION_NUM >= 150000
|
|
||||||
1024 * 1024, 1024 * 1024,
|
|
||||||
#endif
|
|
||||||
1024 * 1024);
|
|
||||||
buildstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
|
buildstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
|
||||||
"Hnsw build temporary context",
|
"Hnsw build temporary context",
|
||||||
ALLOCSET_DEFAULT_SIZES);
|
ALLOCSET_DEFAULT_SIZES);
|
||||||
|
|
||||||
InitAllocator(&buildstate->allocator, &HnswMemoryContextAlloc, buildstate);
|
|
||||||
|
|
||||||
buildstate->hnswleader = NULL;
|
buildstate->hnswleader = NULL;
|
||||||
buildstate->hnswshared = NULL;
|
buildstate->hnswshared = NULL;
|
||||||
buildstate->hnswarea = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -669,7 +504,6 @@ static void
|
|||||||
FreeBuildState(HnswBuildState * buildstate)
|
FreeBuildState(HnswBuildState * buildstate)
|
||||||
{
|
{
|
||||||
pfree(buildstate->normvec);
|
pfree(buildstate->normvec);
|
||||||
MemoryContextDelete(buildstate->graphCtx);
|
|
||||||
MemoryContextDelete(buildstate->tmpCtx);
|
MemoryContextDelete(buildstate->tmpCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -689,8 +523,7 @@ ParallelHeapScan(HnswBuildState * buildstate)
|
|||||||
SpinLockAcquire(&hnswshared->mutex);
|
SpinLockAcquire(&hnswshared->mutex);
|
||||||
if (hnswshared->nparticipantsdone == nparticipanttuplesorts)
|
if (hnswshared->nparticipantsdone == nparticipanttuplesorts)
|
||||||
{
|
{
|
||||||
buildstate->graph = &hnswshared->graphData;
|
buildstate->indtuples = hnswshared->indtuples;
|
||||||
buildstate->hnswarea = buildstate->hnswleader->hnswarea;
|
|
||||||
reltuples = hnswshared->reltuples;
|
reltuples = hnswshared->reltuples;
|
||||||
SpinLockRelease(&hnswshared->mutex);
|
SpinLockRelease(&hnswshared->mutex);
|
||||||
break;
|
break;
|
||||||
@@ -710,7 +543,7 @@ ParallelHeapScan(HnswBuildState * buildstate)
|
|||||||
* Perform a worker's portion of a parallel insert
|
* Perform a worker's portion of a parallel insert
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
HnswParallelScanAndInsert(HnswSpool * hnswspool, HnswShared * hnswshared, char *hnswarea, bool progress)
|
HnswParallelScanAndInsert(HnswSpool * hnswspool, HnswShared * hnswshared, bool progress)
|
||||||
{
|
{
|
||||||
HnswBuildState buildstate;
|
HnswBuildState buildstate;
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
@@ -725,9 +558,10 @@ HnswParallelScanAndInsert(HnswSpool * hnswspool, HnswShared * hnswshared, char *
|
|||||||
indexInfo = BuildIndexInfo(hnswspool->index);
|
indexInfo = BuildIndexInfo(hnswspool->index);
|
||||||
indexInfo->ii_Concurrent = hnswshared->isconcurrent;
|
indexInfo->ii_Concurrent = hnswshared->isconcurrent;
|
||||||
InitBuildState(&buildstate, hnswspool->heap, hnswspool->index, indexInfo, MAIN_FORKNUM);
|
InitBuildState(&buildstate, hnswspool->heap, hnswspool->index, indexInfo, MAIN_FORKNUM);
|
||||||
buildstate.graph = &hnswshared->graphData;
|
/* TODO Support in-memory builds */
|
||||||
buildstate.hnswarea = hnswarea;
|
buildstate.memoryLeft = 0;
|
||||||
InitAllocator(&buildstate.allocator, &HnswSharedMemoryAlloc, &buildstate);
|
buildstate.flushed = true;
|
||||||
|
buildstate.hnswshared = hnswshared;
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
scan = table_beginscan_parallel(hnswspool->heap,
|
scan = table_beginscan_parallel(hnswspool->heap,
|
||||||
ParallelTableScanFromHnswShared(hnswshared));
|
ParallelTableScanFromHnswShared(hnswshared));
|
||||||
@@ -768,9 +602,6 @@ HnswParallelBuildMain(dsm_segment *seg, shm_toc *toc)
|
|||||||
char *sharedquery;
|
char *sharedquery;
|
||||||
HnswSpool *hnswspool;
|
HnswSpool *hnswspool;
|
||||||
HnswShared *hnswshared;
|
HnswShared *hnswshared;
|
||||||
dsa_area *dsa;
|
|
||||||
void *dsaspace;
|
|
||||||
char *hnswarea;
|
|
||||||
Relation heapRel;
|
Relation heapRel;
|
||||||
Relation indexRel;
|
Relation indexRel;
|
||||||
LOCKMODE heapLockmode;
|
LOCKMODE heapLockmode;
|
||||||
@@ -811,12 +642,8 @@ HnswParallelBuildMain(dsm_segment *seg, shm_toc *toc)
|
|||||||
hnswspool->heap = heapRel;
|
hnswspool->heap = heapRel;
|
||||||
hnswspool->index = indexRel;
|
hnswspool->index = indexRel;
|
||||||
|
|
||||||
dsaspace = shm_toc_lookup(toc, PARALLEL_KEY_HNSW_AREA, false);
|
|
||||||
dsa = dsa_attach_in_place(dsaspace, seg);
|
|
||||||
hnswarea = dsa_get_address(dsa, hnswshared->dsaptr);
|
|
||||||
|
|
||||||
/* Perform inserts */
|
/* Perform inserts */
|
||||||
HnswParallelScanAndInsert(hnswspool, hnswshared, hnswarea, false);
|
HnswParallelScanAndInsert(hnswspool, hnswshared, false);
|
||||||
|
|
||||||
/* Close relations within worker */
|
/* Close relations within worker */
|
||||||
index_close(indexRel, indexLockmode);
|
index_close(indexRel, indexLockmode);
|
||||||
@@ -879,7 +706,7 @@ HnswLeaderParticipateAsWorker(HnswBuildState * buildstate)
|
|||||||
leaderworker->index = buildstate->index;
|
leaderworker->index = buildstate->index;
|
||||||
|
|
||||||
/* Perform work common to all participants */
|
/* Perform work common to all participants */
|
||||||
HnswParallelScanAndInsert(leaderworker, hnswleader->hnswshared, hnswleader->hnswarea, true);
|
HnswParallelScanAndInsert(leaderworker, hnswleader->hnswshared, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -892,12 +719,7 @@ HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
|||||||
int scantuplesortstates;
|
int scantuplesortstates;
|
||||||
Snapshot snapshot;
|
Snapshot snapshot;
|
||||||
Size esthnswshared;
|
Size esthnswshared;
|
||||||
Size esthnswarea;
|
|
||||||
Size estdsaspace;
|
|
||||||
HnswShared *hnswshared;
|
HnswShared *hnswshared;
|
||||||
void *dsaspace;
|
|
||||||
dsa_area *dsa;
|
|
||||||
char *hnswarea;
|
|
||||||
HnswLeader *hnswleader = (HnswLeader *) palloc0(sizeof(HnswLeader));
|
HnswLeader *hnswleader = (HnswLeader *) palloc0(sizeof(HnswLeader));
|
||||||
bool leaderparticipates = true;
|
bool leaderparticipates = true;
|
||||||
int querylen;
|
int querylen;
|
||||||
@@ -926,11 +748,7 @@ HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
|||||||
/* Estimate size of workspaces */
|
/* Estimate size of workspaces */
|
||||||
esthnswshared = ParallelEstimateShared(buildstate->heap, snapshot);
|
esthnswshared = ParallelEstimateShared(buildstate->heap, snapshot);
|
||||||
shm_toc_estimate_chunk(&pcxt->estimator, esthnswshared);
|
shm_toc_estimate_chunk(&pcxt->estimator, esthnswshared);
|
||||||
|
shm_toc_estimate_keys(&pcxt->estimator, 1);
|
||||||
/* Start with a minimal DSA so InitializeParallelDSM does not fail */
|
|
||||||
estdsaspace = dsa_minimum_size();
|
|
||||||
shm_toc_estimate_chunk(&pcxt->estimator, estdsaspace);
|
|
||||||
shm_toc_estimate_keys(&pcxt->estimator, 2);
|
|
||||||
|
|
||||||
/* Finally, estimate PARALLEL_KEY_QUERY_TEXT space */
|
/* Finally, estimate PARALLEL_KEY_QUERY_TEXT space */
|
||||||
if (debug_query_string)
|
if (debug_query_string)
|
||||||
@@ -967,6 +785,7 @@ HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
|||||||
/* Initialize mutable state */
|
/* Initialize mutable state */
|
||||||
hnswshared->nparticipantsdone = 0;
|
hnswshared->nparticipantsdone = 0;
|
||||||
hnswshared->reltuples = 0;
|
hnswshared->reltuples = 0;
|
||||||
|
hnswshared->indtuples = 0;
|
||||||
#if PG_VERSION_NUM >= 120000
|
#if PG_VERSION_NUM >= 120000
|
||||||
table_parallelscan_initialize(buildstate->heap,
|
table_parallelscan_initialize(buildstate->heap,
|
||||||
ParallelTableScanFromHnswShared(hnswshared),
|
ParallelTableScanFromHnswShared(hnswshared),
|
||||||
@@ -975,28 +794,7 @@ HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
|||||||
heap_parallelscan_initialize(&hnswshared->heapdesc, buildstate->heap, snapshot);
|
heap_parallelscan_initialize(&hnswshared->heapdesc, buildstate->heap, snapshot);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
dsaspace = shm_toc_allocate(pcxt->toc, estdsaspace);
|
|
||||||
dsa = dsa_create_in_place(dsaspace, estdsaspace, hnsw_lock_tranche_id, pcxt->seg);
|
|
||||||
|
|
||||||
esthnswarea = maintenance_work_mem * 1024L;
|
|
||||||
hnswshared->dsaptr = dsa_allocate_extended(dsa, esthnswarea, DSA_ALLOC_HUGE | DSA_ALLOC_NO_OOM);
|
|
||||||
|
|
||||||
/* If not enough shared memory, back out (do serial build) */
|
|
||||||
if (!DsaPointerIsValid(hnswshared->dsaptr))
|
|
||||||
{
|
|
||||||
if (IsMVCCSnapshot(snapshot))
|
|
||||||
UnregisterSnapshot(snapshot);
|
|
||||||
DestroyParallelContext(pcxt);
|
|
||||||
ExitParallelMode();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
hnswarea = dsa_get_address(dsa, hnswshared->dsaptr);
|
|
||||||
/* Report less than allocated so never fails */
|
|
||||||
InitGraph(&hnswshared->graphData, hnswarea, esthnswarea - 1024 * 1024);
|
|
||||||
|
|
||||||
shm_toc_insert(pcxt->toc, PARALLEL_KEY_HNSW_SHARED, hnswshared);
|
shm_toc_insert(pcxt->toc, PARALLEL_KEY_HNSW_SHARED, hnswshared);
|
||||||
shm_toc_insert(pcxt->toc, PARALLEL_KEY_HNSW_AREA, dsaspace);
|
|
||||||
|
|
||||||
/* Store query string for workers */
|
/* Store query string for workers */
|
||||||
if (debug_query_string)
|
if (debug_query_string)
|
||||||
@@ -1016,7 +814,6 @@ HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
|||||||
hnswleader->nparticipanttuplesorts++;
|
hnswleader->nparticipanttuplesorts++;
|
||||||
hnswleader->hnswshared = hnswshared;
|
hnswleader->hnswshared = hnswshared;
|
||||||
hnswleader->snapshot = snapshot;
|
hnswleader->snapshot = snapshot;
|
||||||
hnswleader->hnswarea = hnswarea;
|
|
||||||
|
|
||||||
/* If no workers were successfully launched, back out (do serial build) */
|
/* If no workers were successfully launched, back out (do serial build) */
|
||||||
if (pcxt->nworkers_launched == 0)
|
if (pcxt->nworkers_launched == 0)
|
||||||
@@ -1039,27 +836,6 @@ HnswBeginParallel(HnswBuildState * buildstate, bool isconcurrent, int request)
|
|||||||
WaitForParallelWorkersToAttach(pcxt);
|
WaitForParallelWorkersToAttach(pcxt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Compute parallel workers
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
ComputeParallelWorkers(Relation heap, Relation index)
|
|
||||||
{
|
|
||||||
int parallel_workers;
|
|
||||||
|
|
||||||
/* Make sure it's safe to use parallel workers */
|
|
||||||
parallel_workers = plan_create_index_workers(RelationGetRelid(heap), RelationGetRelid(index));
|
|
||||||
if (parallel_workers == 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* Use parallel_workers storage parameter on table if set */
|
|
||||||
parallel_workers = RelationGetParallelWorkers(heap, -1);
|
|
||||||
if (parallel_workers != -1)
|
|
||||||
return Min(parallel_workers, max_parallel_maintenance_workers);
|
|
||||||
|
|
||||||
return max_parallel_maintenance_workers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Build graph
|
* Build graph
|
||||||
*/
|
*/
|
||||||
@@ -1071,57 +847,36 @@ BuildGraph(HnswBuildState * buildstate, ForkNumber forkNum)
|
|||||||
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_HNSW_PHASE_LOAD);
|
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_HNSW_PHASE_LOAD);
|
||||||
|
|
||||||
/* Calculate parallel workers */
|
/* Calculate parallel workers */
|
||||||
if (buildstate->heap != NULL)
|
if (hnsw_enable_parallel_build)
|
||||||
parallel_workers = ComputeParallelWorkers(buildstate->heap, buildstate->index);
|
parallel_workers = plan_create_index_workers(RelationGetRelid(buildstate->heap), RelationGetRelid(buildstate->index));
|
||||||
|
|
||||||
/* Attempt to launch parallel worker scan when required */
|
/* Attempt to launch parallel worker scan when required */
|
||||||
if (parallel_workers > 0)
|
if (parallel_workers > 0)
|
||||||
HnswBeginParallel(buildstate, buildstate->indexInfo->ii_Concurrent, parallel_workers);
|
|
||||||
|
|
||||||
/* Add tuples to graph */
|
|
||||||
if (buildstate->heap != NULL)
|
|
||||||
{
|
{
|
||||||
if (buildstate->hnswleader)
|
/* TODO Support in-memory builds */
|
||||||
buildstate->reltuples = ParallelHeapScan(buildstate);
|
FlushPages(buildstate);
|
||||||
else
|
HnswBeginParallel(buildstate, buildstate->indexInfo->ii_Concurrent, parallel_workers);
|
||||||
{
|
|
||||||
#if PG_VERSION_NUM >= 120000
|
|
||||||
buildstate->reltuples = table_index_build_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
|
||||||
true, true, BuildCallback, (void *) buildstate, NULL);
|
|
||||||
#else
|
|
||||||
buildstate->reltuples = IndexBuildHeapScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
|
||||||
true, BuildCallback, (void *) buildstate, NULL);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
buildstate->indtuples = buildstate->graph->indtuples;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Flush pages */
|
/* Add tuples to sort */
|
||||||
if (!buildstate->graph->flushed)
|
if (buildstate->hnswleader)
|
||||||
FlushPages(buildstate);
|
buildstate->reltuples = ParallelHeapScan(buildstate);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#if PG_VERSION_NUM >= 120000
|
||||||
|
buildstate->reltuples = table_index_build_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||||
|
true, true, BuildCallback, (void *) buildstate, NULL);
|
||||||
|
#else
|
||||||
|
buildstate->reltuples = IndexBuildHeapScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||||
|
true, BuildCallback, (void *) buildstate, NULL);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/* End parallel build */
|
/* End parallel build */
|
||||||
if (buildstate->hnswleader)
|
if (buildstate->hnswleader)
|
||||||
HnswEndParallel(buildstate->hnswleader);
|
HnswEndParallel(buildstate->hnswleader);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if PG_VERSION_NUM < 110008
|
|
||||||
void
|
|
||||||
log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std)
|
|
||||||
{
|
|
||||||
for (BlockNumber blkno = startblk; blkno < endblk; blkno++)
|
|
||||||
{
|
|
||||||
Buffer buf = ReadBufferExtended(rel, forkNum, blkno, RBM_NORMAL, NULL);
|
|
||||||
|
|
||||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
|
||||||
MarkBufferDirty(buf);
|
|
||||||
log_newpage_buffer(buf, page_std);
|
|
||||||
UnlockReleaseBuffer(buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Build the index
|
* Build the index
|
||||||
*/
|
*/
|
||||||
@@ -1129,16 +884,13 @@ static void
|
|||||||
BuildIndex(Relation heap, Relation index, IndexInfo *indexInfo,
|
BuildIndex(Relation heap, Relation index, IndexInfo *indexInfo,
|
||||||
HnswBuildState * buildstate, ForkNumber forkNum)
|
HnswBuildState * buildstate, ForkNumber forkNum)
|
||||||
{
|
{
|
||||||
#ifdef HNSW_MEMORY
|
|
||||||
SeedRandom(42);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
InitBuildState(buildstate, heap, index, indexInfo, forkNum);
|
InitBuildState(buildstate, heap, index, indexInfo, forkNum);
|
||||||
|
|
||||||
BuildGraph(buildstate, forkNum);
|
if (buildstate->heap != NULL)
|
||||||
|
BuildGraph(buildstate, forkNum);
|
||||||
|
|
||||||
if (RelationNeedsWAL(index))
|
if (!buildstate->flushed)
|
||||||
log_newpage_range(index, forkNum, 0, RelationGetNumberOfBlocks(index), true);
|
FlushPages(buildstate);
|
||||||
|
|
||||||
FreeBuildState(buildstate);
|
FreeBuildState(buildstate);
|
||||||
}
|
}
|
||||||
|
|||||||
232
src/hnswinsert.c
232
src/hnswinsert.c
@@ -5,7 +5,6 @@
|
|||||||
#include "hnsw.h"
|
#include "hnsw.h"
|
||||||
#include "storage/bufmgr.h"
|
#include "storage/bufmgr.h"
|
||||||
#include "storage/lmgr.h"
|
#include "storage/lmgr.h"
|
||||||
#include "utils/datum.h"
|
|
||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -93,7 +92,7 @@ HnswFreeOffset(Relation index, Buffer buf, Page page, HnswElement element, Size
|
|||||||
* Add a new page
|
* Add a new page
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
HnswInsertAppendPage(Relation index, Buffer *nbuf, Page *npage, GenericXLogState *state, Page page, bool building)
|
HnswInsertAppendPage(Relation index, Buffer *nbuf, Page *npage, GenericXLogState *state, Page page)
|
||||||
{
|
{
|
||||||
/* Add a new page */
|
/* Add a new page */
|
||||||
LockRelationForExtension(index, ExclusiveLock);
|
LockRelationForExtension(index, ExclusiveLock);
|
||||||
@@ -101,11 +100,7 @@ HnswInsertAppendPage(Relation index, Buffer *nbuf, Page *npage, GenericXLogState
|
|||||||
UnlockRelationForExtension(index, ExclusiveLock);
|
UnlockRelationForExtension(index, ExclusiveLock);
|
||||||
|
|
||||||
/* Init new page */
|
/* Init new page */
|
||||||
if (building)
|
*npage = GenericXLogRegisterBuffer(state, *nbuf, GENERIC_XLOG_FULL_IMAGE);
|
||||||
*npage = BufferGetPage(*nbuf);
|
|
||||||
else
|
|
||||||
*npage = GenericXLogRegisterBuffer(state, *nbuf, GENERIC_XLOG_FULL_IMAGE);
|
|
||||||
|
|
||||||
HnswInitPage(*nbuf, *npage);
|
HnswInitPage(*nbuf, *npage);
|
||||||
|
|
||||||
/* Update previous buffer */
|
/* Update previous buffer */
|
||||||
@@ -116,7 +111,7 @@ HnswInsertAppendPage(Relation index, Buffer *nbuf, Page *npage, GenericXLogState
|
|||||||
* Add to element and neighbor pages
|
* Add to element and neighbor pages
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPage, BlockNumber *updatedInsertPage, bool building)
|
WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPage, BlockNumber *updatedInsertPage)
|
||||||
{
|
{
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
@@ -134,10 +129,9 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
|||||||
OffsetNumber freeOffno = InvalidOffsetNumber;
|
OffsetNumber freeOffno = InvalidOffsetNumber;
|
||||||
OffsetNumber freeNeighborOffno = InvalidOffsetNumber;
|
OffsetNumber freeNeighborOffno = InvalidOffsetNumber;
|
||||||
BlockNumber newInsertPage = InvalidBlockNumber;
|
BlockNumber newInsertPage = InvalidBlockNumber;
|
||||||
char *base = NULL;
|
|
||||||
|
|
||||||
/* Calculate sizes */
|
/* Calculate sizes */
|
||||||
etupSize = HNSW_ELEMENT_TUPLE_SIZE(VARSIZE_ANY(HnswPtrAccess(base, e->value)));
|
etupSize = HNSW_ELEMENT_TUPLE_SIZE(VARSIZE_ANY(DatumGetPointer(e->value)));
|
||||||
ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(e->level, m);
|
ntupSize = HNSW_NEIGHBOR_TUPLE_SIZE(e->level, m);
|
||||||
combinedSize = etupSize + ntupSize + sizeof(ItemIdData);
|
combinedSize = etupSize + ntupSize + sizeof(ItemIdData);
|
||||||
maxSize = HNSW_MAX_SIZE;
|
maxSize = HNSW_MAX_SIZE;
|
||||||
@@ -145,11 +139,11 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
|||||||
|
|
||||||
/* Prepare element tuple */
|
/* Prepare element tuple */
|
||||||
etup = palloc0(etupSize);
|
etup = palloc0(etupSize);
|
||||||
HnswSetElementTuple(base, etup, e);
|
HnswSetElementTuple(etup, e);
|
||||||
|
|
||||||
/* Prepare neighbor tuple */
|
/* Prepare neighbor tuple */
|
||||||
ntup = palloc0(ntupSize);
|
ntup = palloc0(ntupSize);
|
||||||
HnswSetNeighborTuple(base, ntup, e, m);
|
HnswSetNeighborTuple(ntup, e, m);
|
||||||
|
|
||||||
/* Find a page (or two if needed) to insert the tuples */
|
/* Find a page (or two if needed) to insert the tuples */
|
||||||
for (;;)
|
for (;;)
|
||||||
@@ -157,16 +151,8 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
|||||||
buf = ReadBuffer(index, currentPage);
|
buf = ReadBuffer(index, currentPage);
|
||||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||||
|
|
||||||
if (building)
|
state = GenericXLogStart(index);
|
||||||
{
|
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||||
state = NULL;
|
|
||||||
page = BufferGetPage(buf);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
state = GenericXLogStart(index);
|
|
||||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Keep track of first page where element at level 0 can fit */
|
/* Keep track of first page where element at level 0 can fit */
|
||||||
if (!BlockNumberIsValid(newInsertPage) && PageGetFreeSpace(page) >= minCombinedSize)
|
if (!BlockNumberIsValid(newInsertPage) && PageGetFreeSpace(page) >= minCombinedSize)
|
||||||
@@ -186,12 +172,7 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
|||||||
if (HnswFreeOffset(index, buf, page, e, ntupSize, &nbuf, &npage, &freeOffno, &freeNeighborOffno, &newInsertPage))
|
if (HnswFreeOffset(index, buf, page, e, ntupSize, &nbuf, &npage, &freeOffno, &freeNeighborOffno, &newInsertPage))
|
||||||
{
|
{
|
||||||
if (nbuf != buf)
|
if (nbuf != buf)
|
||||||
{
|
npage = GenericXLogRegisterBuffer(state, nbuf, 0);
|
||||||
if (building)
|
|
||||||
npage = BufferGetPage(nbuf);
|
|
||||||
else
|
|
||||||
npage = GenericXLogRegisterBuffer(state, nbuf, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -200,7 +181,7 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
|||||||
/* Skip if both tuples can fit on the same page */
|
/* Skip if both tuples can fit on the same page */
|
||||||
if (combinedSize > maxSize && PageGetFreeSpace(page) >= etupSize && !BlockNumberIsValid(HnswPageGetOpaque(page)->nextblkno))
|
if (combinedSize > maxSize && PageGetFreeSpace(page) >= etupSize && !BlockNumberIsValid(HnswPageGetOpaque(page)->nextblkno))
|
||||||
{
|
{
|
||||||
HnswInsertAppendPage(index, &nbuf, &npage, state, page, building);
|
HnswInsertAppendPage(index, &nbuf, &npage, state, page);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,8 +190,7 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
|||||||
if (BlockNumberIsValid(currentPage))
|
if (BlockNumberIsValid(currentPage))
|
||||||
{
|
{
|
||||||
/* Move to next page */
|
/* Move to next page */
|
||||||
if (!building)
|
GenericXLogAbort(state);
|
||||||
GenericXLogAbort(state);
|
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -218,33 +198,22 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
|||||||
Buffer newbuf;
|
Buffer newbuf;
|
||||||
Page newpage;
|
Page newpage;
|
||||||
|
|
||||||
HnswInsertAppendPage(index, &newbuf, &newpage, state, page, building);
|
HnswInsertAppendPage(index, &newbuf, &newpage, state, page);
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
if (building)
|
GenericXLogFinish(state);
|
||||||
MarkBufferDirty(buf);
|
|
||||||
else
|
|
||||||
GenericXLogFinish(state);
|
|
||||||
|
|
||||||
/* Unlock previous buffer */
|
/* Unlock previous buffer */
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
|
|
||||||
/* Prepare new buffer */
|
/* Prepare new buffer */
|
||||||
|
state = GenericXLogStart(index);
|
||||||
buf = newbuf;
|
buf = newbuf;
|
||||||
if (building)
|
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||||
{
|
|
||||||
state = NULL;
|
|
||||||
page = BufferGetPage(buf);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
state = GenericXLogStart(index);
|
|
||||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create new page for neighbors if needed */
|
/* Create new page for neighbors if needed */
|
||||||
if (PageGetFreeSpace(page) < combinedSize)
|
if (PageGetFreeSpace(page) < combinedSize)
|
||||||
HnswInsertAppendPage(index, &nbuf, &npage, state, page, building);
|
HnswInsertAppendPage(index, &nbuf, &npage, state, page);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
nbuf = buf;
|
nbuf = buf;
|
||||||
@@ -298,14 +267,7 @@ WriteNewElementPages(Relation index, HnswElement e, int m, BlockNumber insertPag
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
if (building)
|
GenericXLogFinish(state);
|
||||||
{
|
|
||||||
MarkBufferDirty(buf);
|
|
||||||
if (nbuf != buf)
|
|
||||||
MarkBufferDirty(nbuf);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
GenericXLogFinish(state);
|
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
if (nbuf != buf)
|
if (nbuf != buf)
|
||||||
UnlockReleaseBuffer(nbuf);
|
UnlockReleaseBuffer(nbuf);
|
||||||
@@ -339,14 +301,12 @@ ConnectionExists(HnswElement e, HnswNeighborTuple ntup, int startIdx, int lm)
|
|||||||
* Update neighbors
|
* Update neighbors
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting, bool building)
|
HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement e, int m, bool checkExisting)
|
||||||
{
|
{
|
||||||
char *base = NULL;
|
|
||||||
|
|
||||||
for (int lc = e->level; lc >= 0; lc--)
|
for (int lc = e->level; lc >= 0; lc--)
|
||||||
{
|
{
|
||||||
int lm = HnswGetLayerM(m, lc);
|
int lm = HnswGetLayerM(m, lc);
|
||||||
HnswNeighborArray *neighbors = HnswGetNeighbors(base, e, lc);
|
HnswNeighborArray *neighbors = &e->neighbors[lc];
|
||||||
|
|
||||||
for (int i = 0; i < neighbors->length; i++)
|
for (int i = 0; i < neighbors->length; i++)
|
||||||
{
|
{
|
||||||
@@ -359,12 +319,11 @@ HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswE
|
|||||||
Size ntupSize;
|
Size ntupSize;
|
||||||
int idx = -1;
|
int idx = -1;
|
||||||
int startIdx;
|
int startIdx;
|
||||||
HnswElement neighborElement = HnswPtrAccess(base, hc->element);
|
OffsetNumber offno = hc->element->neighborOffno;
|
||||||
OffsetNumber offno = neighborElement->neighborOffno;
|
|
||||||
|
|
||||||
/* Get latest neighbors since they may have changed */
|
/* Get latest neighbors since they may have changed */
|
||||||
/* Do not lock yet since selecting neighbors can take time */
|
/* Do not lock yet since selecting neighbors can take time */
|
||||||
HnswLoadNeighbors(neighborElement, index, m);
|
HnswLoadNeighbors(hc->element, index, m);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Could improve performance for vacuuming by checking neighbors
|
* Could improve performance for vacuuming by checking neighbors
|
||||||
@@ -374,25 +333,17 @@ HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswE
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* Select neighbors */
|
/* Select neighbors */
|
||||||
HnswUpdateConnection(NULL, e, hc, lm, lc, &idx, index, procinfo, collation);
|
HnswUpdateConnection(e, hc, lm, lc, &idx, index, procinfo, collation);
|
||||||
|
|
||||||
/* New element was not selected as a neighbor */
|
/* New element was not selected as a neighbor */
|
||||||
if (idx == -1)
|
if (idx == -1)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Register page */
|
/* Register page */
|
||||||
buf = ReadBuffer(index, neighborElement->neighborPage);
|
buf = ReadBuffer(index, hc->element->neighborPage);
|
||||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||||
if (building)
|
state = GenericXLogStart(index);
|
||||||
{
|
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||||
state = NULL;
|
|
||||||
page = BufferGetPage(buf);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
state = GenericXLogStart(index);
|
|
||||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get tuple */
|
/* Get tuple */
|
||||||
itemid = PageGetItemId(page, offno);
|
itemid = PageGetItemId(page, offno);
|
||||||
@@ -400,7 +351,7 @@ HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswE
|
|||||||
ntupSize = ItemIdGetLength(itemid);
|
ntupSize = ItemIdGetLength(itemid);
|
||||||
|
|
||||||
/* Calculate index for update */
|
/* Calculate index for update */
|
||||||
startIdx = (neighborElement->level - lc) * m;
|
startIdx = (hc->element->level - lc) * m;
|
||||||
|
|
||||||
/* Check for existing connection */
|
/* Check for existing connection */
|
||||||
if (checkExisting && ConnectionExists(e, ntup, startIdx, lm))
|
if (checkExisting && ConnectionExists(e, ntup, startIdx, lm))
|
||||||
@@ -434,12 +385,9 @@ HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswE
|
|||||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
if (building)
|
GenericXLogFinish(state);
|
||||||
MarkBufferDirty(buf);
|
|
||||||
else
|
|
||||||
GenericXLogFinish(state);
|
|
||||||
}
|
}
|
||||||
else if (!building)
|
else
|
||||||
GenericXLogAbort(state);
|
GenericXLogAbort(state);
|
||||||
|
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
@@ -451,7 +399,7 @@ HnswUpdateNeighborPages(Relation index, FmgrInfo *procinfo, Oid collation, HnswE
|
|||||||
* Add a heap TID to an existing element
|
* Add a heap TID to an existing element
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
HnswAddDuplicate(Relation index, HnswElement element, HnswElement dup, bool building)
|
HnswAddDuplicate(Relation index, HnswElement element, HnswElement dup)
|
||||||
{
|
{
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
Page page;
|
Page page;
|
||||||
@@ -464,16 +412,8 @@ HnswAddDuplicate(Relation index, HnswElement element, HnswElement dup, bool buil
|
|||||||
/* Read page */
|
/* Read page */
|
||||||
buf = ReadBuffer(index, dup->blkno);
|
buf = ReadBuffer(index, dup->blkno);
|
||||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||||
if (building)
|
state = GenericXLogStart(index);
|
||||||
{
|
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||||
state = NULL;
|
|
||||||
page = BufferGetPage(buf);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
state = GenericXLogStart(index);
|
|
||||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Find space */
|
/* Find space */
|
||||||
itemid = PageGetItemId(page, dup->offno);
|
itemid = PageGetItemId(page, dup->offno);
|
||||||
@@ -488,97 +428,82 @@ HnswAddDuplicate(Relation index, HnswElement element, HnswElement dup, bool buil
|
|||||||
/* Either being deleted or we lost our chance to another backend */
|
/* Either being deleted or we lost our chance to another backend */
|
||||||
if (i == 0 || i == HNSW_HEAPTIDS)
|
if (i == 0 || i == HNSW_HEAPTIDS)
|
||||||
{
|
{
|
||||||
if (!building)
|
GenericXLogAbort(state);
|
||||||
GenericXLogAbort(state);
|
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add heap TID */
|
/* Add heap TID */
|
||||||
etup->heaptids[i] = element->heaptids[0];
|
etup->heaptids[i] = *((ItemPointer) linitial(element->heaptids));
|
||||||
|
|
||||||
/* Overwrite tuple */
|
/* Overwrite tuple */
|
||||||
if (!PageIndexTupleOverwrite(page, dup->offno, (Item) etup, etupSize))
|
if (!PageIndexTupleOverwrite(page, dup->offno, (Item) etup, etupSize))
|
||||||
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
elog(ERROR, "failed to add index item to \"%s\"", RelationGetRelationName(index));
|
||||||
|
|
||||||
/* Commit */
|
/* Commit */
|
||||||
if (building)
|
GenericXLogFinish(state);
|
||||||
MarkBufferDirty(buf);
|
|
||||||
else
|
|
||||||
GenericXLogFinish(state);
|
|
||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Find duplicate element
|
|
||||||
*/
|
|
||||||
static bool
|
|
||||||
HnswFindDuplicate(Relation index, HnswElement element, bool building)
|
|
||||||
{
|
|
||||||
char *base = NULL;
|
|
||||||
HnswNeighborArray *neighbors = HnswGetNeighbors(base, element, 0);
|
|
||||||
|
|
||||||
for (int i = 0; i < neighbors->length; i++)
|
|
||||||
{
|
|
||||||
HnswCandidate *neighbor = &neighbors->items[i];
|
|
||||||
HnswElement neighborElement = HnswPtrAccess(base, neighbor->element);
|
|
||||||
Datum value = HnswGetValue(base, element);
|
|
||||||
Datum neighborValue = HnswGetValue(base, neighborElement);
|
|
||||||
|
|
||||||
/* Exit early since ordered by distance */
|
|
||||||
if (!datumIsEqual(value, neighborValue, false, -1))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (HnswAddDuplicate(index, element, neighborElement, building))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Write changes to disk
|
* Write changes to disk
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
WriteElement(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement element, int m, int efConstruction, HnswElement entryPoint, bool building)
|
WriteElement(Relation index, FmgrInfo *procinfo, Oid collation, HnswElement element, int m, int efConstruction, HnswElement dup, HnswElement entryPoint)
|
||||||
{
|
{
|
||||||
BlockNumber newInsertPage = InvalidBlockNumber;
|
BlockNumber newInsertPage = InvalidBlockNumber;
|
||||||
|
|
||||||
/* Look for duplicate */
|
/* Try to add to existing page */
|
||||||
if (HnswFindDuplicate(index, element, building))
|
if (dup != NULL)
|
||||||
return;
|
{
|
||||||
|
if (HnswAddDuplicate(index, element, dup))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* Write element and neighbor tuples */
|
/* Write element and neighbor tuples */
|
||||||
WriteNewElementPages(index, element, m, GetInsertPage(index), &newInsertPage, building);
|
WriteNewElementPages(index, element, m, GetInsertPage(index), &newInsertPage);
|
||||||
|
|
||||||
/* Update insert page if needed */
|
/* Update insert page if needed */
|
||||||
if (BlockNumberIsValid(newInsertPage))
|
if (BlockNumberIsValid(newInsertPage))
|
||||||
HnswUpdateMetaPage(index, 0, NULL, newInsertPage, MAIN_FORKNUM, building);
|
HnswUpdateMetaPage(index, 0, NULL, newInsertPage, MAIN_FORKNUM);
|
||||||
|
|
||||||
/* Update neighbors */
|
/* Update neighbors */
|
||||||
HnswUpdateNeighborPages(index, procinfo, collation, element, m, false, building);
|
HnswUpdateNeighborPages(index, procinfo, collation, element, m, false);
|
||||||
|
|
||||||
/* Update entry point if needed */
|
/* Update metapage if needed */
|
||||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||||
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_GREATER, element, InvalidBlockNumber, MAIN_FORKNUM, building);
|
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_GREATER, element, InvalidBlockNumber, MAIN_FORKNUM);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Insert a tuple into the index
|
* Insert a tuple into the index
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel, bool building)
|
HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel)
|
||||||
{
|
{
|
||||||
|
Datum value;
|
||||||
|
FmgrInfo *normprocinfo;
|
||||||
HnswElement entryPoint;
|
HnswElement entryPoint;
|
||||||
HnswElement element;
|
HnswElement element;
|
||||||
int m;
|
int m;
|
||||||
int efConstruction = HnswGetEfConstruction(index);
|
int efConstruction = HnswGetEfConstruction(index);
|
||||||
FmgrInfo *procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
|
FmgrInfo *procinfo = index_getprocinfo(index, 1, HNSW_DISTANCE_PROC);
|
||||||
Oid collation = index->rd_indcollation[0];
|
Oid collation = index->rd_indcollation[0];
|
||||||
|
HnswElement dup;
|
||||||
LOCKMODE lockmode = ShareLock;
|
LOCKMODE lockmode = ShareLock;
|
||||||
char *base = NULL;
|
|
||||||
|
/* Detoast once for all calls */
|
||||||
|
value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
||||||
|
|
||||||
|
/* Normalize if needed */
|
||||||
|
normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
|
||||||
|
if (normprocinfo != NULL)
|
||||||
|
{
|
||||||
|
if (!HnswNormValue(normprocinfo, collation, &value, NULL))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get a shared lock. This allows vacuum to ensure no in-flight inserts
|
* Get a shared lock. This allows vacuum to ensure no in-flight inserts
|
||||||
@@ -591,8 +516,8 @@ HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull,
|
|||||||
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
||||||
|
|
||||||
/* Create an element */
|
/* Create an element */
|
||||||
element = HnswInitElement(base, heap_tid, m, HnswGetMl(m), HnswGetMaxLevel(m), NULL);
|
element = HnswInitElement(heap_tid, m, HnswGetMl(m), HnswGetMaxLevel(m));
|
||||||
HnswPtrStore(base, element->value, DatumGetPointer(value));
|
element->value = value;
|
||||||
|
|
||||||
/* Prevent concurrent inserts when likely updating entry point */
|
/* Prevent concurrent inserts when likely updating entry point */
|
||||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||||
@@ -609,10 +534,13 @@ HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Insert element in graph */
|
/* Insert element in graph */
|
||||||
HnswInsertElement(base, element, entryPoint, index, procinfo, collation, m, efConstruction, false);
|
HnswInsertElement(element, entryPoint, index, procinfo, collation, m, efConstruction, false);
|
||||||
|
|
||||||
|
/* Look for duplicate */
|
||||||
|
dup = HnswFindDuplicate(element);
|
||||||
|
|
||||||
/* Write to disk */
|
/* Write to disk */
|
||||||
WriteElement(index, procinfo, collation, element, m, efConstruction, entryPoint, building);
|
WriteElement(index, procinfo, collation, element, m, efConstruction, dup, entryPoint);
|
||||||
|
|
||||||
/* Release lock */
|
/* Release lock */
|
||||||
UnlockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
UnlockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
||||||
@@ -620,30 +548,6 @@ HnswInsertTupleOnDisk(Relation index, Datum value, Datum *values, bool *isnull,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Insert a tuple into the index
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, Relation heapRel)
|
|
||||||
{
|
|
||||||
Datum value;
|
|
||||||
FmgrInfo *normprocinfo;
|
|
||||||
Oid collation = index->rd_indcollation[0];
|
|
||||||
|
|
||||||
/* Detoast once for all calls */
|
|
||||||
value = PointerGetDatum(PG_DETOAST_DATUM(values[0]));
|
|
||||||
|
|
||||||
/* Normalize if needed */
|
|
||||||
normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
|
|
||||||
if (normprocinfo != NULL)
|
|
||||||
{
|
|
||||||
if (!HnswNormValue(normprocinfo, collation, &value, NULL))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HnswInsertTupleOnDisk(index, value, values, isnull, heap_tid, heapRel, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Insert a tuple into the index
|
* Insert a tuple into the index
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ GetScanItems(IndexScanDesc scan, Datum q)
|
|||||||
List *w;
|
List *w;
|
||||||
int m;
|
int m;
|
||||||
HnswElement entryPoint;
|
HnswElement entryPoint;
|
||||||
char *base = NULL;
|
|
||||||
|
|
||||||
/* Get m and entry point */
|
/* Get m and entry point */
|
||||||
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
HnswGetMetaPageInfo(index, &m, &entryPoint);
|
||||||
@@ -29,15 +28,15 @@ GetScanItems(IndexScanDesc scan, Datum q)
|
|||||||
if (entryPoint == NULL)
|
if (entryPoint == NULL)
|
||||||
return NIL;
|
return NIL;
|
||||||
|
|
||||||
ep = list_make1(HnswEntryCandidate(base, entryPoint, q, index, procinfo, collation, false));
|
ep = list_make1(HnswEntryCandidate(entryPoint, q, index, procinfo, collation, false));
|
||||||
|
|
||||||
for (int lc = entryPoint->level; lc >= 1; lc--)
|
for (int lc = entryPoint->level; lc >= 1; lc--)
|
||||||
{
|
{
|
||||||
w = HnswSearchLayer(base, q, ep, 1, lc, index, procinfo, collation, m, false, NULL);
|
w = HnswSearchLayer(q, ep, 1, lc, index, procinfo, collation, m, false, NULL);
|
||||||
ep = w;
|
ep = w;
|
||||||
}
|
}
|
||||||
|
|
||||||
return HnswSearchLayer(base, q, ep, hnsw_ef_search, 0, index, procinfo, collation, m, false, NULL);
|
return HnswSearchLayer(q, ep, hnsw_ef_search, 0, index, procinfo, collation, m, false, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -185,19 +184,19 @@ hnswgettuple(IndexScanDesc scan, ScanDirection dir)
|
|||||||
|
|
||||||
while (list_length(so->w) > 0)
|
while (list_length(so->w) > 0)
|
||||||
{
|
{
|
||||||
char *base = NULL;
|
|
||||||
HnswCandidate *hc = llast(so->w);
|
HnswCandidate *hc = llast(so->w);
|
||||||
HnswElement element = HnswPtrAccess(base, hc->element);
|
|
||||||
ItemPointer heaptid;
|
ItemPointer heaptid;
|
||||||
|
|
||||||
/* Move to next element if no valid heap TIDs */
|
/* Move to next element if no valid heap TIDs */
|
||||||
if (element->heaptidsLength == 0)
|
if (list_length(hc->element->heaptids) == 0)
|
||||||
{
|
{
|
||||||
so->w = list_delete_last(so->w);
|
so->w = list_delete_last(so->w);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
heaptid = &element->heaptids[--element->heaptidsLength];
|
heaptid = llast(hc->element->heaptids);
|
||||||
|
|
||||||
|
hc->element->heaptids = list_delete_last(hc->element->heaptids);
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldCtx);
|
MemoryContextSwitchTo(oldCtx);
|
||||||
|
|
||||||
|
|||||||
576
src/hnswutils.c
576
src/hnswutils.c
File diff suppressed because it is too large
Load Diff
@@ -12,9 +12,12 @@
|
|||||||
* Check if deleted list contains an index TID
|
* Check if deleted list contains an index TID
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
DeletedContains(tidhash_hash * deleted, ItemPointer indextid)
|
DeletedContains(HTAB *deleted, ItemPointer indextid)
|
||||||
{
|
{
|
||||||
return tidhash_lookup(deleted, *indextid) != NULL;
|
bool found;
|
||||||
|
|
||||||
|
hash_search(deleted, indextid, HASH_FIND, &found);
|
||||||
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -107,13 +110,11 @@ RemoveHeapTids(HnswVacuumState * vacuumstate)
|
|||||||
if (!ItemPointerIsValid(&etup->heaptids[0]))
|
if (!ItemPointerIsValid(&etup->heaptids[0]))
|
||||||
{
|
{
|
||||||
ItemPointerData ip;
|
ItemPointerData ip;
|
||||||
bool found;
|
|
||||||
|
|
||||||
/* Add to deleted list */
|
/* Add to deleted list */
|
||||||
ItemPointerSet(&ip, blkno, offno);
|
ItemPointerSet(&ip, blkno, offno);
|
||||||
|
|
||||||
tidhash_insert(vacuumstate->deleted, ip, &found);
|
(void) hash_search(vacuumstate->deleted, &ip, HASH_ENTER, NULL);
|
||||||
Assert(!found);
|
|
||||||
}
|
}
|
||||||
else if (etup->level > highestLevel && !(entryPoint != NULL && blkno == entryPoint->blkno && offno == entryPoint->offno))
|
else if (etup->level > highestLevel && !(entryPoint != NULL && blkno == entryPoint->blkno && offno == entryPoint->offno))
|
||||||
{
|
{
|
||||||
@@ -205,15 +206,15 @@ RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswEleme
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
/* Init fields */
|
/* Init fields */
|
||||||
HnswInitNeighbors(NULL, element, m, NULL);
|
HnswInitNeighbors(element, m);
|
||||||
element->heaptidsLength = 0;
|
element->heaptids = NIL;
|
||||||
|
|
||||||
/* Add element to graph, skipping itself */
|
/* Add element to graph, skipping itself */
|
||||||
HnswInsertElement(NULL, element, entryPoint, index, procinfo, collation, m, efConstruction, true);
|
HnswInsertElement(element, entryPoint, index, procinfo, collation, m, efConstruction, true);
|
||||||
|
|
||||||
/* Update neighbor tuple */
|
/* Update neighbor tuple */
|
||||||
/* Do this before getting page to minimize locking */
|
/* Do this before getting page to minimize locking */
|
||||||
HnswSetNeighborTuple(NULL, ntup, element, m);
|
HnswSetNeighborTuple(ntup, element, m);
|
||||||
|
|
||||||
/* Get neighbor page */
|
/* Get neighbor page */
|
||||||
buf = ReadBufferExtended(index, MAIN_FORKNUM, element->neighborPage, RBM_NORMAL, bas);
|
buf = ReadBufferExtended(index, MAIN_FORKNUM, element->neighborPage, RBM_NORMAL, bas);
|
||||||
@@ -230,7 +231,7 @@ RepairGraphElement(HnswVacuumState * vacuumstate, HnswElement element, HnswEleme
|
|||||||
UnlockReleaseBuffer(buf);
|
UnlockReleaseBuffer(buf);
|
||||||
|
|
||||||
/* Update neighbors */
|
/* Update neighbors */
|
||||||
HnswUpdateNeighborPages(index, procinfo, collation, element, m, true, false);
|
HnswUpdateNeighborPages(index, procinfo, collation, element, m, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -286,7 +287,7 @@ RepairGraphEntryPoint(HnswVacuumState * vacuumstate)
|
|||||||
* point is outdated and empty, the entry point will be empty
|
* point is outdated and empty, the entry point will be empty
|
||||||
* until an element is repaired.
|
* until an element is repaired.
|
||||||
*/
|
*/
|
||||||
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_ALWAYS, highestPoint, InvalidBlockNumber, MAIN_FORKNUM, false);
|
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_ALWAYS, highestPoint, InvalidBlockNumber, MAIN_FORKNUM);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -301,7 +302,7 @@ RepairGraphEntryPoint(HnswVacuumState * vacuumstate)
|
|||||||
{
|
{
|
||||||
/* Reset neighbors from previous update */
|
/* Reset neighbors from previous update */
|
||||||
if (highestPoint != NULL)
|
if (highestPoint != NULL)
|
||||||
HnswPtrStore((char *) NULL, highestPoint->neighbors, (HnswNeighborArrayPtr *) NULL);
|
highestPoint->neighbors = NULL;
|
||||||
|
|
||||||
RepairGraphElement(vacuumstate, entryPoint, highestPoint);
|
RepairGraphElement(vacuumstate, entryPoint, highestPoint);
|
||||||
}
|
}
|
||||||
@@ -419,7 +420,7 @@ RepairGraph(HnswVacuumState * vacuumstate)
|
|||||||
* was replaced and highest point was outdated.
|
* was replaced and highest point was outdated.
|
||||||
*/
|
*/
|
||||||
if (entryPoint == NULL || element->level > entryPoint->level)
|
if (entryPoint == NULL || element->level > entryPoint->level)
|
||||||
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_GREATER, element, InvalidBlockNumber, MAIN_FORKNUM, false);
|
HnswUpdateMetaPage(index, HNSW_UPDATE_ENTRY_GREATER, element, InvalidBlockNumber, MAIN_FORKNUM);
|
||||||
|
|
||||||
/* Release lock */
|
/* Release lock */
|
||||||
UnlockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
UnlockPage(index, HNSW_UPDATE_LOCK, lockmode);
|
||||||
@@ -564,7 +565,7 @@ MarkDeleted(HnswVacuumState * vacuumstate)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Update insert page last, after everything has been marked as deleted */
|
/* Update insert page last, after everything has been marked as deleted */
|
||||||
HnswUpdateMetaPage(index, 0, NULL, insertPage, MAIN_FORKNUM, false);
|
HnswUpdateMetaPage(index, 0, NULL, insertPage, MAIN_FORKNUM);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -574,6 +575,7 @@ static void
|
|||||||
InitVacuumState(HnswVacuumState * vacuumstate, IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state)
|
InitVacuumState(HnswVacuumState * vacuumstate, IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state)
|
||||||
{
|
{
|
||||||
Relation index = info->index;
|
Relation index = info->index;
|
||||||
|
HASHCTL hash_ctl;
|
||||||
|
|
||||||
if (stats == NULL)
|
if (stats == NULL)
|
||||||
stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
|
stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
|
||||||
@@ -595,7 +597,10 @@ InitVacuumState(HnswVacuumState * vacuumstate, IndexVacuumInfo *info, IndexBulkD
|
|||||||
HnswGetMetaPageInfo(index, &vacuumstate->m, NULL);
|
HnswGetMetaPageInfo(index, &vacuumstate->m, NULL);
|
||||||
|
|
||||||
/* Create hash table */
|
/* Create hash table */
|
||||||
vacuumstate->deleted = tidhash_create(CurrentMemoryContext, 256, NULL);
|
hash_ctl.keysize = sizeof(ItemPointerData);
|
||||||
|
hash_ctl.entrysize = sizeof(ItemPointerData);
|
||||||
|
hash_ctl.hcxt = CurrentMemoryContext;
|
||||||
|
vacuumstate->deleted = hash_create("hnswbulkdelete indextids", 256, &hash_ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -604,7 +609,7 @@ InitVacuumState(HnswVacuumState * vacuumstate, IndexVacuumInfo *info, IndexBulkD
|
|||||||
static void
|
static void
|
||||||
FreeVacuumState(HnswVacuumState * vacuumstate)
|
FreeVacuumState(HnswVacuumState * vacuumstate)
|
||||||
{
|
{
|
||||||
tidhash_destroy(vacuumstate->deleted);
|
hash_destroy(vacuumstate->deleted);
|
||||||
FreeAccessStrategy(vacuumstate->bas);
|
FreeAccessStrategy(vacuumstate->bas);
|
||||||
pfree(vacuumstate->ntup);
|
pfree(vacuumstate->ntup);
|
||||||
MemoryContextDelete(vacuumstate->tmpCtx);
|
MemoryContextDelete(vacuumstate->tmpCtx);
|
||||||
|
|||||||
@@ -543,10 +543,10 @@ CreateListPages(Relation index, VectorArray centers, int dimensions,
|
|||||||
pfree(list);
|
pfree(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
|
||||||
/*
|
/*
|
||||||
* Print k-means metrics
|
* Print k-means metrics
|
||||||
*/
|
*/
|
||||||
|
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||||
static void
|
static void
|
||||||
PrintKmeansMetrics(IvfflatBuildState * buildstate)
|
PrintKmeansMetrics(IvfflatBuildState * buildstate)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,10 +6,6 @@
|
|||||||
#include "ivfflat.h"
|
#include "ivfflat.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
|
|
||||||
#ifdef IVFFLAT_MEMORY
|
|
||||||
#include "utils/memutils.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize with kmeans++
|
* Initialize with kmeans++
|
||||||
*
|
*
|
||||||
@@ -155,23 +151,6 @@ QuickCenters(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef IVFFLAT_MEMORY
|
|
||||||
/*
|
|
||||||
* Show memory usage
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
ShowMemoryUsage(Size estimatedSize)
|
|
||||||
{
|
|
||||||
#if PG_VERSION_NUM >= 130000
|
|
||||||
elog(INFO, "total memory: %zu MB",
|
|
||||||
MemoryContextMemAllocated(CurrentMemoryContext, true) / (1024 * 1024));
|
|
||||||
#else
|
|
||||||
MemoryContextStats(CurrentMemoryContext);
|
|
||||||
#endif
|
|
||||||
elog(INFO, "estimated memory: %zu MB", estimatedSize / (1024 * 1024));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Use Elkan for performance. This requires distance function to satisfy triangle inequality.
|
* Use Elkan for performance. This requires distance function to satisfy triangle inequality.
|
||||||
*
|
*
|
||||||
@@ -252,10 +231,6 @@ ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
|||||||
vec->dim = dimensions;
|
vec->dim = dimensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef IVFFLAT_MEMORY
|
|
||||||
ShowMemoryUsage(totalSize);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Pick initial centers */
|
/* Pick initial centers */
|
||||||
InitCenters(index, samples, centers, lowerBound);
|
InitCenters(index, samples, centers, lowerBound);
|
||||||
|
|
||||||
|
|||||||
30
src/vector.c
30
src/vector.c
@@ -724,7 +724,7 @@ vector_spherical_distance(PG_FUNCTION_ARGS)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the L1 distance between two vectors
|
* Get the L1 distance between vectors
|
||||||
*/
|
*/
|
||||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(l1_distance);
|
PGDLLEXPORT PG_FUNCTION_INFO_V1(l1_distance);
|
||||||
Datum
|
Datum
|
||||||
@@ -903,8 +903,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_lt);
|
|||||||
Datum
|
Datum
|
||||||
vector_lt(PG_FUNCTION_ARGS)
|
vector_lt(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
||||||
|
|
||||||
PG_RETURN_BOOL(vector_cmp_internal(a, b) < 0);
|
PG_RETURN_BOOL(vector_cmp_internal(a, b) < 0);
|
||||||
}
|
}
|
||||||
@@ -916,8 +916,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_le);
|
|||||||
Datum
|
Datum
|
||||||
vector_le(PG_FUNCTION_ARGS)
|
vector_le(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
||||||
|
|
||||||
PG_RETURN_BOOL(vector_cmp_internal(a, b) <= 0);
|
PG_RETURN_BOOL(vector_cmp_internal(a, b) <= 0);
|
||||||
}
|
}
|
||||||
@@ -929,8 +929,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_eq);
|
|||||||
Datum
|
Datum
|
||||||
vector_eq(PG_FUNCTION_ARGS)
|
vector_eq(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
||||||
|
|
||||||
PG_RETURN_BOOL(vector_cmp_internal(a, b) == 0);
|
PG_RETURN_BOOL(vector_cmp_internal(a, b) == 0);
|
||||||
}
|
}
|
||||||
@@ -942,8 +942,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_ne);
|
|||||||
Datum
|
Datum
|
||||||
vector_ne(PG_FUNCTION_ARGS)
|
vector_ne(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
||||||
|
|
||||||
PG_RETURN_BOOL(vector_cmp_internal(a, b) != 0);
|
PG_RETURN_BOOL(vector_cmp_internal(a, b) != 0);
|
||||||
}
|
}
|
||||||
@@ -955,8 +955,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_ge);
|
|||||||
Datum
|
Datum
|
||||||
vector_ge(PG_FUNCTION_ARGS)
|
vector_ge(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
||||||
|
|
||||||
PG_RETURN_BOOL(vector_cmp_internal(a, b) >= 0);
|
PG_RETURN_BOOL(vector_cmp_internal(a, b) >= 0);
|
||||||
}
|
}
|
||||||
@@ -968,8 +968,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_gt);
|
|||||||
Datum
|
Datum
|
||||||
vector_gt(PG_FUNCTION_ARGS)
|
vector_gt(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
||||||
|
|
||||||
PG_RETURN_BOOL(vector_cmp_internal(a, b) > 0);
|
PG_RETURN_BOOL(vector_cmp_internal(a, b) > 0);
|
||||||
}
|
}
|
||||||
@@ -981,8 +981,8 @@ PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_cmp);
|
|||||||
Datum
|
Datum
|
||||||
vector_cmp(PG_FUNCTION_ARGS)
|
vector_cmp(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
Vector *a = (Vector *) PG_GETARG_VECTOR_P(0);
|
||||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
Vector *b = (Vector *) PG_GETARG_VECTOR_P(1);
|
||||||
|
|
||||||
PG_RETURN_INT32(vector_cmp_internal(a, b));
|
PG_RETURN_INT32(vector_cmp_internal(a, b));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
CREATE TABLE t (val vector(3));
|
CREATE TABLE t (val vector(3), val2 half[]);
|
||||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
INSERT INTO t (val, val2) VALUES ('[0,0,0]', '{0,0,0}'), ('[1,2,3]', '{1,2,3}'), ('[1,1,1]', '{1,1,1}'), (NULL, NULL);
|
||||||
CREATE TABLE t2 (val vector(3));
|
CREATE TABLE t2 (val vector(3), val2 half[]);
|
||||||
\copy t TO 'results/data.bin' WITH (FORMAT binary)
|
\copy t TO 'results/data.bin' WITH (FORMAT binary)
|
||||||
\copy t2 FROM 'results/data.bin' WITH (FORMAT binary)
|
\copy t2 FROM 'results/data.bin' WITH (FORMAT binary)
|
||||||
SELECT * FROM t2 ORDER BY val;
|
SELECT * FROM t2 ORDER BY val;
|
||||||
val
|
val | val2
|
||||||
---------
|
---------+---------
|
||||||
[0,0,0]
|
[0,0,0] | {0,0,0}
|
||||||
[1,1,1]
|
[1,1,1] | {1,1,1}
|
||||||
[1,2,3]
|
[1,2,3] | {1,2,3}
|
||||||
|
|
|
||||||
(4 rows)
|
(4 rows)
|
||||||
|
|
||||||
DROP TABLE t;
|
DROP TABLE t;
|
||||||
|
|||||||
@@ -54,105 +54,105 @@ SELECT vector_norm('[3e37,4e37]')::real;
|
|||||||
5e+37
|
5e+37
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT l2_distance('[0,0]', '[3,4]');
|
SELECT l2_distance('[0,0]'::vector, '[3,4]');
|
||||||
l2_distance
|
l2_distance
|
||||||
-------------
|
-------------
|
||||||
5
|
5
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT l2_distance('[0,0]', '[0,1]');
|
SELECT l2_distance('[0,0]'::vector, '[0,1]');
|
||||||
l2_distance
|
l2_distance
|
||||||
-------------
|
-------------
|
||||||
1
|
1
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT l2_distance('[1,2]', '[3]');
|
SELECT l2_distance('[1,2]'::vector, '[3]');
|
||||||
ERROR: different vector dimensions 2 and 1
|
ERROR: different vector dimensions 2 and 1
|
||||||
SELECT l2_distance('[3e38]', '[-3e38]');
|
SELECT l2_distance('[3e38]'::vector, '[-3e38]');
|
||||||
l2_distance
|
l2_distance
|
||||||
-------------
|
-------------
|
||||||
Infinity
|
Infinity
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT inner_product('[1,2]', '[3,4]');
|
SELECT inner_product('[1,2]'::vector, '[3,4]');
|
||||||
inner_product
|
inner_product
|
||||||
---------------
|
---------------
|
||||||
11
|
11
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT inner_product('[1,2]', '[3]');
|
SELECT inner_product('[1,2]'::vector, '[3]');
|
||||||
ERROR: different vector dimensions 2 and 1
|
ERROR: different vector dimensions 2 and 1
|
||||||
SELECT inner_product('[3e38]', '[3e38]');
|
SELECT inner_product('[3e38]'::vector, '[3e38]');
|
||||||
inner_product
|
inner_product
|
||||||
---------------
|
---------------
|
||||||
Infinity
|
Infinity
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,2]', '[2,4]');
|
SELECT cosine_distance('[1,2]'::vector, '[2,4]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
0
|
0
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,2]', '[0,0]');
|
SELECT cosine_distance('[1,2]'::vector, '[0,0]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
NaN
|
NaN
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,1]', '[1,1]');
|
SELECT cosine_distance('[1,1]'::vector, '[1,1]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
0
|
0
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,0]', '[0,2]');
|
SELECT cosine_distance('[1,0]'::vector, '[0,2]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
1
|
1
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,1]', '[-1,-1]');
|
SELECT cosine_distance('[1,1]'::vector, '[-1,-1]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
2
|
2
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,2]', '[3]');
|
SELECT cosine_distance('[1,2]'::vector, '[3]');
|
||||||
ERROR: different vector dimensions 2 and 1
|
ERROR: different vector dimensions 2 and 1
|
||||||
SELECT cosine_distance('[1,1]', '[1.1,1.1]');
|
SELECT cosine_distance('[1,1]'::vector, '[1.1,1.1]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
0
|
0
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[1,1]', '[-1.1,-1.1]');
|
SELECT cosine_distance('[1,1]'::vector, '[-1.1,-1.1]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
2
|
2
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT cosine_distance('[3e38]', '[3e38]');
|
SELECT cosine_distance('[3e38]'::vector, '[3e38]');
|
||||||
cosine_distance
|
cosine_distance
|
||||||
-----------------
|
-----------------
|
||||||
NaN
|
NaN
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT l1_distance('[0,0]', '[3,4]');
|
SELECT l1_distance('[0,0]'::vector, '[3,4]');
|
||||||
l1_distance
|
l1_distance
|
||||||
-------------
|
-------------
|
||||||
7
|
7
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT l1_distance('[0,0]', '[0,1]');
|
SELECT l1_distance('[0,0]'::vector, '[0,1]');
|
||||||
l1_distance
|
l1_distance
|
||||||
-------------
|
-------------
|
||||||
1
|
1
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
SELECT l1_distance('[1,2]', '[3]');
|
SELECT l1_distance('[1,2]'::vector, '[3]');
|
||||||
ERROR: different vector dimensions 2 and 1
|
ERROR: different vector dimensions 2 and 1
|
||||||
SELECT l1_distance('[3e38]', '[-3e38]');
|
SELECT l1_distance('[3e38]'::vector, '[-3e38]');
|
||||||
l1_distance
|
l1_distance
|
||||||
-------------
|
-------------
|
||||||
Infinity
|
Infinity
|
||||||
|
|||||||
226
test/expected/half.out
Normal file
226
test/expected/half.out
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
SELECT '1.5'::half;
|
||||||
|
half
|
||||||
|
------
|
||||||
|
1.5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '65504'::half;
|
||||||
|
half
|
||||||
|
-------
|
||||||
|
65504
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '65505'::half;
|
||||||
|
ERROR: "65505" is out of range for type half
|
||||||
|
LINE 1: SELECT '65505'::half;
|
||||||
|
^
|
||||||
|
SELECT '-65504'::half;
|
||||||
|
half
|
||||||
|
--------
|
||||||
|
-65504
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '-65505'::half;
|
||||||
|
ERROR: "-65505" is out of range for type half
|
||||||
|
LINE 1: SELECT '-65505'::half;
|
||||||
|
^
|
||||||
|
SELECT ''::half;
|
||||||
|
ERROR: invalid input syntax for type half: ""
|
||||||
|
LINE 1: SELECT ''::half;
|
||||||
|
^
|
||||||
|
SELECT ' '::half;
|
||||||
|
ERROR: invalid input syntax for type half: " "
|
||||||
|
LINE 1: SELECT ' '::half;
|
||||||
|
^
|
||||||
|
SELECT '-'::half;
|
||||||
|
ERROR: invalid input syntax for type half: "-"
|
||||||
|
LINE 1: SELECT '-'::half;
|
||||||
|
^
|
||||||
|
SELECT ' 1.5'::half;
|
||||||
|
half
|
||||||
|
------
|
||||||
|
1.5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '1.5 '::half;
|
||||||
|
half
|
||||||
|
------
|
||||||
|
1.5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '1.5a'::half;
|
||||||
|
ERROR: invalid input syntax for type half: "1.5a"
|
||||||
|
LINE 1: SELECT '1.5a'::half;
|
||||||
|
^
|
||||||
|
SELECT '{1,2,3}'::half[];
|
||||||
|
half
|
||||||
|
---------
|
||||||
|
{1,2,3}
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '{1,2,3}'::half[]::real[];
|
||||||
|
float4
|
||||||
|
---------
|
||||||
|
{1,2,3}
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '65505'::integer::half;
|
||||||
|
half
|
||||||
|
-------
|
||||||
|
65504
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT 'NaN'::real::half;
|
||||||
|
half
|
||||||
|
------
|
||||||
|
NaN
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT 'Infinity'::real::half;
|
||||||
|
half
|
||||||
|
----------
|
||||||
|
Infinity
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '1e-38'::real::half;
|
||||||
|
ERROR: value out of range: underflow
|
||||||
|
SELECT '1.5'::half::real;
|
||||||
|
float4
|
||||||
|
--------
|
||||||
|
1.5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '1.5'::real::half;
|
||||||
|
half
|
||||||
|
------
|
||||||
|
1.5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '1.5'::half::double precision;
|
||||||
|
float8
|
||||||
|
--------
|
||||||
|
1.5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '1.5'::double precision::half;
|
||||||
|
half
|
||||||
|
------
|
||||||
|
1.5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '1.5'::half::numeric;
|
||||||
|
numeric
|
||||||
|
---------
|
||||||
|
1.5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '1.5'::numeric::half;
|
||||||
|
half
|
||||||
|
------
|
||||||
|
1.5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT l2_distance('{0,0}'::half[], '{3,4}'::half[]);
|
||||||
|
l2_distance
|
||||||
|
-------------
|
||||||
|
5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT l2_distance('{0,0}'::half[], '{0,1}'::half[]);
|
||||||
|
l2_distance
|
||||||
|
-------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT l2_distance('{1,2}'::half[], '{3}'::half[]);
|
||||||
|
ERROR: different dimensions 2 and 1
|
||||||
|
SELECT '{0,0}'::half[] <-> '{3,4}'::half[];
|
||||||
|
?column?
|
||||||
|
----------
|
||||||
|
5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT inner_product('{1,2}'::half[], '{3,4}'::half[]);
|
||||||
|
inner_product
|
||||||
|
---------------
|
||||||
|
11
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT inner_product('{1,2}'::half[], '{3}'::half[]);
|
||||||
|
ERROR: different dimensions 2 and 1
|
||||||
|
SELECT inner_product('{65504}'::half[], '{65504}'::half[]);
|
||||||
|
inner_product
|
||||||
|
---------------
|
||||||
|
4290774016
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '{1,2}'::half[] <#> '{3,4}'::half[];
|
||||||
|
?column?
|
||||||
|
----------
|
||||||
|
-11
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT cosine_distance('{1,2}'::half[], '{2,4}'::half[]);
|
||||||
|
cosine_distance
|
||||||
|
-----------------
|
||||||
|
0
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT cosine_distance('{1,2}'::half[], '{0,0}'::half[]);
|
||||||
|
cosine_distance
|
||||||
|
-----------------
|
||||||
|
NaN
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT cosine_distance('{1,1}'::half[], '{1,1}'::half[]);
|
||||||
|
cosine_distance
|
||||||
|
-----------------
|
||||||
|
0
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT cosine_distance('{1,0}'::half[], '{0,2}'::half[]);
|
||||||
|
cosine_distance
|
||||||
|
-----------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT cosine_distance('{1,1}'::half[], '{-1,-1}'::half[]);
|
||||||
|
cosine_distance
|
||||||
|
-----------------
|
||||||
|
2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT cosine_distance('{1,2}'::half[], '{3}'::half[]);
|
||||||
|
ERROR: different dimensions 2 and 1
|
||||||
|
SELECT cosine_distance('{1,1}'::half[], '{1.1,1.1}'::half[]);
|
||||||
|
cosine_distance
|
||||||
|
-----------------
|
||||||
|
0
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT cosine_distance('{1,1}'::half[], '{-1.1,-1.1}'::half[]);
|
||||||
|
cosine_distance
|
||||||
|
-----------------
|
||||||
|
2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT '{1,2}'::half[] <=> '{2,4}'::half[];
|
||||||
|
?column?
|
||||||
|
----------
|
||||||
|
0
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT l1_distance('{0,0}'::half[], '{3,4}');
|
||||||
|
l1_distance
|
||||||
|
-------------
|
||||||
|
7
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT l1_distance('{0,0}'::half[], '{0,1}');
|
||||||
|
l1_distance
|
||||||
|
-------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT l1_distance('{1,2}'::half[], '{3}');
|
||||||
|
ERROR: different dimensions 2 and 1
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
CREATE TABLE t (val vector(3));
|
CREATE TABLE t (val vector(3), val2 half[]);
|
||||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
INSERT INTO t (val, val2) VALUES ('[0,0,0]', '{0,0,0}'), ('[1,2,3]', '{1,2,3}'), ('[1,1,1]', '{1,1,1}'), (NULL, NULL);
|
||||||
|
|
||||||
CREATE TABLE t2 (val vector(3));
|
CREATE TABLE t2 (val vector(3), val2 half[]);
|
||||||
|
|
||||||
\copy t TO 'results/data.bin' WITH (FORMAT binary)
|
\copy t TO 'results/data.bin' WITH (FORMAT binary)
|
||||||
\copy t2 FROM 'results/data.bin' WITH (FORMAT binary)
|
\copy t2 FROM 'results/data.bin' WITH (FORMAT binary)
|
||||||
|
|||||||
@@ -13,29 +13,29 @@ SELECT vector_norm('[3,4]');
|
|||||||
SELECT vector_norm('[0,1]');
|
SELECT vector_norm('[0,1]');
|
||||||
SELECT vector_norm('[3e37,4e37]')::real;
|
SELECT vector_norm('[3e37,4e37]')::real;
|
||||||
|
|
||||||
SELECT l2_distance('[0,0]', '[3,4]');
|
SELECT l2_distance('[0,0]'::vector, '[3,4]');
|
||||||
SELECT l2_distance('[0,0]', '[0,1]');
|
SELECT l2_distance('[0,0]'::vector, '[0,1]');
|
||||||
SELECT l2_distance('[1,2]', '[3]');
|
SELECT l2_distance('[1,2]'::vector, '[3]');
|
||||||
SELECT l2_distance('[3e38]', '[-3e38]');
|
SELECT l2_distance('[3e38]'::vector, '[-3e38]');
|
||||||
|
|
||||||
SELECT inner_product('[1,2]', '[3,4]');
|
SELECT inner_product('[1,2]'::vector, '[3,4]');
|
||||||
SELECT inner_product('[1,2]', '[3]');
|
SELECT inner_product('[1,2]'::vector, '[3]');
|
||||||
SELECT inner_product('[3e38]', '[3e38]');
|
SELECT inner_product('[3e38]'::vector, '[3e38]');
|
||||||
|
|
||||||
SELECT cosine_distance('[1,2]', '[2,4]');
|
SELECT cosine_distance('[1,2]'::vector, '[2,4]');
|
||||||
SELECT cosine_distance('[1,2]', '[0,0]');
|
SELECT cosine_distance('[1,2]'::vector, '[0,0]');
|
||||||
SELECT cosine_distance('[1,1]', '[1,1]');
|
SELECT cosine_distance('[1,1]'::vector, '[1,1]');
|
||||||
SELECT cosine_distance('[1,0]', '[0,2]');
|
SELECT cosine_distance('[1,0]'::vector, '[0,2]');
|
||||||
SELECT cosine_distance('[1,1]', '[-1,-1]');
|
SELECT cosine_distance('[1,1]'::vector, '[-1,-1]');
|
||||||
SELECT cosine_distance('[1,2]', '[3]');
|
SELECT cosine_distance('[1,2]'::vector, '[3]');
|
||||||
SELECT cosine_distance('[1,1]', '[1.1,1.1]');
|
SELECT cosine_distance('[1,1]'::vector, '[1.1,1.1]');
|
||||||
SELECT cosine_distance('[1,1]', '[-1.1,-1.1]');
|
SELECT cosine_distance('[1,1]'::vector, '[-1.1,-1.1]');
|
||||||
SELECT cosine_distance('[3e38]', '[3e38]');
|
SELECT cosine_distance('[3e38]'::vector, '[3e38]');
|
||||||
|
|
||||||
SELECT l1_distance('[0,0]', '[3,4]');
|
SELECT l1_distance('[0,0]'::vector, '[3,4]');
|
||||||
SELECT l1_distance('[0,0]', '[0,1]');
|
SELECT l1_distance('[0,0]'::vector, '[0,1]');
|
||||||
SELECT l1_distance('[1,2]', '[3]');
|
SELECT l1_distance('[1,2]'::vector, '[3]');
|
||||||
SELECT l1_distance('[3e38]', '[-3e38]');
|
SELECT l1_distance('[3e38]'::vector, '[-3e38]');
|
||||||
|
|
||||||
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v;
|
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]']) v;
|
||||||
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]', NULL]) v;
|
SELECT avg(v) FROM unnest(ARRAY['[1,2,3]'::vector, '[3,5,7]', NULL]) v;
|
||||||
|
|||||||
53
test/sql/half.sql
Normal file
53
test/sql/half.sql
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
SELECT '1.5'::half;
|
||||||
|
SELECT '65504'::half;
|
||||||
|
SELECT '65505'::half;
|
||||||
|
SELECT '-65504'::half;
|
||||||
|
SELECT '-65505'::half;
|
||||||
|
|
||||||
|
SELECT ''::half;
|
||||||
|
SELECT ' '::half;
|
||||||
|
SELECT '-'::half;
|
||||||
|
SELECT ' 1.5'::half;
|
||||||
|
SELECT '1.5 '::half;
|
||||||
|
SELECT '1.5a'::half;
|
||||||
|
|
||||||
|
SELECT '{1,2,3}'::half[];
|
||||||
|
SELECT '{1,2,3}'::half[]::real[];
|
||||||
|
|
||||||
|
SELECT '65505'::integer::half;
|
||||||
|
SELECT 'NaN'::real::half;
|
||||||
|
SELECT 'Infinity'::real::half;
|
||||||
|
SELECT '1e-38'::real::half;
|
||||||
|
|
||||||
|
SELECT '1.5'::half::real;
|
||||||
|
SELECT '1.5'::real::half;
|
||||||
|
|
||||||
|
SELECT '1.5'::half::double precision;
|
||||||
|
SELECT '1.5'::double precision::half;
|
||||||
|
|
||||||
|
SELECT '1.5'::half::numeric;
|
||||||
|
SELECT '1.5'::numeric::half;
|
||||||
|
|
||||||
|
SELECT l2_distance('{0,0}'::half[], '{3,4}'::half[]);
|
||||||
|
SELECT l2_distance('{0,0}'::half[], '{0,1}'::half[]);
|
||||||
|
SELECT l2_distance('{1,2}'::half[], '{3}'::half[]);
|
||||||
|
SELECT '{0,0}'::half[] <-> '{3,4}'::half[];
|
||||||
|
|
||||||
|
SELECT inner_product('{1,2}'::half[], '{3,4}'::half[]);
|
||||||
|
SELECT inner_product('{1,2}'::half[], '{3}'::half[]);
|
||||||
|
SELECT inner_product('{65504}'::half[], '{65504}'::half[]);
|
||||||
|
SELECT '{1,2}'::half[] <#> '{3,4}'::half[];
|
||||||
|
|
||||||
|
SELECT cosine_distance('{1,2}'::half[], '{2,4}'::half[]);
|
||||||
|
SELECT cosine_distance('{1,2}'::half[], '{0,0}'::half[]);
|
||||||
|
SELECT cosine_distance('{1,1}'::half[], '{1,1}'::half[]);
|
||||||
|
SELECT cosine_distance('{1,0}'::half[], '{0,2}'::half[]);
|
||||||
|
SELECT cosine_distance('{1,1}'::half[], '{-1,-1}'::half[]);
|
||||||
|
SELECT cosine_distance('{1,2}'::half[], '{3}'::half[]);
|
||||||
|
SELECT cosine_distance('{1,1}'::half[], '{1.1,1.1}'::half[]);
|
||||||
|
SELECT cosine_distance('{1,1}'::half[], '{-1.1,-1.1}'::half[]);
|
||||||
|
SELECT '{1,2}'::half[] <=> '{2,4}'::half[];
|
||||||
|
|
||||||
|
SELECT l1_distance('{0,0}'::half[], '{3,4}');
|
||||||
|
SELECT l1_distance('{0,0}'::half[], '{0,1}');
|
||||||
|
SELECT l1_distance('{1,2}'::half[], '{3}');
|
||||||
@@ -95,10 +95,11 @@ for my $i (0 .. $#operators)
|
|||||||
|
|
||||||
$node->safe_psql("postgres", "DROP INDEX idx;");
|
$node->safe_psql("postgres", "DROP INDEX idx;");
|
||||||
|
|
||||||
# Build index in parallel in memory
|
# Build index in parallel
|
||||||
my ($ret, $stdout, $stderr) = $node->psql("postgres", qq(
|
my ($ret, $stdout, $stderr) = $node->psql("postgres", qq(
|
||||||
SET client_min_messages = DEBUG;
|
SET client_min_messages = DEBUG;
|
||||||
SET min_parallel_table_scan_size = 1;
|
SET min_parallel_table_scan_size = 1;
|
||||||
|
SET hnsw.enable_parallel_build = on;
|
||||||
CREATE INDEX idx ON tst USING hnsw (v $opclass);
|
CREATE INDEX idx ON tst USING hnsw (v $opclass);
|
||||||
));
|
));
|
||||||
is($ret, 0, $stderr);
|
is($ret, 0, $stderr);
|
||||||
@@ -108,21 +109,6 @@ for my $i (0 .. $#operators)
|
|||||||
test_recall($min, $operator);
|
test_recall($min, $operator);
|
||||||
|
|
||||||
$node->safe_psql("postgres", "DROP INDEX idx;");
|
$node->safe_psql("postgres", "DROP INDEX idx;");
|
||||||
|
|
||||||
# Build index in parallel on disk
|
|
||||||
# Set parallel_workers on table to use workers with low maintenance_work_mem
|
|
||||||
($ret, $stdout, $stderr) = $node->psql("postgres", qq(
|
|
||||||
ALTER TABLE tst SET (parallel_workers = 2);
|
|
||||||
SET client_min_messages = DEBUG;
|
|
||||||
SET maintenance_work_mem = '4MB';
|
|
||||||
CREATE INDEX idx ON tst USING hnsw (v $opclass);
|
|
||||||
ALTER TABLE tst RESET (parallel_workers);
|
|
||||||
));
|
|
||||||
is($ret, 0, $stderr);
|
|
||||||
like($stderr, qr/using \d+ parallel workers/);
|
|
||||||
like($stderr, qr/hnsw graph no longer fits into maintenance_work_mem/);
|
|
||||||
|
|
||||||
$node->safe_psql("postgres", "DROP INDEX idx;");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
done_testing();
|
done_testing();
|
||||||
|
|||||||
Reference in New Issue
Block a user