mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 20:15:46 +08:00
Compare commits
10 Commits
windows-pr
...
minibatch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9852351746 | ||
|
|
50349ed4f5 | ||
|
|
2ee510aa67 | ||
|
|
cad655b77f | ||
|
|
21ca5d3845 | ||
|
|
8374498e6c | ||
|
|
c1d6b9b41b | ||
|
|
a77340d40b | ||
|
|
81b68fbf5b | ||
|
|
8ee6d0e596 |
@@ -1,6 +1,6 @@
|
||||
root = true
|
||||
|
||||
[*.{c,h,pl,pm}]
|
||||
[*.{c,h,pl}]
|
||||
indent_style = tab
|
||||
indent_size = tab
|
||||
tab_width = 4
|
||||
|
||||
89
.github/workflows/build.yml
vendored
89
.github/workflows/build.yml
vendored
@@ -1,65 +1,38 @@
|
||||
name: build
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !startsWith(github.ref_name, 'windows') }}
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
postgres: [15, 14, 13, 12, 11, 10]
|
||||
os: [ubuntu-latest]
|
||||
postgres: [14, 13, 12, 11, 10, 9.6]
|
||||
include:
|
||||
- os: macos-latest
|
||||
postgres: 14
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ankane/setup-postgres@v1
|
||||
with:
|
||||
postgres-version: ${{ matrix.postgres }}
|
||||
dev-files: true
|
||||
- run: make
|
||||
- run: |
|
||||
export PG_CONFIG=`which pg_config`
|
||||
sudo --preserve-env=PG_CONFIG make install
|
||||
- run: make installcheck
|
||||
- if: ${{ failure() }}
|
||||
run: cat regression.diffs
|
||||
- run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install libipc-run-perl
|
||||
make prove_installcheck
|
||||
mac:
|
||||
runs-on: macos-latest
|
||||
if: ${{ !startsWith(github.ref_name, 'windows') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ankane/setup-postgres@v1
|
||||
with:
|
||||
postgres-version: 14
|
||||
- run: make
|
||||
- run: make install
|
||||
- run: make installcheck
|
||||
- if: ${{ failure() }}
|
||||
run: cat regression.diffs
|
||||
- run: |
|
||||
brew install cpanm
|
||||
cpanm IPC::Run
|
||||
wget -q https://github.com/postgres/postgres/archive/refs/tags/REL_14_5.tar.gz
|
||||
tar xf REL_14_5.tar.gz
|
||||
make prove_installcheck PROVE=prove PROVE_FLAGS="-I ./postgres-REL_14_5/src/test/perl" PERL5LIB="/Users/runner/perl5/lib/perl5"
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ankane/setup-postgres@v1
|
||||
with:
|
||||
postgres-version: 14
|
||||
- run: |
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
|
||||
nmake /NOLOGO /F Makefile.win
|
||||
nmake /NOLOGO /F Makefile.win install
|
||||
curl -Ls -o REL_14_5.tar.gz https://github.com/postgres/postgres/archive/refs/tags/REL_14_5.tar.gz
|
||||
7z x REL_14_5.tar.gz
|
||||
7z x REL_14_5.tar
|
||||
ls ./postgres-REL_14_5/src/test/perl
|
||||
set PROVE=prove
|
||||
set PROVE_FLAGS="-I ./postgres-REL_14_5/src/test/perl"
|
||||
nmake /NOLOGO /F Makefile.win prove_installcheck
|
||||
shell: cmd
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ankane/setup-postgres@v1
|
||||
with:
|
||||
postgres-version: ${{ matrix.postgres }}
|
||||
- if: ${{ startsWith(matrix.os, 'ubuntu') }}
|
||||
run: sudo apt-get update && sudo apt-get install postgresql-server-dev-${{ matrix.postgres }} libipc-run-perl
|
||||
- run: make
|
||||
- if: ${{ startsWith(matrix.os, 'ubuntu') }}
|
||||
run: |
|
||||
export PG_CONFIG=`which pg_config`
|
||||
sudo --preserve-env=PG_CONFIG make install
|
||||
- if: ${{ startsWith(matrix.os, 'macos') }}
|
||||
run: make install
|
||||
- run: make installcheck
|
||||
- if: ${{ failure() }}
|
||||
run: cat regression.diffs
|
||||
- if: ${{ startsWith(matrix.os, 'ubuntu') }}
|
||||
run: make prove_installcheck
|
||||
- if: ${{ startsWith(matrix.os, 'macos') }}
|
||||
run: |
|
||||
brew install cpanm && cpanm IPC::Run
|
||||
wget -q https://github.com/postgres/postgres/archive/refs/tags/REL_14_1.tar.gz
|
||||
tar xf REL_14_1.tar.gz
|
||||
make prove_installcheck PROVE=prove PERL5LIB=postgres-REL_14_1/src/test/perl
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,4 +5,3 @@
|
||||
regression.*
|
||||
*.o
|
||||
*.so
|
||||
*.bc
|
||||
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,31 +1,6 @@
|
||||
## 0.4.0 (unreleased)
|
||||
|
||||
- Changed text representation for vector elements to match `real`
|
||||
- Improved accuracy of text parsing for certain inputs
|
||||
- Added experimental support for Windows
|
||||
|
||||
## 0.3.2 (2022-11-22)
|
||||
|
||||
- Fixed `invalid memory alloc request size` error
|
||||
|
||||
## 0.3.1 (2022-11-02)
|
||||
|
||||
If upgrading from 0.2.7 or 0.3.0, [recreate](https://github.com/pgvector/pgvector#031) all `ivfflat` indexes after upgrading to ensure all data is indexed.
|
||||
|
||||
- Fixed issue with inserts silently corrupting `ivfflat` indexes (introduced in 0.2.7)
|
||||
- Fixed segmentation fault with index creation when lists > 6500
|
||||
|
||||
## 0.3.0 (2022-10-15)
|
||||
|
||||
- Added support for Postgres 15
|
||||
- Dropped support for Postgres 9.6
|
||||
|
||||
## 0.2.7 (2022-07-31)
|
||||
|
||||
- Fixed `unexpected data beyond EOF` error
|
||||
|
||||
## 0.2.6 (2022-05-22)
|
||||
## 0.2.6 (unreleased)
|
||||
|
||||
- Switched to mini-batch k-means
|
||||
- Improved performance of index creation for Postgres < 12
|
||||
|
||||
## 0.2.5 (2022-02-11)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
FROM postgres:15
|
||||
FROM postgres:14
|
||||
|
||||
COPY . /tmp/pgvector
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential postgresql-server-dev-15 && \
|
||||
apt-get install -y --no-install-recommends build-essential postgresql-server-dev-14 && \
|
||||
cd /tmp/pgvector && \
|
||||
make clean && \
|
||||
make OPTFLAGS="" && \
|
||||
@@ -11,6 +11,6 @@ RUN apt-get update && \
|
||||
mkdir /usr/share/doc/pgvector && \
|
||||
cp LICENSE README.md /usr/share/doc/pgvector && \
|
||||
rm -r /tmp/pgvector && \
|
||||
apt-get remove -y build-essential postgresql-server-dev-15 && \
|
||||
apt-get remove -y build-essential postgresql-server-dev-14 && \
|
||||
apt-get autoremove -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "vector",
|
||||
"abstract": "Open-source vector similarity search for Postgres",
|
||||
"description": "Supports L2 distance, inner product, and cosine distance",
|
||||
"version": "0.3.2",
|
||||
"version": "0.2.5",
|
||||
"maintainer": [
|
||||
"Andrew Kane <andrew@ankane.org>"
|
||||
],
|
||||
@@ -12,7 +12,7 @@
|
||||
"prereqs": {
|
||||
"runtime": {
|
||||
"requires": {
|
||||
"PostgreSQL": "10.0.0"
|
||||
"PostgreSQL": "9.6.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -20,7 +20,7 @@
|
||||
"vector": {
|
||||
"file": "sql/vector.sql",
|
||||
"docfile": "README.md",
|
||||
"version": "0.3.2",
|
||||
"version": "0.2.5",
|
||||
"abstract": "Open-source vector similarity search for Postgres"
|
||||
}
|
||||
},
|
||||
|
||||
7
Makefile
7
Makefile
@@ -1,5 +1,5 @@
|
||||
EXTENSION = vector
|
||||
EXTVERSION = 0.3.2
|
||||
EXTVERSION = 0.2.5
|
||||
|
||||
MODULE_big = vector
|
||||
DATA = $(wildcard sql/*--*.sql)
|
||||
@@ -7,7 +7,7 @@ OBJS = src/ivfbuild.o src/ivfflat.o src/ivfinsert.o src/ivfkmeans.o src/ivfscan.
|
||||
|
||||
TESTS = $(wildcard test/sql/*.sql)
|
||||
REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS))
|
||||
REGRESS_OPTS = --inputdir=test --load-extension=vector
|
||||
REGRESS_OPTS = --inputdir=test
|
||||
|
||||
OPTFLAGS = -march=native
|
||||
|
||||
@@ -40,9 +40,6 @@ PG_CONFIG ?= pg_config
|
||||
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||
include $(PGXS)
|
||||
|
||||
# for Postgres 15
|
||||
PROVE_FLAGS += -I ./test/perl
|
||||
|
||||
prove_installcheck:
|
||||
rm -rf $(CURDIR)/tmp_check
|
||||
cd $(srcdir) && TESTDIR='$(CURDIR)' PATH="$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' PG_REGRESS='$(top_builddir)/src/test/regress/pg_regress' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),test/t/*.pl)
|
||||
|
||||
62
Makefile.win
62
Makefile.win
@@ -1,62 +0,0 @@
|
||||
EXTENSION = vector
|
||||
EXTVERSION = 0.3.2
|
||||
|
||||
OBJS = src\ivfbuild.obj src\ivfflat.obj src\ivfinsert.obj src\ivfkmeans.obj src\ivfscan.obj src\ivfutils.obj src\ivfvacuum.obj src\vector.obj
|
||||
|
||||
REGRESS = btree cast copy functions input ivfflat_cosine ivfflat_ip ivfflat_l2 ivfflat_options ivfflat_unlogged
|
||||
REGRESS_OPTS = --inputdir=test --load-extension=vector
|
||||
|
||||
# For /arch flags
|
||||
# https://learn.microsoft.com/en-us/cpp/build/reference/arch-minimum-cpu-architecture
|
||||
OPTFLAGS =
|
||||
|
||||
# For auto-vectorization:
|
||||
# - MSVC (needs /O2 /fp:fast) - https://learn.microsoft.com/en-us/cpp/parallel/auto-parallelization-and-auto-vectorization?#auto-vectorizer
|
||||
PG_CFLAGS = $(PG_CFLAGS) $(OPTFLAGS) /O2 /fp:fast
|
||||
|
||||
# Debug MSVC auto-vectorization
|
||||
# https://learn.microsoft.com/en-us/cpp/error-messages/tool-errors/vectorizer-and-parallelizer-messages
|
||||
# PG_CFLAGS = $(PG_CFLAGS) /Qvec-report:2
|
||||
|
||||
all: sql\$(EXTENSION)--$(EXTVERSION).sql
|
||||
|
||||
sql\$(EXTENSION)--$(EXTVERSION).sql: sql\$(EXTENSION).sql
|
||||
copy sql\$(EXTENSION).sql $@
|
||||
|
||||
# TODO use pg_config
|
||||
BINDIR = $(PGROOT)\bin
|
||||
INCLUDEDIR = $(PGROOT)\include
|
||||
INCLUDEDIR_SERVER = $(PGROOT)\include\server
|
||||
LIBDIR = $(PGROOT)\lib
|
||||
PKGLIBDIR = $(PGROOT)\lib
|
||||
SHAREDIR = $(PGROOT)\share
|
||||
|
||||
CFLAGS = /nologo /I"$(INCLUDEDIR_SERVER)\port\win32_msvc" /I"$(INCLUDEDIR_SERVER)\port\win32" /I"$(INCLUDEDIR_SERVER)" /I"$(INCLUDEDIR)"
|
||||
|
||||
CFLAGS = $(CFLAGS) $(PG_CFLAGS)
|
||||
|
||||
SHLIB = src\$(EXTENSION).dll
|
||||
|
||||
LIBS = "$(LIBDIR)\postgres.lib"
|
||||
|
||||
.c.obj:
|
||||
$(CC) $(CFLAGS) /c $< /Fo$@
|
||||
|
||||
$(SHLIB): $(OBJS)
|
||||
$(CC) $(CFLAGS) $(OBJS) $(LIBS) /link /DLL /OUT:$(SHLIB)
|
||||
|
||||
all: $(SHLIB)
|
||||
|
||||
install:
|
||||
copy $(SHLIB) "$(PKGLIBDIR)"
|
||||
copy $(EXTENSION).control "$(SHAREDIR)\extension"
|
||||
copy sql\$(EXTENSION)--*.sql "$(SHAREDIR)\extension"
|
||||
|
||||
installcheck:
|
||||
"$(BINDIR)\pg_regress" --bindir="$(BINDIR)" $(REGRESS_OPTS) $(REGRESS)
|
||||
|
||||
prove_installcheck:
|
||||
rm -rf tmp_check
|
||||
set PGPORT=65432
|
||||
set PG_REGRESS="$(BINDIR)\pg_regress"
|
||||
$(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) test/t/*.pl
|
||||
75
README.md
75
README.md
@@ -3,9 +3,9 @@
|
||||
Open-source vector similarity search for Postgres
|
||||
|
||||
```sql
|
||||
CREATE TABLE items (embedding vector(3));
|
||||
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops);
|
||||
SELECT * FROM items ORDER BY embedding <-> '[1,2,3]' LIMIT 5;
|
||||
CREATE TABLE table (column vector(3));
|
||||
CREATE INDEX ON table USING ivfflat (column vector_l2_ops);
|
||||
SELECT * FROM table ORDER BY column <-> '[1,2,3]' LIMIT 5;
|
||||
```
|
||||
|
||||
Supports L2 distance, inner product, and cosine distance
|
||||
@@ -14,10 +14,10 @@ Supports L2 distance, inner product, and cosine distance
|
||||
|
||||
## Installation
|
||||
|
||||
Compile and install the extension (supports Postgres 10+)
|
||||
Compile and install the extension (supports Postgres 9.6+)
|
||||
|
||||
```sh
|
||||
git clone --branch v0.3.2 https://github.com/pgvector/pgvector.git
|
||||
git clone --branch v0.2.5 https://github.com/pgvector/pgvector.git
|
||||
cd pgvector
|
||||
make
|
||||
make install # may need sudo
|
||||
@@ -33,22 +33,22 @@ You can also install it with [Docker](#docker), [Homebrew](#homebrew), or [PGXN]
|
||||
|
||||
## Getting Started
|
||||
|
||||
Create a vector column with 3 dimensions
|
||||
Create a vector column with 3 dimensions (replace `table` and `column` with non-reserved names)
|
||||
|
||||
```sql
|
||||
CREATE TABLE items (embedding vector(3));
|
||||
CREATE TABLE table (column vector(3));
|
||||
```
|
||||
|
||||
Insert values
|
||||
|
||||
```sql
|
||||
INSERT INTO items VALUES ('[1,2,3]'), ('[4,5,6]');
|
||||
INSERT INTO table VALUES ('[1,2,3]'), ('[4,5,6]');
|
||||
```
|
||||
|
||||
Get the nearest neighbor by L2 distance
|
||||
|
||||
```sql
|
||||
SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 1;
|
||||
SELECT * FROM table ORDER BY column <-> '[3,1,2]' LIMIT 1;
|
||||
```
|
||||
|
||||
Also supports inner product (`<#>`) and cosine distance (`<=>`)
|
||||
@@ -62,19 +62,19 @@ Speed up queries with an approximate index. Add an index for each distance funct
|
||||
L2 distance
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops);
|
||||
CREATE INDEX ON table USING ivfflat (column vector_l2_ops);
|
||||
```
|
||||
|
||||
Inner product
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON items USING ivfflat (embedding vector_ip_ops);
|
||||
CREATE INDEX ON table USING ivfflat (column vector_ip_ops);
|
||||
```
|
||||
|
||||
Cosine distance
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops);
|
||||
CREATE INDEX ON table USING ivfflat (column vector_cosine_ops);
|
||||
```
|
||||
|
||||
Indexes should be created after the table has some data for optimal clustering. Also, unlike typical indexes which only affect performance, you may see different results for queries after adding an approximate index.
|
||||
@@ -84,7 +84,7 @@ Indexes should be created after the table has some data for optimal clustering.
|
||||
Specify the number of inverted lists (100 by default)
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
|
||||
CREATE INDEX ON table USING ivfflat (column opclass) WITH (lists = 100);
|
||||
```
|
||||
|
||||
A [good place to start](https://github.com/facebookresearch/faiss/issues/112) is `4 * sqrt(rows)`
|
||||
@@ -119,10 +119,9 @@ SELECT phase, tuples_done, tuples_total FROM pg_stat_progress_create_index;
|
||||
The phases are:
|
||||
|
||||
1. `initializing`
|
||||
2. `sampling table`
|
||||
3. `performing k-means`
|
||||
4. `sorting tuples`
|
||||
5. `loading tuples`
|
||||
2. `performing k-means`
|
||||
3. `sorting tuples`
|
||||
4. `loading tuples`
|
||||
|
||||
Note: `tuples_done` and `tuples_total` are only populated during the `loading tuples` phase
|
||||
|
||||
@@ -131,20 +130,10 @@ Note: `tuples_done` and `tuples_total` are only populated during the `loading tu
|
||||
Consider [partial indexes](https://www.postgresql.org/docs/current/indexes-partial.html) for queries with a `WHERE` clause
|
||||
|
||||
```sql
|
||||
SELECT * FROM items WHERE category_id = 123 ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
|
||||
CREATE INDEX ON table USING ivfflat (column opclass) WHERE (other_column = 123);
|
||||
```
|
||||
|
||||
can be indexed with:
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WHERE (category_id = 123);
|
||||
```
|
||||
|
||||
To index many different values of `category_id`, consider [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) on `category_id`.
|
||||
|
||||
```sql
|
||||
CREATE TABLE items (embedding vector(3), category_id int) PARTITION BY LIST(category_id);
|
||||
```
|
||||
To index many different values of `other_column`, consider [partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) on `other_column`.
|
||||
|
||||
## Performance
|
||||
|
||||
@@ -157,14 +146,14 @@ SET max_parallel_workers_per_gather = 4;
|
||||
To speed up queries with an index, increase the number of inverted lists (at the expense of recall).
|
||||
|
||||
```sql
|
||||
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 1000);
|
||||
CREATE INDEX ON table USING ivfflat (column opclass) WITH (lists = 1000);
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
### Vector Type
|
||||
|
||||
Each vector takes `4 * dimensions + 8` bytes of storage. Each element is a single precision floating-point number (like the `real` type in Postgres), and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Vectors can have up to 1024 dimensions.
|
||||
Each vector takes `4 * dimensions + 8` bytes of storage. Each element is a float, and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Vectors can have up to 1024 dimensions.
|
||||
|
||||
### Vector Operators
|
||||
|
||||
@@ -195,10 +184,8 @@ Libraries that use pgvector:
|
||||
- [pgvector-ruby](https://github.com/pgvector/pgvector-ruby) (Ruby)
|
||||
- [pgvector-node](https://github.com/pgvector/pgvector-node) (Node.js)
|
||||
- [pgvector-go](https://github.com/pgvector/pgvector-go) (Go)
|
||||
- [pgvector-php](https://github.com/pgvector/pgvector-php) (PHP)
|
||||
- [pgvector-rust](https://github.com/pgvector/pgvector-rust) (Rust)
|
||||
- [pgvector-cpp](https://github.com/pgvector/pgvector-cpp) (C++)
|
||||
- [pgvector-elixir](https://github.com/pgvector/pgvector-elixir) (Elixir)
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
@@ -232,14 +219,14 @@ This adds pgvector to the [Postgres image](https://hub.docker.com/_/postgres).
|
||||
You can also build the image manually
|
||||
|
||||
```sh
|
||||
git clone --branch v0.3.2 https://github.com/pgvector/pgvector.git
|
||||
git clone --branch v0.2.5 https://github.com/pgvector/pgvector.git
|
||||
cd pgvector
|
||||
docker build -t pgvector .
|
||||
```
|
||||
|
||||
### Homebrew
|
||||
|
||||
With Homebrew Postgres, you can use:
|
||||
On Mac with Homebrew Postgres, you can use:
|
||||
|
||||
```sh
|
||||
brew install pgvector/brew/pgvector
|
||||
@@ -270,29 +257,13 @@ Install the latest version and run:
|
||||
ALTER EXTENSION vector UPDATE;
|
||||
```
|
||||
|
||||
## Upgrade Notes
|
||||
|
||||
### 0.3.1
|
||||
|
||||
If upgrading from 0.2.7 or 0.3.0, recreate all `ivfflat` indexes after upgrading to ensure all data is indexed.
|
||||
|
||||
```sql
|
||||
-- Postgres 12+
|
||||
REINDEX INDEX CONCURRENTLY index_name;
|
||||
|
||||
-- Postgres < 12
|
||||
CREATE INDEX CONCURRENTLY temp_name ON table USING ivfflat (column opclass);
|
||||
DROP INDEX CONCURRENTLY index_name;
|
||||
ALTER INDEX temp_name RENAME TO index_name;
|
||||
```
|
||||
|
||||
## Thanks
|
||||
|
||||
Thanks to:
|
||||
|
||||
- [PASE: PostgreSQL Ultra-High-Dimensional Approximate Nearest Neighbor Search Extension](https://dl.acm.org/doi/pdf/10.1145/3318464.3386131)
|
||||
- [Faiss: A Library for Efficient Similarity Search and Clustering of Dense Vectors](https://github.com/facebookresearch/faiss)
|
||||
- [Using the Triangle Inequality to Accelerate k-means](https://www.aaai.org/Papers/ICML/2003/ICML03-022.pdf)
|
||||
- [Web-Scale k-means Clustering](https://www.eecs.tufts.edu/~dsculley/papers/fastkmeans.pdf)
|
||||
- [k-means++: The Advantage of Careful Seeding](https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf)
|
||||
- [Concept Decompositions for Large Sparse Text Data using Clustering](https://www.cs.utexas.edu/users/inderjit/public_papers/concept_mlj.pdf)
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||
\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.6'" to load this file. \quit
|
||||
@@ -1,2 +0,0 @@
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||
\echo Use "ALTER EXTENSION vector UPDATE TO '0.2.7'" to load this file. \quit
|
||||
@@ -1,2 +0,0 @@
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||
\echo Use "ALTER EXTENSION vector UPDATE TO '0.3.0'" to load this file. \quit
|
||||
@@ -1,2 +0,0 @@
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||
\echo Use "ALTER EXTENSION vector UPDATE TO '0.3.1'" to load this file. \quit
|
||||
@@ -1,2 +0,0 @@
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||
\echo Use "ALTER EXTENSION vector UPDATE TO '0.3.2'" to load this file. \quit
|
||||
133
src/ivfbuild.c
133
src/ivfbuild.c
@@ -42,91 +42,6 @@
|
||||
#define UpdateProgress(index, val) ((void)val)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Callback for sampling
|
||||
*/
|
||||
static void
|
||||
SampleCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
|
||||
bool *isnull, bool tupleIsAlive, void *state)
|
||||
{
|
||||
IvfflatBuildState *buildstate = (IvfflatBuildState *) state;
|
||||
VectorArray samples = buildstate->samples;
|
||||
int targsamples = samples->maxlen;
|
||||
Datum value = values[0];
|
||||
|
||||
/* Skip nulls */
|
||||
if (isnull[0])
|
||||
return;
|
||||
|
||||
/*
|
||||
* Normalize with KMEANS_NORM_PROC since spherical distance function
|
||||
* expects unit vectors
|
||||
*/
|
||||
if (buildstate->kmeansnormprocinfo != NULL)
|
||||
{
|
||||
if (!IvfflatNormValue(buildstate->kmeansnormprocinfo, buildstate->collation, &value, buildstate->normvec))
|
||||
return;
|
||||
}
|
||||
|
||||
if (samples->length < targsamples)
|
||||
{
|
||||
VectorArraySet(samples, samples->length, DatumGetVector(value));
|
||||
samples->length++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (buildstate->rowstoskip < 0)
|
||||
buildstate->rowstoskip = reservoir_get_next_S(&buildstate->rstate, samples->length, targsamples);
|
||||
|
||||
if (buildstate->rowstoskip <= 0)
|
||||
{
|
||||
#if PG_VERSION_NUM >= 150000
|
||||
int k = (int) (targsamples * sampler_random_fract(&buildstate->rstate.randstate));
|
||||
#else
|
||||
int k = (int) (targsamples * sampler_random_fract(buildstate->rstate.randstate));
|
||||
#endif
|
||||
|
||||
Assert(k >= 0 && k < targsamples);
|
||||
VectorArraySet(samples, k, DatumGetVector(value));
|
||||
}
|
||||
|
||||
buildstate->rowstoskip -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sample rows with same logic as ANALYZE
|
||||
*/
|
||||
static void
|
||||
SampleRows(IvfflatBuildState * buildstate)
|
||||
{
|
||||
int targsamples = buildstate->samples->maxlen;
|
||||
BlockNumber totalblocks = RelationGetNumberOfBlocks(buildstate->heap);
|
||||
|
||||
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_SAMPLE);
|
||||
|
||||
buildstate->rowstoskip = -1;
|
||||
|
||||
BlockSampler_Init(&buildstate->bs, totalblocks, targsamples, RandomInt());
|
||||
|
||||
reservoir_init_selection_state(&buildstate->rstate, targsamples);
|
||||
while (BlockSampler_HasMore(&buildstate->bs))
|
||||
{
|
||||
BlockNumber targblock = BlockSampler_Next(&buildstate->bs);
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
table_index_build_range_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||
false, true, false, targblock, 1, SampleCallback, (void *) buildstate, NULL);
|
||||
#elif PG_VERSION_NUM >= 110000
|
||||
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||
false, true, targblock, 1, SampleCallback, (void *) buildstate, NULL);
|
||||
#else
|
||||
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||
false, true, targblock, 1, SampleCallback, (void *) buildstate);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Callback for table_index_build_scan
|
||||
*/
|
||||
@@ -233,8 +148,8 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
|
||||
GenericXLogState *state;
|
||||
int list;
|
||||
IndexTuple itup = NULL; /* silence compiler warning */
|
||||
BlockNumber startPage;
|
||||
BlockNumber insertPage;
|
||||
BlockNumber startPage = InvalidBlockNumber;
|
||||
BlockNumber insertPage = InvalidBlockNumber;
|
||||
Size itemsz;
|
||||
int i;
|
||||
int64 inserted = 0;
|
||||
@@ -259,7 +174,7 @@ InsertTuples(Relation index, IvfflatBuildState * buildstate, ForkNumber forkNum)
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
|
||||
buf = IvfflatNewBuffer(index, forkNum);
|
||||
IvfflatInitRegisterPage(index, &buf, &page, &state);
|
||||
IvfflatInitPage(index, &buf, &page, &state);
|
||||
|
||||
startPage = BufferGetBlockNumber(buf);
|
||||
|
||||
@@ -362,7 +277,7 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
|
||||
static void
|
||||
FreeBuildState(IvfflatBuildState * buildstate)
|
||||
{
|
||||
VectorArrayFree(buildstate->centers);
|
||||
pfree(buildstate->centers);
|
||||
pfree(buildstate->listInfo);
|
||||
pfree(buildstate->normvec);
|
||||
|
||||
@@ -372,38 +287,6 @@ FreeBuildState(IvfflatBuildState * buildstate)
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute centers
|
||||
*/
|
||||
static void
|
||||
ComputeCenters(IvfflatBuildState * buildstate)
|
||||
{
|
||||
int numSamples;
|
||||
|
||||
/* Target 50 samples per list, with at least 10000 samples */
|
||||
/* The number of samples has a large effect on index build time */
|
||||
numSamples = buildstate->lists * 50;
|
||||
if (numSamples < 10000)
|
||||
numSamples = 10000;
|
||||
|
||||
/* Skip samples for unlogged table */
|
||||
if (buildstate->heap == NULL)
|
||||
numSamples = 1;
|
||||
|
||||
/* Sample rows */
|
||||
/* TODO Ensure within maintenance_work_mem */
|
||||
buildstate->samples = VectorArrayInit(numSamples, buildstate->dimensions);
|
||||
if (buildstate->heap != NULL)
|
||||
SampleRows(buildstate);
|
||||
|
||||
/* Calculate centers */
|
||||
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_KMEANS);
|
||||
IvfflatBench("k-means", IvfflatKmeans(buildstate->index, buildstate->samples, buildstate->centers));
|
||||
|
||||
/* Free samples before we allocate more memory */
|
||||
VectorArrayFree(buildstate->samples);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create the metapage
|
||||
*/
|
||||
@@ -416,7 +299,7 @@ CreateMetaPage(Relation index, int dimensions, int lists, ForkNumber forkNum)
|
||||
IvfflatMetaPage metap;
|
||||
|
||||
buf = IvfflatNewBuffer(index, forkNum);
|
||||
IvfflatInitRegisterPage(index, &buf, &page, &state);
|
||||
IvfflatInitPage(index, &buf, &page, &state);
|
||||
|
||||
/* Set metapage data */
|
||||
metap = IvfflatPageGetMeta(page);
|
||||
@@ -449,7 +332,7 @@ CreateListPages(Relation index, VectorArray centers, int dimensions,
|
||||
list = palloc(itemsz);
|
||||
|
||||
buf = IvfflatNewBuffer(index, forkNum);
|
||||
IvfflatInitRegisterPage(index, &buf, &page, &state);
|
||||
IvfflatInitPage(index, &buf, &page, &state);
|
||||
|
||||
for (i = 0; i < lists; i++)
|
||||
{
|
||||
@@ -577,7 +460,9 @@ BuildIndex(Relation heap, Relation index, IndexInfo *indexInfo,
|
||||
{
|
||||
InitBuildState(buildstate, heap, index, indexInfo);
|
||||
|
||||
ComputeCenters(buildstate);
|
||||
/* Perform k-means clustering */
|
||||
UpdateProgress(PROGRESS_CREATEIDX_SUBPHASE, PROGRESS_IVFFLAT_PHASE_KMEANS);
|
||||
IvfflatBench("k-means", IvfflatKmeans(buildstate));
|
||||
|
||||
/* Create pages */
|
||||
CreateMetaPage(index, buildstate->dimensions, buildstate->lists, forkNum);
|
||||
|
||||
@@ -45,8 +45,6 @@ ivfflatbuildphasename(int64 phasenum)
|
||||
{
|
||||
case PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE:
|
||||
return "initializing";
|
||||
case PROGRESS_IVFFLAT_PHASE_SAMPLE:
|
||||
return "sampling table";
|
||||
case PROGRESS_IVFFLAT_PHASE_KMEANS:
|
||||
return "performing k-means";
|
||||
case PROGRESS_IVFFLAT_PHASE_SORT:
|
||||
@@ -164,7 +162,7 @@ ivfflatvalidate(Oid opclassoid)
|
||||
*
|
||||
* See https://www.postgresql.org/docs/current/index-api.html
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(ivfflathandler);
|
||||
PG_FUNCTION_INFO_V1(ivfflathandler);
|
||||
Datum
|
||||
ivfflathandler(PG_FUNCTION_ARGS)
|
||||
{
|
||||
|
||||
@@ -3,26 +3,21 @@
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#if PG_VERSION_NUM < 100000
|
||||
#error "Requires PostgreSQL 10+"
|
||||
#endif
|
||||
|
||||
#include "access/generic_xlog.h"
|
||||
#include "access/reloptions.h"
|
||||
#include "nodes/execnodes.h"
|
||||
#include "port.h" /* for strtof() and random() */
|
||||
#include "utils/sampling.h"
|
||||
#include "utils/tuplesort.h"
|
||||
#include "vector.h"
|
||||
|
||||
#if PG_VERSION_NUM >= 150000
|
||||
#include "common/pg_prng.h"
|
||||
#endif
|
||||
|
||||
#ifdef IVFFLAT_BENCH
|
||||
#include "portability/instr_time.h"
|
||||
#endif
|
||||
|
||||
#if PG_VERSION_NUM < 90600
|
||||
#error "Requires PostgreSQL 9.6+"
|
||||
#endif
|
||||
|
||||
/* Support functions */
|
||||
#define IVFFLAT_DISTANCE_PROC 1
|
||||
#define IVFFLAT_NORM_PROC 2
|
||||
@@ -42,10 +37,9 @@
|
||||
|
||||
/* Build phases */
|
||||
/* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 */
|
||||
#define PROGRESS_IVFFLAT_PHASE_SAMPLE 2
|
||||
#define PROGRESS_IVFFLAT_PHASE_KMEANS 3
|
||||
#define PROGRESS_IVFFLAT_PHASE_SORT 4
|
||||
#define PROGRESS_IVFFLAT_PHASE_LOAD 5
|
||||
#define PROGRESS_IVFFLAT_PHASE_KMEANS 2
|
||||
#define PROGRESS_IVFFLAT_PHASE_SORT 3
|
||||
#define PROGRESS_IVFFLAT_PHASE_LOAD 4
|
||||
|
||||
#define IVFFLAT_LIST_SIZE(_dim) (offsetof(IvfflatListData, center) + VECTOR_SIZE(_dim))
|
||||
|
||||
@@ -67,26 +61,15 @@
|
||||
#define IvfflatBench(name, code) (code)
|
||||
#endif
|
||||
|
||||
#if PG_VERSION_NUM >= 150000
|
||||
#define RandomDouble() pg_prng_double(&pg_global_prng_state)
|
||||
#define RandomInt() pg_prng_uint32(&pg_global_prng_state)
|
||||
#else
|
||||
#define RandomDouble() (((double) random()) / MAX_RANDOM_VALUE)
|
||||
#define RandomInt() random()
|
||||
#endif
|
||||
|
||||
/* Variables */
|
||||
extern int ivfflat_probes;
|
||||
|
||||
/* Exported functions */
|
||||
PGDLLEXPORT void _PG_init(void);
|
||||
|
||||
typedef struct VectorArrayData
|
||||
{
|
||||
int length;
|
||||
int maxlen;
|
||||
int dim;
|
||||
Vector *items;
|
||||
Vector items[FLEXIBLE_ARRAY_MEMBER];
|
||||
} VectorArrayData;
|
||||
|
||||
typedef VectorArrayData * VectorArray;
|
||||
@@ -207,16 +190,16 @@ typedef struct IvfflatScanOpaqueData
|
||||
|
||||
typedef IvfflatScanOpaqueData * IvfflatScanOpaque;
|
||||
|
||||
#define VECTOR_ARRAY_SIZE(_length, _dim) (sizeof(VectorArrayData) + (_length) * VECTOR_SIZE(_dim))
|
||||
#define VECTOR_ARRAY_OFFSET(_arr, _offset) ((char*) (_arr)->items + (_offset) * VECTOR_SIZE((_arr)->dim))
|
||||
#define VECTOR_ARRAY_SIZE(_length, _dim) (offsetof(VectorArrayData, items) + _length * VECTOR_SIZE(_dim))
|
||||
#define VECTOR_ARRAY_OFFSET(_arr, _offset) ((char*) _arr + offsetof(VectorArrayData, items) + (_offset) * VECTOR_SIZE(_arr->dim))
|
||||
#define VectorArrayGet(_arr, _offset) ((Vector *) VECTOR_ARRAY_OFFSET(_arr, _offset))
|
||||
#define VectorArraySet(_arr, _offset, _val) memcpy(VECTOR_ARRAY_OFFSET(_arr, _offset), _val, VECTOR_SIZE((_arr)->dim))
|
||||
#define VectorArraySet(_arr, _offset, _val) (memcpy(VECTOR_ARRAY_OFFSET(_arr, _offset), _val, VECTOR_SIZE(_arr->dim)))
|
||||
|
||||
/* Methods */
|
||||
void _PG_init(void);
|
||||
VectorArray VectorArrayInit(int maxlen, int dimensions);
|
||||
void VectorArrayFree(VectorArray arr);
|
||||
void PrintVectorArray(char *msg, VectorArray arr);
|
||||
void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers);
|
||||
void IvfflatKmeans(IvfflatBuildState * buildstate);
|
||||
FmgrInfo *IvfflatOptionalProcInfo(Relation rel, uint16 procnum);
|
||||
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
|
||||
int IvfflatGetLists(Relation index);
|
||||
@@ -224,8 +207,7 @@ void IvfflatUpdateList(Relation index, GenericXLogState *state, ListInfo listIn
|
||||
void IvfflatCommitBuffer(Buffer buf, GenericXLogState *state);
|
||||
void IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum);
|
||||
Buffer IvfflatNewBuffer(Relation index, ForkNumber forkNum);
|
||||
void IvfflatInitPage(Buffer buf, Page page);
|
||||
void IvfflatInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state);
|
||||
void IvfflatInitPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state);
|
||||
|
||||
/* Index access methods */
|
||||
IndexBuildResult *ivfflatbuild(Relation heap, Relation index, IndexInfo *indexInfo);
|
||||
|
||||
@@ -53,6 +53,18 @@ FindInsertPage(Relation rel, Datum *values, BlockNumber *insertPage, ListInfo *
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Prepare to insert an index tuple
|
||||
*/
|
||||
static void
|
||||
LoadInsertPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, BlockNumber insertPage)
|
||||
{
|
||||
*buf = ReadBuffer(index, insertPage);
|
||||
LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
|
||||
*state = GenericXLogStart(index);
|
||||
*page = GenericXLogRegisterBuffer(*state, *buf, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert a tuple into the index
|
||||
*/
|
||||
@@ -75,18 +87,11 @@ InsertTuple(Relation rel, IndexTuple itup, Relation heapRel, Datum *values)
|
||||
itemsz = MAXALIGN(IndexTupleSize(itup));
|
||||
Assert(itemsz <= BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(IvfflatPageOpaqueData)));
|
||||
|
||||
LoadInsertPage(rel, &buf, &page, &state, insertPage);
|
||||
|
||||
/* Find a page to insert the item */
|
||||
for (;;)
|
||||
while (PageGetFreeSpace(page) < itemsz)
|
||||
{
|
||||
buf = ReadBuffer(rel, insertPage);
|
||||
LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
|
||||
|
||||
state = GenericXLogStart(rel);
|
||||
page = GenericXLogRegisterBuffer(state, buf, 0);
|
||||
|
||||
if (PageGetFreeSpace(page) >= itemsz)
|
||||
break;
|
||||
|
||||
insertPage = IvfflatPageGetOpaque(page)->nextblkno;
|
||||
|
||||
if (BlockNumberIsValid(insertPage))
|
||||
@@ -94,45 +99,15 @@ InsertTuple(Relation rel, IndexTuple itup, Relation heapRel, Datum *values)
|
||||
/* Move to next page */
|
||||
GenericXLogAbort(state);
|
||||
UnlockReleaseBuffer(buf);
|
||||
|
||||
LoadInsertPage(rel, &buf, &page, &state, insertPage);
|
||||
}
|
||||
else
|
||||
{
|
||||
Buffer metabuf;
|
||||
Buffer newbuf;
|
||||
Page newpage;
|
||||
|
||||
/*
|
||||
* From ReadBufferExtended: Caller is responsible for ensuring
|
||||
* that only one backend tries to extend a relation at the same
|
||||
* time!
|
||||
*/
|
||||
metabuf = ReadBuffer(rel, IVFFLAT_METAPAGE_BLKNO);
|
||||
LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);
|
||||
|
||||
/* Add a new page */
|
||||
newbuf = IvfflatNewBuffer(rel, MAIN_FORKNUM);
|
||||
newpage = GenericXLogRegisterBuffer(state, newbuf, GENERIC_XLOG_FULL_IMAGE);
|
||||
IvfflatAppendPage(rel, &buf, &page, &state, MAIN_FORKNUM);
|
||||
|
||||
/* Init new page */
|
||||
IvfflatInitPage(newbuf, newpage);
|
||||
|
||||
/* Update insert page */
|
||||
insertPage = BufferGetBlockNumber(newbuf);
|
||||
|
||||
/* Update previous buffer */
|
||||
IvfflatPageGetOpaque(page)->nextblkno = insertPage;
|
||||
|
||||
/* Commit */
|
||||
MarkBufferDirty(newbuf);
|
||||
MarkBufferDirty(buf);
|
||||
GenericXLogFinish(state);
|
||||
|
||||
/* Unlock extend relation lock as early as possible */
|
||||
UnlockReleaseBuffer(metabuf);
|
||||
|
||||
/* Unlock rest */
|
||||
UnlockReleaseBuffer(newbuf);
|
||||
UnlockReleaseBuffer(buf);
|
||||
insertPage = BufferGetBlockNumber(buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
476
src/ivfkmeans.c
476
src/ivfkmeans.c
@@ -2,8 +2,20 @@
|
||||
|
||||
#include <float.h>
|
||||
|
||||
#include "catalog/index.h"
|
||||
#include "ivfflat.h"
|
||||
#include "miscadmin.h"
|
||||
#include "storage/bufmgr.h"
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
#include "access/tableam.h"
|
||||
#endif
|
||||
|
||||
#if PG_VERSION_NUM >= 130000
|
||||
#define CALLBACK_ITEM_POINTER ItemPointer tid
|
||||
#else
|
||||
#define CALLBACK_ITEM_POINTER HeapTuple hup
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Initialize with kmeans++
|
||||
@@ -11,12 +23,12 @@
|
||||
* https://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf
|
||||
*/
|
||||
static void
|
||||
InitCenters(Relation index, VectorArray samples, VectorArray centers, float *lowerBound)
|
||||
InitCenters(Relation index, VectorArray samples, VectorArray centers)
|
||||
{
|
||||
FmgrInfo *procinfo;
|
||||
Oid collation;
|
||||
int i;
|
||||
int64 j;
|
||||
int j;
|
||||
double distance;
|
||||
double sum;
|
||||
double choice;
|
||||
@@ -29,13 +41,13 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, float *low
|
||||
collation = index->rd_indcollation[0];
|
||||
|
||||
/* Choose an initial center uniformly at random */
|
||||
VectorArraySet(centers, 0, VectorArrayGet(samples, RandomInt() % samples->length));
|
||||
VectorArraySet(centers, 0, VectorArrayGet(samples, random() % samples->length));
|
||||
centers->length++;
|
||||
|
||||
for (j = 0; j < numSamples; j++)
|
||||
weight[j] = DBL_MAX;
|
||||
|
||||
for (i = 0; i < numCenters; i++)
|
||||
for (i = 0; i < numCenters - 1; i++)
|
||||
{
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
|
||||
@@ -49,9 +61,6 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, float *low
|
||||
/* TODO Use triangle inequality to reduce distance calculations */
|
||||
distance = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, i))));
|
||||
|
||||
/* Set lower bound */
|
||||
lowerBound[j * numCenters + i] = distance;
|
||||
|
||||
/* Use distance squared for weighted probability distribution */
|
||||
distance *= distance;
|
||||
|
||||
@@ -61,12 +70,8 @@ InitCenters(Relation index, VectorArray samples, VectorArray centers, float *low
|
||||
sum += weight[j];
|
||||
}
|
||||
|
||||
/* Only compute lower bound on last iteration */
|
||||
if (i + 1 == numCenters)
|
||||
break;
|
||||
|
||||
/* Choose new center using weighted probability distribution. */
|
||||
choice = sum * RandomDouble();
|
||||
choice = sum * (((double) random()) / MAX_RANDOM_VALUE);
|
||||
for (j = 0; j < numSamples - 1; j++)
|
||||
{
|
||||
choice -= weight[j];
|
||||
@@ -145,7 +150,7 @@ QuickCenters(Relation index, VectorArray samples, VectorArray centers)
|
||||
vec->dim = dimensions;
|
||||
|
||||
for (j = 0; j < dimensions; j++)
|
||||
vec->x[j] = RandomDouble();
|
||||
vec->x[j] = ((double) random()) / MAX_RANDOM_VALUE;
|
||||
|
||||
/* Normalize if needed (only needed for random centers) */
|
||||
if (normprocinfo != NULL)
|
||||
@@ -156,301 +161,202 @@ QuickCenters(Relation index, VectorArray samples, VectorArray centers)
|
||||
}
|
||||
|
||||
/*
|
||||
* Use Elkan for performance. This requires distance function to satisfy triangle inequality.
|
||||
* Callback for sampling
|
||||
*/
|
||||
static void
|
||||
SampleCallback(Relation index, CALLBACK_ITEM_POINTER, Datum *values,
|
||||
bool *isnull, bool tupleIsAlive, void *state)
|
||||
{
|
||||
IvfflatBuildState *buildstate = (IvfflatBuildState *) state;
|
||||
VectorArray samples = buildstate->samples;
|
||||
int targsamples = samples->maxlen;
|
||||
Datum value = values[0];
|
||||
|
||||
/* Skip nulls */
|
||||
if (isnull[0])
|
||||
return;
|
||||
|
||||
/*
|
||||
* Normalize with KMEANS_NORM_PROC since spherical distance function
|
||||
* expects unit vectors
|
||||
*/
|
||||
if (buildstate->kmeansnormprocinfo != NULL)
|
||||
{
|
||||
if (!IvfflatNormValue(buildstate->kmeansnormprocinfo, buildstate->collation, &value, buildstate->normvec))
|
||||
return;
|
||||
}
|
||||
|
||||
if (samples->length < targsamples)
|
||||
{
|
||||
VectorArraySet(samples, samples->length, DatumGetVector(value));
|
||||
samples->length++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (buildstate->rowstoskip < 0)
|
||||
buildstate->rowstoskip = reservoir_get_next_S(&buildstate->rstate, samples->length, targsamples);
|
||||
|
||||
if (buildstate->rowstoskip <= 0)
|
||||
{
|
||||
int k = (int) (targsamples * sampler_random_fract(buildstate->rstate.randstate));
|
||||
|
||||
Assert(k >= 0 && k < targsamples);
|
||||
VectorArraySet(samples, k, DatumGetVector(value));
|
||||
}
|
||||
|
||||
buildstate->rowstoskip -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sample rows with same logic as ANALYZE
|
||||
*/
|
||||
static void
|
||||
SampleRows(IvfflatBuildState * buildstate)
|
||||
{
|
||||
int targsamples = buildstate->samples->maxlen;
|
||||
BlockNumber totalblocks = RelationGetNumberOfBlocks(buildstate->heap);
|
||||
|
||||
buildstate->rowstoskip = -1;
|
||||
buildstate->samples->length = 0;
|
||||
|
||||
BlockSampler_Init(&buildstate->bs, totalblocks, targsamples, random());
|
||||
|
||||
reservoir_init_selection_state(&buildstate->rstate, targsamples);
|
||||
while (BlockSampler_HasMore(&buildstate->bs))
|
||||
{
|
||||
BlockNumber targblock = BlockSampler_Next(&buildstate->bs);
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
table_index_build_range_scan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||
false, true, false, targblock, 1, SampleCallback, (void *) buildstate, NULL);
|
||||
#elif PG_VERSION_NUM >= 110000
|
||||
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||
false, true, targblock, 1, SampleCallback, (void *) buildstate, NULL);
|
||||
#else
|
||||
IndexBuildHeapRangeScan(buildstate->heap, buildstate->index, buildstate->indexInfo,
|
||||
false, true, targblock, 1, SampleCallback, (void *) buildstate);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Use mini-batch k-means
|
||||
*
|
||||
* We use L2 distance for L2 (not L2 squared like index scan)
|
||||
* and angular distance for inner product and cosine distance
|
||||
*
|
||||
* https://www.aaai.org/Papers/ICML/2003/ICML03-022.pdf
|
||||
* https://www.eecs.tufts.edu/~dsculley/papers/fastkmeans.pdf
|
||||
*/
|
||||
static void
|
||||
ElkanKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
MiniBatchKmeans(IvfflatBuildState * buildstate)
|
||||
{
|
||||
FmgrInfo *procinfo;
|
||||
FmgrInfo *normprocinfo;
|
||||
Oid collation;
|
||||
Vector *vec;
|
||||
Vector *newCenter;
|
||||
int iteration;
|
||||
int64 j;
|
||||
int64 k;
|
||||
int dimensions = centers->dim;
|
||||
int numCenters = centers->maxlen;
|
||||
int numSamples = samples->length;
|
||||
VectorArray newCenters;
|
||||
int *centerCounts;
|
||||
int *closestCenters;
|
||||
float *lowerBound;
|
||||
float *upperBound;
|
||||
float *s;
|
||||
float *halfcdist;
|
||||
float *newcdist;
|
||||
int changes;
|
||||
VectorArray centers = buildstate->centers;
|
||||
VectorArray m = buildstate->samples;
|
||||
int b = m->maxlen;
|
||||
int t = 20;
|
||||
double distance;
|
||||
double minDistance;
|
||||
int closestCenter;
|
||||
double distance;
|
||||
bool rj;
|
||||
bool rjreset;
|
||||
double dxcx;
|
||||
double dxc;
|
||||
|
||||
/* Calculate allocation sizes */
|
||||
Size samplesSize = VECTOR_ARRAY_SIZE(samples->maxlen, samples->dim);
|
||||
Size centersSize = VECTOR_ARRAY_SIZE(centers->maxlen, centers->dim);
|
||||
Size newCentersSize = VECTOR_ARRAY_SIZE(numCenters, dimensions);
|
||||
Size centerCountsSize = sizeof(int) * numCenters;
|
||||
Size closestCentersSize = sizeof(int) * numSamples;
|
||||
Size lowerBoundSize = sizeof(float) * numSamples * numCenters;
|
||||
Size upperBoundSize = sizeof(float) * numSamples;
|
||||
Size sSize = sizeof(float) * numCenters;
|
||||
Size halfcdistSize = sizeof(float) * numCenters * numCenters;
|
||||
Size newcdistSize = sizeof(float) * numCenters;
|
||||
|
||||
/* Calculate total size */
|
||||
Size totalSize = samplesSize + centersSize + newCentersSize + centerCountsSize + closestCentersSize + lowerBoundSize + upperBoundSize + sSize + halfcdistSize + newcdistSize;
|
||||
|
||||
/* Check memory requirements */
|
||||
/* Add one to error message to ceil */
|
||||
if (totalSize / 1024 > maintenance_work_mem)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
||||
errmsg("memory required is %zu MB, maintenance_work_mem is %d MB",
|
||||
totalSize / (1024 * 1024) + 1, maintenance_work_mem / 1024)));
|
||||
|
||||
/* Ensure indexing does not overflow */
|
||||
if (numCenters * numCenters > INT_MAX)
|
||||
elog(ERROR, "Indexing overflow detected. Please report a bug.");
|
||||
int i;
|
||||
int j;
|
||||
int k;
|
||||
Vector *c;
|
||||
Vector *x;
|
||||
int *v;
|
||||
int *d;
|
||||
double eta;
|
||||
|
||||
/* Set support functions */
|
||||
procinfo = index_getprocinfo(index, 1, IVFFLAT_KMEANS_DISTANCE_PROC);
|
||||
normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_KMEANS_NORM_PROC);
|
||||
collation = index->rd_indcollation[0];
|
||||
|
||||
/* Allocate space */
|
||||
/* Use float instead of double to save memory */
|
||||
centerCounts = palloc(centerCountsSize);
|
||||
closestCenters = palloc(closestCentersSize);
|
||||
lowerBound = palloc_extended(lowerBoundSize, MCXT_ALLOC_HUGE);
|
||||
upperBound = palloc(upperBoundSize);
|
||||
s = palloc(sSize);
|
||||
halfcdist = palloc_extended(halfcdistSize, MCXT_ALLOC_HUGE);
|
||||
newcdist = palloc(newcdistSize);
|
||||
|
||||
newCenters = VectorArrayInit(numCenters, dimensions);
|
||||
for (j = 0; j < numCenters; j++)
|
||||
{
|
||||
vec = VectorArrayGet(newCenters, j);
|
||||
SET_VARSIZE(vec, VECTOR_SIZE(dimensions));
|
||||
vec->dim = dimensions;
|
||||
}
|
||||
FmgrInfo *procinfo = index_getprocinfo(buildstate->index, 1, IVFFLAT_KMEANS_DISTANCE_PROC);
|
||||
FmgrInfo *normprocinfo = buildstate->kmeansnormprocinfo;
|
||||
Oid collation = buildstate->index->rd_indcollation[0];
|
||||
|
||||
/* Pick initial centers */
|
||||
InitCenters(index, samples, centers, lowerBound);
|
||||
InitCenters(buildstate->index, buildstate->samples, buildstate->centers);
|
||||
|
||||
/* Assign each x to its closest initial center c(x) = argmin d(x,c) */
|
||||
for (j = 0; j < numSamples; j++)
|
||||
{
|
||||
minDistance = DBL_MAX;
|
||||
closestCenter = -1;
|
||||
v = palloc(sizeof(int) * centers->maxlen);
|
||||
d = palloc(sizeof(int) * b);
|
||||
|
||||
/* Find closest center */
|
||||
for (k = 0; k < numCenters; k++)
|
||||
{
|
||||
/* TODO Use Lemma 1 in k-means++ initialization */
|
||||
distance = lowerBound[j * numCenters + k];
|
||||
for (int i = 0; i < centers->length; i++)
|
||||
v[i] = 0;
|
||||
|
||||
if (distance < minDistance)
|
||||
{
|
||||
minDistance = distance;
|
||||
closestCenter = k;
|
||||
}
|
||||
}
|
||||
|
||||
upperBound[j] = minDistance;
|
||||
closestCenters[j] = closestCenter;
|
||||
}
|
||||
|
||||
/* Give 500 iterations to converge */
|
||||
for (iteration = 0; iteration < 500; iteration++)
|
||||
for (i = 0; i < t; i++)
|
||||
{
|
||||
/* Can take a while, so ensure we can interrupt */
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
|
||||
changes = 0;
|
||||
/* Get b examples picked randomly from X */
|
||||
SampleRows(buildstate);
|
||||
|
||||
/* Step 1: For all centers, compute distance */
|
||||
for (j = 0; j < numCenters; j++)
|
||||
{
|
||||
vec = VectorArrayGet(centers, j);
|
||||
|
||||
for (k = j + 1; k < numCenters; k++)
|
||||
{
|
||||
distance = 0.5 * DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, k))));
|
||||
halfcdist[j * numCenters + k] = distance;
|
||||
halfcdist[k * numCenters + j] = distance;
|
||||
}
|
||||
}
|
||||
|
||||
/* For all centers c, compute s(c) */
|
||||
for (j = 0; j < numCenters; j++)
|
||||
/* Cache nearest center to x */
|
||||
for (j = 0; j < m->length; j++)
|
||||
{
|
||||
/* compute closest */
|
||||
minDistance = DBL_MAX;
|
||||
closestCenter = -1;
|
||||
|
||||
for (k = 0; k < numCenters; k++)
|
||||
x = VectorArrayGet(m, j);
|
||||
|
||||
/* Find closest center */
|
||||
for (k = 0; k < centers->length; k++)
|
||||
{
|
||||
if (j == k)
|
||||
continue;
|
||||
distance = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(x), PointerGetDatum(VectorArrayGet(centers, k))));
|
||||
|
||||
distance = halfcdist[j * numCenters + k];
|
||||
if (distance < minDistance)
|
||||
{
|
||||
minDistance = distance;
|
||||
closestCenter = k;
|
||||
}
|
||||
}
|
||||
|
||||
s[j] = minDistance;
|
||||
d[j] = closestCenter;
|
||||
}
|
||||
|
||||
rjreset = iteration != 0;
|
||||
|
||||
for (j = 0; j < numSamples; j++)
|
||||
for (j = 0; j < m->length; j++)
|
||||
{
|
||||
/* Step 2: Identify all points x such that u(x) <= s(c(x)) */
|
||||
if (upperBound[j] <= s[closestCenters[j]])
|
||||
continue;
|
||||
x = VectorArrayGet(m, j);
|
||||
|
||||
rj = rjreset;
|
||||
/* Get cached center for this x */
|
||||
c = VectorArrayGet(centers, d[j]);
|
||||
|
||||
for (k = 0; k < numCenters; k++)
|
||||
/* Update per-center counts */
|
||||
v[d[j]]++;
|
||||
|
||||
/* Get per-center learning rate */
|
||||
eta = 1.0 / v[d[j]];
|
||||
|
||||
/* Take gradient step */
|
||||
for (k = 0; k < c->dim; k++)
|
||||
c->x[k] = (1 - eta) * c->x[k] + eta * x->x[k];
|
||||
}
|
||||
|
||||
/* Check for empty centers (likely duplicates) */
|
||||
if (i == 0)
|
||||
{
|
||||
for (j = 0; j < centers->length; j++)
|
||||
{
|
||||
/* Step 3: For all remaining points x and centers c */
|
||||
if (k == closestCenters[j])
|
||||
continue;
|
||||
|
||||
if (upperBound[j] <= lowerBound[j * numCenters + k])
|
||||
continue;
|
||||
|
||||
if (upperBound[j] <= halfcdist[closestCenters[j] * numCenters + k])
|
||||
continue;
|
||||
|
||||
vec = VectorArrayGet(samples, j);
|
||||
|
||||
/* Step 3a */
|
||||
if (rj)
|
||||
if (v[j] == 0)
|
||||
{
|
||||
dxcx = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, closestCenters[j]))));
|
||||
|
||||
/* d(x,c(x)) computed, which is a form of d(x,c) */
|
||||
lowerBound[j * numCenters + closestCenters[j]] = dxcx;
|
||||
upperBound[j] = dxcx;
|
||||
|
||||
rj = false;
|
||||
}
|
||||
else
|
||||
dxcx = upperBound[j];
|
||||
|
||||
/* Step 3b */
|
||||
if (dxcx > lowerBound[j * numCenters + k] || dxcx > halfcdist[closestCenters[j] * numCenters + k])
|
||||
{
|
||||
dxc = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(vec), PointerGetDatum(VectorArrayGet(centers, k))));
|
||||
|
||||
/* d(x,c) calculated */
|
||||
lowerBound[j * numCenters + k] = dxc;
|
||||
|
||||
if (dxc < dxcx)
|
||||
{
|
||||
closestCenters[j] = k;
|
||||
|
||||
/* c(x) changed */
|
||||
upperBound[j] = dxc;
|
||||
|
||||
changes++;
|
||||
}
|
||||
c = VectorArrayGet(centers, j);
|
||||
|
||||
/* TODO Handle empty centers properly */
|
||||
for (k = 0; k < c->dim; k++)
|
||||
c->x[k] = ((double) random()) / MAX_RANDOM_VALUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Step 4: For each center c, let m(c) be mean of all points assigned */
|
||||
for (j = 0; j < numCenters; j++)
|
||||
/* Normalize if needed */
|
||||
if (normprocinfo != NULL)
|
||||
{
|
||||
vec = VectorArrayGet(newCenters, j);
|
||||
for (k = 0; k < dimensions; k++)
|
||||
vec->x[k] = 0.0;
|
||||
|
||||
centerCounts[j] = 0;
|
||||
for (j = 0; j < centers->length; j++)
|
||||
ApplyNorm(normprocinfo, collation, VectorArrayGet(centers, j));
|
||||
}
|
||||
|
||||
for (j = 0; j < numSamples; j++)
|
||||
{
|
||||
vec = VectorArrayGet(samples, j);
|
||||
closestCenter = closestCenters[j];
|
||||
|
||||
/* Increment sum and count of closest center */
|
||||
newCenter = VectorArrayGet(newCenters, closestCenter);
|
||||
for (k = 0; k < dimensions; k++)
|
||||
newCenter->x[k] += vec->x[k];
|
||||
|
||||
centerCounts[closestCenter] += 1;
|
||||
}
|
||||
|
||||
for (j = 0; j < numCenters; j++)
|
||||
{
|
||||
vec = VectorArrayGet(newCenters, j);
|
||||
|
||||
if (centerCounts[j] > 0)
|
||||
{
|
||||
for (k = 0; k < dimensions; k++)
|
||||
vec->x[k] /= centerCounts[j];
|
||||
}
|
||||
else
|
||||
{
|
||||
/* TODO Handle empty centers properly */
|
||||
for (k = 0; k < dimensions; k++)
|
||||
vec->x[k] = RandomDouble();
|
||||
}
|
||||
|
||||
/* Normalize if needed */
|
||||
if (normprocinfo != NULL)
|
||||
ApplyNorm(normprocinfo, collation, vec);
|
||||
}
|
||||
|
||||
/* Step 5 */
|
||||
for (j = 0; j < numCenters; j++)
|
||||
newcdist[j] = DatumGetFloat8(FunctionCall2Coll(procinfo, collation, PointerGetDatum(VectorArrayGet(centers, j)), PointerGetDatum(VectorArrayGet(newCenters, j))));
|
||||
|
||||
for (j = 0; j < numSamples; j++)
|
||||
{
|
||||
for (k = 0; k < numCenters; k++)
|
||||
{
|
||||
distance = lowerBound[j * numCenters + k] - newcdist[k];
|
||||
|
||||
if (distance < 0)
|
||||
distance = 0;
|
||||
|
||||
lowerBound[j * numCenters + k] = distance;
|
||||
}
|
||||
}
|
||||
|
||||
/* Step 6 */
|
||||
/* We reset r(x) before Step 3 in the next iteration */
|
||||
for (j = 0; j < numSamples; j++)
|
||||
upperBound[j] += newcdist[closestCenters[j]];
|
||||
|
||||
/* Step 7 */
|
||||
for (j = 0; j < numCenters; j++)
|
||||
memcpy(VectorArrayGet(centers, j), VectorArrayGet(newCenters, j), VECTOR_SIZE(dimensions));
|
||||
|
||||
if (changes == 0 && iteration != 0)
|
||||
break;
|
||||
}
|
||||
|
||||
VectorArrayFree(newCenters);
|
||||
pfree(centerCounts);
|
||||
pfree(closestCenters);
|
||||
pfree(lowerBound);
|
||||
pfree(upperBound);
|
||||
pfree(s);
|
||||
pfree(halfcdist);
|
||||
pfree(newcdist);
|
||||
pfree(v);
|
||||
pfree(d);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -493,16 +399,48 @@ CheckCenters(Relation index, VectorArray centers)
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform naive k-means centering
|
||||
* Perform k-means clustering
|
||||
* We use spherical k-means for inner product and cosine
|
||||
*/
|
||||
void
|
||||
IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers)
|
||||
IvfflatKmeans(IvfflatBuildState * buildstate)
|
||||
{
|
||||
if (samples->length <= centers->maxlen)
|
||||
QuickCenters(index, samples, centers);
|
||||
else
|
||||
ElkanKmeans(index, samples, centers);
|
||||
int numSamples;
|
||||
Size totalSize;
|
||||
|
||||
CheckCenters(index, centers);
|
||||
/* Target 10 samples per list, with at least 10000 samples */
|
||||
/* The number of samples has a large effect on index build time */
|
||||
numSamples = buildstate->lists * 10;
|
||||
if (numSamples < 10000)
|
||||
numSamples = 10000;
|
||||
|
||||
/* Skip samples for unlogged table */
|
||||
if (buildstate->heap == NULL)
|
||||
numSamples = 1;
|
||||
|
||||
/* Calculate total size */
|
||||
totalSize = VECTOR_ARRAY_SIZE(numSamples, buildstate->dimensions);
|
||||
|
||||
/* Check memory requirements */
|
||||
/* Add one to error message to ceil */
|
||||
if (totalSize / 1024 > maintenance_work_mem)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
||||
errmsg("memory required is %zu MB, maintenance_work_mem is %d MB",
|
||||
totalSize / (1024 * 1024) + 1, maintenance_work_mem / 1024)));
|
||||
|
||||
/* Sample rows */
|
||||
buildstate->samples = VectorArrayInit(numSamples, buildstate->dimensions);
|
||||
if (buildstate->heap != NULL)
|
||||
SampleRows(buildstate);
|
||||
|
||||
if (buildstate->samples->length <= buildstate->centers->maxlen)
|
||||
QuickCenters(buildstate->index, buildstate->samples, buildstate->centers);
|
||||
else
|
||||
MiniBatchKmeans(buildstate);
|
||||
|
||||
CheckCenters(buildstate->index, buildstate->centers);
|
||||
|
||||
/* Free samples before we allocate more memory */
|
||||
pfree(buildstate->samples);
|
||||
}
|
||||
|
||||
@@ -10,25 +10,14 @@
|
||||
VectorArray
|
||||
VectorArrayInit(int maxlen, int dimensions)
|
||||
{
|
||||
VectorArray res = palloc(sizeof(VectorArrayData));
|
||||
VectorArray res = palloc0(VECTOR_ARRAY_SIZE(maxlen, dimensions));
|
||||
|
||||
res->length = 0;
|
||||
res->maxlen = maxlen;
|
||||
res->dim = dimensions;
|
||||
res->items = palloc_extended(maxlen * VECTOR_SIZE(dimensions), MCXT_ALLOC_ZERO | MCXT_ALLOC_HUGE);
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free a vector array
|
||||
*/
|
||||
void
|
||||
VectorArrayFree(VectorArray arr)
|
||||
{
|
||||
pfree(arr->items);
|
||||
pfree(arr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Print vector array - useful for debugging
|
||||
*/
|
||||
@@ -118,22 +107,13 @@ IvfflatNewBuffer(Relation index, ForkNumber forkNum)
|
||||
* Init page
|
||||
*/
|
||||
void
|
||||
IvfflatInitPage(Buffer buf, Page page)
|
||||
{
|
||||
PageInit(page, BufferGetPageSize(buf), sizeof(IvfflatPageOpaqueData));
|
||||
IvfflatPageGetOpaque(page)->nextblkno = InvalidBlockNumber;
|
||||
IvfflatPageGetOpaque(page)->page_id = IVFFLAT_PAGE_ID;
|
||||
}
|
||||
|
||||
/*
|
||||
* Init and register page
|
||||
*/
|
||||
void
|
||||
IvfflatInitRegisterPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state)
|
||||
IvfflatInitPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state)
|
||||
{
|
||||
*state = GenericXLogStart(index);
|
||||
*page = GenericXLogRegisterBuffer(*state, *buf, GENERIC_XLOG_FULL_IMAGE);
|
||||
IvfflatInitPage(*buf, *page);
|
||||
PageInit(*page, BufferGetPageSize(*buf), sizeof(IvfflatPageOpaqueData));
|
||||
IvfflatPageGetOpaque(*page)->nextblkno = InvalidBlockNumber;
|
||||
IvfflatPageGetOpaque(*page)->page_id = IVFFLAT_PAGE_ID;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -155,27 +135,17 @@ IvfflatCommitBuffer(Buffer buf, GenericXLogState *state)
|
||||
void
|
||||
IvfflatAppendPage(Relation index, Buffer *buf, Page *page, GenericXLogState **state, ForkNumber forkNum)
|
||||
{
|
||||
/* Get new buffer */
|
||||
Buffer newbuf = IvfflatNewBuffer(index, forkNum);
|
||||
Page newpage = GenericXLogRegisterBuffer(*state, newbuf, GENERIC_XLOG_FULL_IMAGE);
|
||||
Buffer prevbuf = *buf;
|
||||
|
||||
/* Update the previous buffer */
|
||||
IvfflatPageGetOpaque(*page)->nextblkno = BufferGetBlockNumber(newbuf);
|
||||
/* Get new buffer */
|
||||
*buf = IvfflatNewBuffer(index, forkNum);
|
||||
|
||||
/* Update and commit previous buffer */
|
||||
IvfflatPageGetOpaque(*page)->nextblkno = BufferGetBlockNumber(*buf);
|
||||
IvfflatCommitBuffer(prevbuf, *state);
|
||||
|
||||
/* Init new page */
|
||||
IvfflatInitPage(newbuf, newpage);
|
||||
|
||||
/* Commit */
|
||||
MarkBufferDirty(*buf);
|
||||
MarkBufferDirty(newbuf);
|
||||
GenericXLogFinish(*state);
|
||||
|
||||
/* Unlock */
|
||||
UnlockReleaseBuffer(*buf);
|
||||
|
||||
*state = GenericXLogStart(index);
|
||||
*page = GenericXLogRegisterBuffer(*state, newbuf, GENERIC_XLOG_FULL_IMAGE);
|
||||
*buf = newbuf;
|
||||
IvfflatInitPage(index, buf, page, state);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
150
src/vector.c
150
src/vector.c
@@ -13,10 +13,7 @@
|
||||
#include "utils/numeric.h"
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
#include "common/shortest_dec.h"
|
||||
#include "utils/float.h"
|
||||
#else
|
||||
#include <float.h>
|
||||
#endif
|
||||
|
||||
#if PG_VERSION_NUM < 130000
|
||||
@@ -109,14 +106,14 @@ PrintVector(char *msg, Vector * vector)
|
||||
/*
|
||||
* Convert textual representation to internal representation
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_in);
|
||||
PG_FUNCTION_INFO_V1(vector_in);
|
||||
Datum
|
||||
vector_in(PG_FUNCTION_ARGS)
|
||||
{
|
||||
char *str = PG_GETARG_CSTRING(0);
|
||||
int32 typmod = PG_GETARG_INT32(2);
|
||||
int i;
|
||||
float x[VECTOR_MAX_DIM];
|
||||
double x[VECTOR_MAX_DIM];
|
||||
int dim = 0;
|
||||
char *pt;
|
||||
char *stringEnd;
|
||||
@@ -139,8 +136,7 @@ vector_in(PG_FUNCTION_ARGS)
|
||||
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
||||
errmsg("vector cannot have more than %d dimensions", VECTOR_MAX_DIM)));
|
||||
|
||||
/* Use strtof like float4in to avoid a double-rounding problem */
|
||||
x[dim] = strtof(pt, &stringEnd);
|
||||
x[dim] = strtod(pt, &stringEnd);
|
||||
CheckElement(x[dim]);
|
||||
dim++;
|
||||
|
||||
@@ -186,68 +182,35 @@ vector_in(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Convert internal representation to textual representation
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_out);
|
||||
PG_FUNCTION_INFO_V1(vector_out);
|
||||
Datum
|
||||
vector_out(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *vector = PG_GETARG_VECTOR_P(0);
|
||||
StringInfoData buf;
|
||||
int dim = vector->dim;
|
||||
char *buf;
|
||||
char *ptr;
|
||||
int i;
|
||||
int n;
|
||||
|
||||
#if PG_VERSION_NUM < 120000
|
||||
int ndig = FLT_DIG + extra_float_digits;
|
||||
initStringInfo(&buf);
|
||||
|
||||
if (ndig < 1)
|
||||
ndig = 1;
|
||||
|
||||
#define FLOAT_SHORTEST_DECIMAL_LEN (ndig + 10)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Need:
|
||||
*
|
||||
* dim * (FLOAT_SHORTEST_DECIMAL_LEN - 1) bytes for
|
||||
* float_to_shortest_decimal_bufn
|
||||
*
|
||||
* dim - 1 bytes for separator
|
||||
*
|
||||
* 3 bytes for [, ], and \0
|
||||
*/
|
||||
buf = (char *) palloc(FLOAT_SHORTEST_DECIMAL_LEN * dim + 2);
|
||||
ptr = buf;
|
||||
|
||||
*ptr = '[';
|
||||
ptr++;
|
||||
appendStringInfoChar(&buf, '[');
|
||||
for (i = 0; i < dim; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
*ptr = ',';
|
||||
ptr++;
|
||||
}
|
||||
appendStringInfoString(&buf, ",");
|
||||
|
||||
#if PG_VERSION_NUM >= 120000
|
||||
n = float_to_shortest_decimal_bufn(vector->x[i], ptr);
|
||||
#else
|
||||
n = sprintf(ptr, "%.*g", ndig, vector->x[i]);
|
||||
#endif
|
||||
ptr += n;
|
||||
appendStringInfoString(&buf, float8out_internal(vector->x[i]));
|
||||
}
|
||||
*ptr = ']';
|
||||
ptr++;
|
||||
*ptr = '\0';
|
||||
appendStringInfoChar(&buf, ']');
|
||||
|
||||
PG_FREE_IF_COPY(vector, 0);
|
||||
PG_RETURN_CSTRING(buf);
|
||||
PG_RETURN_CSTRING(buf.data);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert type modifier
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_typmod_in);
|
||||
PG_FUNCTION_INFO_V1(vector_typmod_in);
|
||||
Datum
|
||||
vector_typmod_in(PG_FUNCTION_ARGS)
|
||||
{
|
||||
@@ -278,7 +241,7 @@ vector_typmod_in(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Convert external binary representation to internal representation
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_recv);
|
||||
PG_FUNCTION_INFO_V1(vector_recv);
|
||||
Datum
|
||||
vector_recv(PG_FUNCTION_ARGS)
|
||||
{
|
||||
@@ -310,7 +273,7 @@ vector_recv(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Convert internal representation to the external binary representation
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_send);
|
||||
PG_FUNCTION_INFO_V1(vector_send);
|
||||
Datum
|
||||
vector_send(PG_FUNCTION_ARGS)
|
||||
{
|
||||
@@ -330,7 +293,7 @@ vector_send(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Convert vector to vector
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector);
|
||||
PG_FUNCTION_INFO_V1(vector);
|
||||
Datum
|
||||
vector(PG_FUNCTION_ARGS)
|
||||
{
|
||||
@@ -345,7 +308,7 @@ vector(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Convert array to vector
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(array_to_vector);
|
||||
PG_FUNCTION_INFO_V1(array_to_vector);
|
||||
Datum
|
||||
array_to_vector(PG_FUNCTION_ARGS)
|
||||
{
|
||||
@@ -403,7 +366,7 @@ array_to_vector(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Convert vector to float4[]
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_to_float4);
|
||||
PG_FUNCTION_INFO_V1(vector_to_float4);
|
||||
Datum
|
||||
vector_to_float4(PG_FUNCTION_ARGS)
|
||||
{
|
||||
@@ -426,14 +389,12 @@ vector_to_float4(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Get the L2 distance between vectors
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(l2_distance);
|
||||
PG_FUNCTION_INFO_V1(l2_distance);
|
||||
Datum
|
||||
l2_distance(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
float *ax = a->x;
|
||||
float *bx = b->x;
|
||||
double distance = 0.0;
|
||||
double diff;
|
||||
|
||||
@@ -441,7 +402,7 @@ l2_distance(PG_FUNCTION_ARGS)
|
||||
|
||||
for (int i = 0; i < a->dim; i++)
|
||||
{
|
||||
diff = ax[i] - bx[i];
|
||||
diff = a->x[i] - b->x[i];
|
||||
distance += diff * diff;
|
||||
}
|
||||
|
||||
@@ -452,14 +413,12 @@ l2_distance(PG_FUNCTION_ARGS)
|
||||
* Get the L2 squared distance between vectors
|
||||
* This saves a sqrt calculation
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_l2_squared_distance);
|
||||
PG_FUNCTION_INFO_V1(vector_l2_squared_distance);
|
||||
Datum
|
||||
vector_l2_squared_distance(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
float *ax = a->x;
|
||||
float *bx = b->x;
|
||||
double distance = 0.0;
|
||||
double diff;
|
||||
|
||||
@@ -467,7 +426,7 @@ vector_l2_squared_distance(PG_FUNCTION_ARGS)
|
||||
|
||||
for (int i = 0; i < a->dim; i++)
|
||||
{
|
||||
diff = ax[i] - bx[i];
|
||||
diff = a->x[i] - b->x[i];
|
||||
distance += diff * diff;
|
||||
}
|
||||
|
||||
@@ -477,20 +436,18 @@ vector_l2_squared_distance(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Get the inner product of two vectors
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(inner_product);
|
||||
PG_FUNCTION_INFO_V1(inner_product);
|
||||
Datum
|
||||
inner_product(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
float *ax = a->x;
|
||||
float *bx = b->x;
|
||||
double distance = 0.0;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
for (int i = 0; i < a->dim; i++)
|
||||
distance += ax[i] * bx[i];
|
||||
distance += a->x[i] * b->x[i];
|
||||
|
||||
PG_RETURN_FLOAT8(distance);
|
||||
}
|
||||
@@ -498,20 +455,18 @@ inner_product(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Get the negative inner product of two vectors
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_negative_inner_product);
|
||||
PG_FUNCTION_INFO_V1(vector_negative_inner_product);
|
||||
Datum
|
||||
vector_negative_inner_product(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
float *ax = a->x;
|
||||
float *bx = b->x;
|
||||
double distance = 0.0;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
for (int i = 0; i < a->dim; i++)
|
||||
distance += ax[i] * bx[i];
|
||||
distance += a->x[i] * b->x[i];
|
||||
|
||||
PG_RETURN_FLOAT8(distance * -1);
|
||||
}
|
||||
@@ -519,14 +474,12 @@ vector_negative_inner_product(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Get the cosine distance between two vectors
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(cosine_distance);
|
||||
PG_FUNCTION_INFO_V1(cosine_distance);
|
||||
Datum
|
||||
cosine_distance(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
float *ax = a->x;
|
||||
float *bx = b->x;
|
||||
double distance = 0.0;
|
||||
double norma = 0.0;
|
||||
double normb = 0.0;
|
||||
@@ -535,9 +488,9 @@ cosine_distance(PG_FUNCTION_ARGS)
|
||||
|
||||
for (int i = 0; i < a->dim; i++)
|
||||
{
|
||||
distance += ax[i] * bx[i];
|
||||
norma += ax[i] * ax[i];
|
||||
normb += bx[i] * bx[i];
|
||||
distance += a->x[i] * b->x[i];
|
||||
norma += a->x[i] * a->x[i];
|
||||
normb += b->x[i] * b->x[i];
|
||||
}
|
||||
|
||||
PG_RETURN_FLOAT8(1 - (distance / (sqrt(norma) * sqrt(normb))));
|
||||
@@ -548,7 +501,7 @@ cosine_distance(PG_FUNCTION_ARGS)
|
||||
* Currently uses angular distance since needs to satisfy triangle inequality
|
||||
* Assumes inputs are unit vectors (skips norm)
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_spherical_distance);
|
||||
PG_FUNCTION_INFO_V1(vector_spherical_distance);
|
||||
Datum
|
||||
vector_spherical_distance(PG_FUNCTION_ARGS)
|
||||
{
|
||||
@@ -573,7 +526,7 @@ vector_spherical_distance(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Get the dimensions of a vector
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_dims);
|
||||
PG_FUNCTION_INFO_V1(vector_dims);
|
||||
Datum
|
||||
vector_dims(PG_FUNCTION_ARGS)
|
||||
{
|
||||
@@ -585,16 +538,15 @@ vector_dims(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Get the L2 norm of a vector
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_norm);
|
||||
PG_FUNCTION_INFO_V1(vector_norm);
|
||||
Datum
|
||||
vector_norm(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
float *ax = a->x;
|
||||
double norm = 0.0;
|
||||
|
||||
for (int i = 0; i < a->dim; i++)
|
||||
norm += ax[i] * ax[i];
|
||||
norm += a->x[i] * a->x[i];
|
||||
|
||||
PG_RETURN_FLOAT8(sqrt(norm));
|
||||
}
|
||||
@@ -602,23 +554,20 @@ vector_norm(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Add vectors
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_add);
|
||||
PG_FUNCTION_INFO_V1(vector_add);
|
||||
Datum
|
||||
vector_add(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
float *ax = a->x;
|
||||
float *bx = b->x;
|
||||
Vector *result;
|
||||
float *rx;
|
||||
int i;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
result = InitVector(a->dim);
|
||||
rx = result->x;
|
||||
for (int i = 0, imax = a->dim; i < imax; i++)
|
||||
rx[i] = ax[i] + bx[i];
|
||||
for (i = 0; i < a->dim; i++)
|
||||
result->x[i] = a->x[i] + b->x[i];
|
||||
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
@@ -626,23 +575,20 @@ vector_add(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Subtract vectors
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_sub);
|
||||
PG_FUNCTION_INFO_V1(vector_sub);
|
||||
Datum
|
||||
vector_sub(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
float *ax = a->x;
|
||||
float *bx = b->x;
|
||||
Vector *result;
|
||||
float *rx;
|
||||
int i;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
result = InitVector(a->dim);
|
||||
rx = result->x;
|
||||
for (int i = 0, imax = a->dim; i < imax; i++)
|
||||
rx[i] = ax[i] - bx[i];
|
||||
for (i = 0; i < a->dim; i++)
|
||||
result->x[i] = a->x[i] - b->x[i];
|
||||
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
@@ -671,7 +617,7 @@ vector_cmp_internal(Vector * a, Vector * b)
|
||||
/*
|
||||
* Less than
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_lt);
|
||||
PG_FUNCTION_INFO_V1(vector_lt);
|
||||
Datum
|
||||
vector_lt(PG_FUNCTION_ARGS)
|
||||
{
|
||||
@@ -684,7 +630,7 @@ vector_lt(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Less than or equal
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_le);
|
||||
PG_FUNCTION_INFO_V1(vector_le);
|
||||
Datum
|
||||
vector_le(PG_FUNCTION_ARGS)
|
||||
{
|
||||
@@ -697,7 +643,7 @@ vector_le(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Equal
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_eq);
|
||||
PG_FUNCTION_INFO_V1(vector_eq);
|
||||
Datum
|
||||
vector_eq(PG_FUNCTION_ARGS)
|
||||
{
|
||||
@@ -710,7 +656,7 @@ vector_eq(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Not equal
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_ne);
|
||||
PG_FUNCTION_INFO_V1(vector_ne);
|
||||
Datum
|
||||
vector_ne(PG_FUNCTION_ARGS)
|
||||
{
|
||||
@@ -723,7 +669,7 @@ vector_ne(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Greater than or equal
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_ge);
|
||||
PG_FUNCTION_INFO_V1(vector_ge);
|
||||
Datum
|
||||
vector_ge(PG_FUNCTION_ARGS)
|
||||
{
|
||||
@@ -736,7 +682,7 @@ vector_ge(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Greater than
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_gt);
|
||||
PG_FUNCTION_INFO_V1(vector_gt);
|
||||
Datum
|
||||
vector_gt(PG_FUNCTION_ARGS)
|
||||
{
|
||||
@@ -749,7 +695,7 @@ vector_gt(PG_FUNCTION_ARGS)
|
||||
/*
|
||||
* Compare vectors
|
||||
*/
|
||||
PGDLLEXPORT PG_FUNCTION_INFO_V1(vector_cmp);
|
||||
PG_FUNCTION_INFO_V1(vector_cmp);
|
||||
Datum
|
||||
vector_cmp(PG_FUNCTION_ARGS)
|
||||
{
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
SELECT ARRAY[1,2,3]::vector;
|
||||
array
|
||||
---------
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
CREATE TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
CREATE TABLE t2 (val vector(3));
|
||||
\copy t TO 'results/data.bin' WITH (FORMAT binary)
|
||||
\copy t2 FROM 'results/data.bin' WITH (FORMAT binary)
|
||||
\copy t TO '/tmp/data.bin' WITH (FORMAT binary)
|
||||
\copy t2 FROM '/tmp/data.bin' WITH (FORMAT binary)
|
||||
SELECT * FROM t2 ORDER BY val;
|
||||
val
|
||||
---------
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
SELECT '[1,2,3]'::vector + '[4,5,6]';
|
||||
?column?
|
||||
----------
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
SELECT '[1,2,3]'::vector;
|
||||
vector
|
||||
---------
|
||||
@@ -10,12 +12,6 @@ SELECT '[-1,2,3]'::vector;
|
||||
[-1,2,3]
|
||||
(1 row)
|
||||
|
||||
SELECT '[1.23456]'::vector;
|
||||
vector
|
||||
-----------
|
||||
[1.23456]
|
||||
(1 row)
|
||||
|
||||
SELECT '[hello,1]'::vector;
|
||||
ERROR: invalid input syntax for type vector: "hello"
|
||||
LINE 1: SELECT '[hello,1]'::vector;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
SET enable_seqscan = off;
|
||||
CREATE TABLE t (val vector(3));
|
||||
CREATE INDEX ON t USING ivfflat (val) WITH (lists = 0);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
SET enable_seqscan = off;
|
||||
CREATE UNLOGGED TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
use PostgreSQL::Test::Cluster;
|
||||
|
||||
sub get_new_node
|
||||
{
|
||||
return PostgreSQL::Test::Cluster->new(@_);
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,3 +0,0 @@
|
||||
use PostgreSQL::Test::Utils;
|
||||
|
||||
1;
|
||||
@@ -1,3 +1,5 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val vector(3));
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
|
||||
SELECT ARRAY[1,2,3]::vector;
|
||||
SELECT ARRAY[1.0,2.0,3.0]::vector;
|
||||
SELECT ARRAY[1,2,3]::float4[]::vector;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
|
||||
CREATE TABLE t (val vector(3));
|
||||
INSERT INTO t (val) VALUES ('[0,0,0]'), ('[1,2,3]'), ('[1,1,1]'), (NULL);
|
||||
|
||||
CREATE TABLE t2 (val vector(3));
|
||||
|
||||
\copy t TO 'results/data.bin' WITH (FORMAT binary)
|
||||
\copy t2 FROM 'results/data.bin' WITH (FORMAT binary)
|
||||
\copy t TO '/tmp/data.bin' WITH (FORMAT binary)
|
||||
\copy t2 FROM '/tmp/data.bin' WITH (FORMAT binary)
|
||||
|
||||
SELECT * FROM t2 ORDER BY val;
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
|
||||
SELECT '[1,2,3]'::vector + '[4,5,6]';
|
||||
SELECT '[1,2,3]'::vector - '[4,5,6]';
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
|
||||
SELECT '[1,2,3]'::vector;
|
||||
SELECT '[-1,2,3]'::vector;
|
||||
SELECT '[1.23456]'::vector;
|
||||
SELECT '[hello,1]'::vector;
|
||||
SELECT '[NaN,1]'::vector;
|
||||
SELECT '[Infinity,1]'::vector;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val vector(3));
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val vector(3));
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val vector(3));
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE TABLE t (val vector(3));
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
SET client_min_messages = warning;
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
SET enable_seqscan = off;
|
||||
|
||||
CREATE UNLOGGED TABLE t (val vector(3));
|
||||
|
||||
@@ -7,8 +7,6 @@ use PostgresNode;
|
||||
use TestLib;
|
||||
use Test::More tests => 31;
|
||||
|
||||
my $dim = 32;
|
||||
|
||||
my $node_primary;
|
||||
my $node_replica;
|
||||
|
||||
@@ -32,15 +30,13 @@ sub test_index_replay
|
||||
$node_primary->poll_query_until('postgres', $caughtup_query)
|
||||
or die "Timed out while waiting for replica 1 to catch up";
|
||||
|
||||
my @r = ();
|
||||
for (1 .. $dim) {
|
||||
push(@r, rand());
|
||||
}
|
||||
my $sql = join(",", @r);
|
||||
my $r1 = rand();
|
||||
my $r2 = rand();
|
||||
my $r3 = rand();
|
||||
|
||||
my $queries = qq(
|
||||
SET enable_seqscan = off;
|
||||
SELECT * FROM tst ORDER BY v <-> '[$sql]' LIMIT 10;
|
||||
SELECT * FROM tst ORDER BY v <-> '[$r1,$r2,$r3]' LIMIT 10;
|
||||
);
|
||||
|
||||
# Run test queries and compare their result
|
||||
@@ -51,18 +47,9 @@ sub test_index_replay
|
||||
return;
|
||||
}
|
||||
|
||||
# Use ARRAY[random(), random(), random(), ...] over
|
||||
# SELECT array_agg(random()) FROM generate_series(1, $dim)
|
||||
# to generate different values for each row
|
||||
my $array_sql = join(",", ('random()') x $dim);
|
||||
|
||||
# Initialize primary node
|
||||
$node_primary = get_new_node('primary');
|
||||
$node_primary->init(allows_streaming => 1);
|
||||
if ($dim > 32) {
|
||||
# TODO use wal_keep_segments for Postgres < 13
|
||||
$node_primary->append_conf('postgresql.conf', qq(wal_keep_size = 1GB));
|
||||
}
|
||||
$node_primary->start;
|
||||
my $backup_name = 'my_backup';
|
||||
|
||||
@@ -77,9 +64,9 @@ $node_replica->start;
|
||||
|
||||
# Create ivfflat index on primary
|
||||
$node_primary->safe_psql("postgres", "CREATE EXTENSION vector;");
|
||||
$node_primary->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector($dim));");
|
||||
$node_primary->safe_psql("postgres", "CREATE TABLE tst (i int4, v vector(3));");
|
||||
$node_primary->safe_psql("postgres",
|
||||
"INSERT INTO tst SELECT i % 10, ARRAY[$array_sql] FROM generate_series(1, 100000) i;"
|
||||
"INSERT INTO tst SELECT i % 10, ARRAY[random(), random(), random()] FROM generate_series(1, 100000) i;"
|
||||
);
|
||||
$node_primary->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v);");
|
||||
|
||||
@@ -95,7 +82,7 @@ for my $i (1 .. 10)
|
||||
test_index_replay("vacuum $i");
|
||||
my ($start, $end) = (100001 + ($i - 1) * 10000, 100000 + $i * 10000);
|
||||
$node_primary->safe_psql("postgres",
|
||||
"INSERT INTO tst SELECT i % 10, ARRAY[$array_sql] FROM generate_series($start, $end) i;"
|
||||
"INSERT INTO tst SELECT i % 10, ARRAY[random(), random(), random()] FROM generate_series($start, $end) i;"
|
||||
);
|
||||
test_index_replay("insert $i");
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use PostgresNode;
|
||||
use TestLib;
|
||||
use Test::More tests => 5;
|
||||
|
||||
my $dim = 768;
|
||||
|
||||
my $array_sql = join(",", ('random()') x $dim);
|
||||
|
||||
# Initialize node
|
||||
my $node = get_new_node('node');
|
||||
$node->init;
|
||||
$node->start;
|
||||
|
||||
# Create table and index
|
||||
$node->safe_psql("postgres", "CREATE EXTENSION vector;");
|
||||
$node->safe_psql("postgres", "CREATE TABLE tst (v vector($dim));");
|
||||
$node->safe_psql("postgres",
|
||||
"INSERT INTO tst SELECT ARRAY[$array_sql] FROM generate_series(1, 10000) i;"
|
||||
);
|
||||
$node->safe_psql("postgres", "CREATE INDEX ON tst USING ivfflat (v);");
|
||||
|
||||
$node->pgbench(
|
||||
"--no-vacuum --client=5 --transactions=100",
|
||||
0,
|
||||
[qr{actually processed}],
|
||||
[qr{^$}],
|
||||
"concurrent INSERTs",
|
||||
{
|
||||
"007_inserts" => "INSERT INTO tst SELECT ARRAY[$array_sql] FROM generate_series(1, 10) i;"
|
||||
}
|
||||
);
|
||||
|
||||
my $expected = 10000 + 5 * 100 * 10;
|
||||
|
||||
my $count = $node->safe_psql("postgres", "SELECT COUNT(*) FROM tst;");
|
||||
is($count, $expected);
|
||||
|
||||
$count = $node->safe_psql("postgres", qq(
|
||||
SET enable_seqscan = off;
|
||||
SET ivfflat.probes = 100;
|
||||
SELECT COUNT(*) FROM (SELECT v FROM tst ORDER BY v <-> (SELECT v FROM tst LIMIT 1)) t;
|
||||
));
|
||||
is($count, $expected);
|
||||
@@ -1,4 +1,4 @@
|
||||
comment = 'vector data type and ivfflat access method'
|
||||
default_version = '0.3.2'
|
||||
default_version = '0.2.5'
|
||||
module_pathname = '$libdir/vector'
|
||||
relocatable = true
|
||||
|
||||
Reference in New Issue
Block a user