В настоящее время я пытаюсь реализовать функцию в Postgres, которая будет вычислять косинусное расстояние между 2 реальными массивами ( real [] ).Массивы / векторы имеют длину 2000 элементов .
Я буду использовать эту функцию для поиска от 1 до n по 500.000 векторов (пока).
Я пытаюсь добиться наилучшей производительности, не задумываясь о том, чтобы выбросить оборудование / процессоры на сервер.
У меня уже есть успешное решение за пределами Postgres.Я кеширую данные в память и там я могу выполнить поиск косинуса под 1 с (используя ядро dotnet).Но чтобы подготовить это производство, требуется много времени на разработку.Прежде чем углубиться в это, я хочу убедиться, что я исчерпал все опции Postgres (Postgres уже используется во многих наших микро сервисах).
Ниже приведены варианты, которые я тестировал, и мои результаты:
1) функция plpgsql (Postgres 10.3)
Это был большой сбой - потребовалось 5 минут для поиска 500 000 строк - с распараллеливанием (2 рабочих).
2) c функцией с Postgres 10,3
Огромное улучшение - заняло 10 секунд , включая 2 рабочих распараллеливания
Источник
#include "postgres.h"
#include "fmgr.h"
#include "math.h"
#include <utils/array.h>
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
PG_FUNCTION_INFO_V1(cosine_distance_vector);
Datum cosine_distance_vector(PG_FUNCTION_ARGS)
{
ArrayType *input1ValuesArray, *input2ValuesArray;
float4 *input1Values, *input2Values;
float4 result;
float4 dot = 0.0;
float4 denom_a = 0.0;
float4 denom_b = 0.0;
input1ValuesArray = PG_GETARG_ARRAYTYPE_P(0);
input2ValuesArray = PG_GETARG_ARRAYTYPE_P(1);
input1Values = (float4 *) ARR_DATA_PTR(input1ValuesArray);
input2Values = (float4 *) ARR_DATA_PTR(input2ValuesArray);
for(unsigned int i = 0u; i < sizeof(input1Values); ++i) {
dot += input1Values[i] * input2Values[i] ;
denom_a += input1Values[i] * input1Values[i] ;
denom_b += input2Values[i] * input2Values[i] ;
}
result = dot / (sqrt(denom_a) * sqrt(denom_b));
PG_RETURN_FLOAT4(result);
}
3) c функцией с Postgres 11.1
Еще одно улучшение - заняло 9 секунд, включая распараллеливание 2 рабочих
Мои наблюдения оФункция C
Насколько я вижу, % 90 времени уходит на вызовы PG_GETARG_ARRAYTYPE_P ;
Я проверил это, сравнив 2 реализации
Реализация 1 заняла 9 секунд , чтобы завершить поиск=>
Datum cosine_distance_vector(PG_FUNCTION_ARGS)
{
ArrayType *input1ValuesArray, *input2ValuesArray;
float4 result = 0.0;
input1ValuesArray = PG_GETARG_ARRAYTYPE_P(0);
input2ValuesArray = PG_GETARG_ARRAYTYPE_P(1);
PG_RETURN_FLOAT4(result);
}
Внедрение 2 заняло 1,5 секунды для выполнения
Datum cosine_distance_vector(PG_FUNCTION_ARGS)
{
float4 result = 0.0;
PG_RETURN_FLOAT4(result);
}
Существует ли более быстрый или более конкретный способ ввода массивов / указателей Float4 вфункция вместо того, чтобы использовать универсальную функцию PG_GETARG_ARRAYTYPE_P?
Я также пытался реализовать эту функцию, используя Соглашение о вызовах версии 0 в Postgres 9.6 (10 и 11 не поддерживают их), так как это кажется болееэффективный (низкий уровень).Но я не смог успешно реализовать эту функцию.Даже примеры в документации Postgres вызывали ошибки сегментации.
Я буду использовать отдельную установку Dockerized Postgres для этой функции поиска, так что я открыт для любой версии postgres и любого типа трюка конфигурации.
Некоторая дополнительная информация на основе комментариев @ LaurenzAlbe.
Это SQL-запрос, который я использую, чтобы найти лучший результат:
SELECT
*
FROM
documents t
ORDER BY cosine_distance_vector(
t.vector,
ARRAY [1,1,1,....]::real[]) DESC
LIMIT 1
Массив огромен, поэтому я не вставил его полностью.
Вот результат EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
:
![Explain Screen Shot](https://i.stack.imgur.com/Ndce4.png)
2019-01-23 Прогресс
Я немного углубился в исходный код Postgres и сосредоточился на том, почему функция косинуса работала медленнее, когда функция PG_GETARG_ARRAYTYPE_P
былавызывается.
Итак, я сталкивался с вызовом этой функции в какой-то момент в fmgr.c
:
struct varlena *
pg_detoast_datum(struct varlena *datum)
{
if (VARATT_IS_EXTENDED(datum))
return heap_tuple_untoast_attr(datum);
else
return datum;
}
Если тип хранения вашего столбца расширенный , который добавляетзначительное превышение.
Размер строки таблицы векторов в общей сложности составил более 8192 байта, что по умолчанию является размером блока Postgres.Вот почему тип хранения векторного столбца был автоматически выбран как EXTENDED .Я попытался преобразовать его в PLAIN и безо всякой ошибки это сработало.Я попытался выполнить запрос и 500ms !
Но, поскольку мой размер строки теперь превышал 8192 (хотя тип хранения был успешно преобразован PLAIN), я не смог добавить новые строки в таблицубольше на INSERT стали жаловаться, что размер строки слишком большой.
Следующим шагом я скомпилировал postgres с 16KB blockize (у меня ушло некоторое время).В конце я смог создать идеальную таблицу с векторным хранилищем PLAIN с работающими INSERT.
Я проверил запрос с шагом 100К строк. Первые строки 100K, для запуска потребовалось 50 мс . На 200К строк это заняло 4 секунды ! - Теперь, я думаю, из-за размера блока 16K мне нужно найти баланс префекта .conf file settings . Я больше не могу оптимизировать функцию.