Ich habe versucht, mit Python Faiss zu laufen, Go, Rust

Einführung

Wie geht es dir im Whirlpool von Corona? Ich habe faiss mit Go und Rust verwendet, die vor kurzem mit dem Studium begonnen haben, um die zusätzliche Zeit für das Lernen und die Überprüfung aufzuwenden, die ich normalerweise nicht haben kann, weil ich nicht unnötig ausgehen muss. Ich habe beschlossen, es zu versuchen. Faiss ist eine beliebte Nachbarschaftssuchbibliothek von Facebook Resarch, die auch in Einführung der leicht nischenhaften Funktionen von faiss eingeführt wurde.

Implementierung

Dann möchte ich es in der Reihenfolge Python, Go, Rust implementieren.

python Erstens ist die Umweltkonstruktion. Wenn Sie dem Installationshandbuch der ursprünglichen Familie folgen, wird das Modul problemlos installiert. Es wird auch beschrieben, wie man mit "conda" installiert, aber ich persönlich habe eine bittere Erinnerung an die "conda" -Umgebung, also habe ich sie aus dem Quellcode erstellt. Als nächstes kommt der Quellcode. Das gleiche gilt für Go und Rust, aber ich wollte die Leistung später messen, also gebe ich das Protokoll mit json aus. Wenn Sie den Speicher nicht in jeder Iteration freigeben, wird der Speicher mit jeder Iteration weiter vergrößert. Setzen Sie daher die Variablen "del variable" und "gc.collect ()" am Ende, um den Speicher zwangsweise freizugeben. Es ist.

main.py


import gc
import logging
import sys
from time import time

import faiss
import numpy as np
from pythonjsonlogger import jsonlogger


def elapsed(f, *args, **kwargs):
    start = time()
    f(*args, **kwargs)
    elapsed_time = time() - start
    return elapsed_time


if __name__ == '__main__':
    # Prepare log.
    logger = logging.getLogger()
    formatter = jsonlogger.JsonFormatter('(levelname) (asctime) (pathname) (lineno) (message)')
    handler = logging.StreamHandler(sys.stdout)
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    logger.setLevel(logging.INFO)
    # Define basic information.
    d = 512
    N = 1e6
    nd = 10
    nbs = np.arange(N / nd, N + 1, N / nd).astype(int)
    nq = 10
    k = 5
    # Start measuring performance.
    for i in range(100):
        for nb in nbs:
            # Prepare data.
            xb = np.random.rand(nb, 512).astype(np.float32)
            xq = np.random.rand(nq, 512).astype(np.float32)
            # Construct index.
            index = faiss.IndexFlatL2(d)
            # Evaluate performance.
            elapsed_add = elapsed(index.add, xb)
            elapsed_search = elapsed(index.search, xq, k)
            # Log performance.
            logger.info('end one iteration.', extra={
                'i': i,
                'nb': nb,
                'elapsed_add': elapsed_add,
                'elapsed_search': elapsed_search
            })
            # Force to free memory.
            del xb
            del xq
            del index
            gc.collect()

Go Als nächstes geht es los. Dies beginnt auch mit dem Umgebungsbau. Für Go ist das alles! Da ich den Faiss-Wrapper nicht hatte, entschied ich mich für zhyon404 / faiss, was das einfachste Wrapping zu sein scheint. In diesem Repository wird die Umgebung von Docker bereitgestellt. Folgen Sie also der README-Datei und erstellen Sie Docker, um die Umgebung zu erstellen. Es war. Als nächstes kommt der Quellcode. Go verwendet außerdem "logrus.JSONFormatter", um das Protokoll in die JSON-Ausgabe auszugeben, und gibt den Speicher bei jeder Iteration frei. Insbesondere dem Faiss-Index ist im C-Bereich Speicher zugewiesen, sodass er nicht einfach freigegeben werden konnte, und es fiel mir schwer, eine Methode namens "faiss_go.FaissIndexFree" zu finden.

main.go


package main

import (
	"github.com/facebookresearch/faiss/faiss_go"
	log "github.com/sirupsen/logrus"
	"math/rand"
	"os"
	"runtime"
	"runtime/debug"
	"time"
)

func main() {
	// Prepare log.
	log.SetFormatter(&log.JSONFormatter{})
	log.SetOutput(os.Stdout)
	// Define basic information.
	d := 512
	nbs := []int{1e5, 2e5, 3e5, 4e5, 5e5, 6e5, 7e5, 8e5, 9e5, 1e6}
	nq := 10
	k := 5
	// Start measuring performance.
	for i := 0; i < 100; i++ {
		for _, nb := range nbs {
			// Prepare data.
			xb := make([]float32, d*nb)
			xq := make([]float32, d*nq)
			for i := 0; i < nb; i++ {
				for j := 0; j < d; j++ {
					xb[d*i+j] = rand.Float32()
				}
			}
			for i := 0; i < nq; i++ {
				for j := 0; j < d; j++ {
					xq[d*i+j] = rand.Float32()
				}
			}
			// Construct index.
			v := new(faiss_go.Faissindexflatl2)
			faiss_go.FaissIndexflatl2NewWith(&v, d)
			index := (*faiss_go.Faissindex)(v)
			// Evaluate performance.
			add_start := time.Now()
			faiss_go.FaissIndexAdd(index, nb, xb)
			add_end := time.Now()
			I := make([]int, k*nq)
			D := make([]float32, k*nq)
			search_start := time.Now()
			faiss_go.FaissIndexSearch(index, nq, xq, k, D, I)
			search_end := time.Now()
			// Log performance.
			log.WithFields(log.Fields{
				"i": i,
				"nb": nb,
				"elapsed_add": add_end.Sub(add_start).Seconds(),
				"elapsed_search": search_end.Sub(search_start).Seconds(),
			}).Info("end one iteration.")
			// Force to free memory.
			faiss_go.FaissIndexFree(index)
			runtime.GC()
			debug.FreeOSMemory()
		}
	}
}

Rust Endlich Rust. Auch hier ist es aus dem Umweltbau. Ich habe mich entschieden, Enet4 / faiss-rs, das auch in Docs.rs veröffentlicht wird, als Wrapper für faiss zu verwenden. Grundsätzlich können Sie es installieren, indem Sie [README] folgen (https://github.com/Enet4/faiss-rs#installing-as-a-dependency).

This will result in the dynamic library faiss_c ("libfaiss_c.so" in Linux), which needs to be installed in a place where your system will pick up. In Linux, try somewhere in the LD_LIBRARY_PATH environment variable, such as "/usr/lib", or try adding a new path to this variable.

Es ist wichtig, nicht zu vergessen, den Pfad zur Bibliothek hinzuzufügen. Abhängig von der Umgebung scheint es auch nicht zu funktionieren, wenn es nicht zu "LIBRARY_PATH" hinzugefügt wird. Als nächstes kommt der Quellcode. Dies verwendet auch "json_logger", um das Protokoll an json auszugeben. Ich habe struct gemäß dem Beispiel definiert, aber ich frage mich, ob es einen besseren Weg gibt. Ich denke. Da die Zufallszahlengenerierung von Rust langsam und die Leistungsmessung schwierig war, verwenden Sie "rand_xorshift" unter Bezugnahme auf Unterschied in der Verarbeitungsgeschwindigkeit beim Generieren von Zufallszahlen mit Rust. Ich habe es gemacht. Interessant war, dass es im Gegensatz zu Python und Go möglich war, es zu implementieren, ohne sich der Speicherfreigabe besonders bewusst zu sein, obwohl die Speicherzuweisung im C-Bereich beteiligt war.

Cargo.toml


[package]
name    = "rust"
version = "0.1.0"
authors = []
edition = "2018"

[dependencies]
faiss           = "0.8.0"
json_logger     = "0.1"
log             = "0.4"
rand            = "0.7"
rand_xorshift   = "0.2"
rustc-serialize = "0.3"

main.rs


use faiss::{Index, index_factory, MetricType};
use log::{info, LevelFilter};
use rand::{RngCore, SeedableRng};
use rand_xorshift::XorShiftRng;
use rustc_serialize::json;
use std::time::Instant;

#[derive(RustcEncodable)]
struct LogMessage<'a> {
    msg: &'a str,
    i: i32,
    nb: i32,
    elapsed_add: f32,
    elapsed_search: f32
}

fn main() {
    // Prepare log.
    json_logger::init("faiss", LevelFilter::Info).unwrap();
    // Define basic information.
    let d: i32 = 512;
    const N: i32 = 1_000_000;
    let nd: i32 = 10;
    let nbs: Vec<i32> = (N/nd..N+1).step_by((N/nd) as usize).collect();
    let nq: i32 = 10;
    let k: usize = 5;
    let mut rng: XorShiftRng = SeedableRng::from_entropy();
    // Start measuring performance.
    for i in 0..100 {
        for &nb in nbs.iter() {
            // Prepare data.
            let xb: Vec<f32> = (0..nb*d).map(|_| rng.next_u32() as f32 / u32::max_value() as f32).collect();
            let xq: Vec<f32> = (0..nq*d).map(|_| rng.next_u32() as f32 / u32::max_value() as f32).collect();
            // Construct index.
            let mut index = index_factory(d as u32, "Flat", MetricType::L2).unwrap();
            // Evaluate performance.
            let start = Instant::now();
            index.add(&xb).unwrap();
            let elapsed_add = start.elapsed().as_micros() as f32 / 1e6;
            let start = Instant::now();
            index.search(&xq, k).unwrap();
            let elapsed_search = start.elapsed().as_micros() as f32 / 1e6;
            // Log performance.
            info!("{}", json::encode(&LogMessage {
                msg: "end one iteration.", i, nb, elapsed_add, elapsed_search
            }).unwrap());
        }
    }
}

Leistungsüberprüfung

Nun, selbst wenn Sie Python, GO oder Rust verwenden, wird nur die C-API von faiss umschlossen, sodass ich dachte, dass es keinen Unterschied in der Leistung geben würde, aber da ich es implementiert habe, habe ich beschlossen, die Leistung zu messen. .. OpenBLAS wurde für die Matrixberechnungsbibliothek verwendet, m5.large von AWS EC2 wurde für die Umgebung verwendet, und AMI wurde mit "Canonical, Ubuntu, 18.04 LTS, amd64 bionic image build on 2020-01-12" gemessen. Die folgende Grafik zeigt die durchschnittliche Verarbeitungszeit für jede Anzahl von Daten, wobei die Anzahl der Zieldaten für das Suchen und Hinzufügen zwischen 10 ^ 5 $ und 10 ^ 6 $ 100 Mal verschoben wird.

add add result

search Screenshot_2020-04-07 Analyze Performance(2).png

In allen Sprachen nimmt die Verarbeitungszeit linear mit der Zunahme der Datenanzahl zu. Mit add gab es fast keinen Unterschied in der Leistung zwischen den drei Sprachen, aber mit der Suche war das Ergebnis, dass nur Python eine bessere Leistung hatte. Ich war ein wenig überrascht, weil ich dachte, dass es keinen Unterschied in der Leistung geben würde, weil es sich um ähnliche Wrapper handelt. Wenn Sie außerdem "Go-Verarbeitungszeit / Python-Verarbeitungszeit" zeichnen, um zu sehen, wie sich dieser Leistungsunterschied in Abhängigkeit von der Anzahl der Daten ändert.

performance ratio

Es scheint, dass Go und Rust langsamer sind als Python, mit einem Durchschnitt von 1,44-mal, unabhängig von der Anzahl der Daten.

Zusammenfassung

Ich habe versucht, faiss, eine Nachbarschaftssuchbibliothek von Facebook Research, mit Python, Go und Rust zu verwenden, und die Leistung gemessen. Als ich versuchte, dieselbe Bibliothek in verschiedenen Sprachen zu verwenden, war es interessant, so etwas wie eine Gewohnheit jeder Sprache zu sehen. Insbesondere fand ich es ermutigend, dass Rust sogar den im C-Bereich zugewiesenen Speicher ordnungsgemäß freigibt. Die Sprache hat sich seit der Implementierung in C erheblich weiterentwickelt. In Bezug auf die Leistung sind Go und Rust ungefähr gleich und nur Python ist 1,44-mal schneller. Ich war überrascht anzunehmen, dass die Leistung insofern gleich sein würde, als alle drei Sprachen in C geschriebene Faiss-Wrapper sind. Nur Python wird offiziell unterstützt, und die Kompilierungsmethode unterscheidet sich zwischen Python und Go / Rust. Daher frage ich mich, ob dies eine Rolle spielt. Diesmal wird es interessant sein, dort tief zu graben!

Recommended Posts

Ich habe versucht, mit Python Faiss zu laufen, Go, Rust
Ich habe es mit Grumpy versucht (Python ausführen).
Ich habe versucht, Prolog mit Python 3.8.2 auszuführen.
Ich habe versucht, Deep Floor Plan mit Python 3.6.10 auszuführen.
Ich habe fp-Wachstum mit Python versucht
Ich habe gRPC mit Python ausprobiert
Ich habe versucht, mit Python zu kratzen
Ich habe versucht, WebScraping mit Python.
Ich habe die SMTP-Kommunikation mit Python versucht
Ich habe versucht, Movidius NCS mit Python von Raspberry Pi3 auszuführen
Python mit Go
Ich habe versucht, mit Python + opencv nicht realistisch zu rendern
Ich habe eine funktionale Sprache mit Python ausprobiert
Ich habe versucht, mit Python ② (Fibonacci-Zahlenfolge) aufzuklären.
# Ich habe so etwas wie Vlookup mit Python # 2 ausprobiert
Ich habe versucht, das Bild mit Python + OpenCV zu "glätten"
Ich habe Hunderte Millionen SQLite mit Python ausprobiert
Ich habe versucht, Pymc auszuführen
Ich habe versucht, das Bild mit Python + OpenCV zu "differenzieren"
Ich habe Python> autopep8 ausprobiert
Ich habe Jacobian und teilweise Differenzierung mit Python versucht
Ich habe versucht, CloudWatch-Daten mit Python abzurufen
Ich habe versucht, Mecab mit Python2.7, Ruby2.3, PHP7 zu verwenden
Ich habe Funktionssynthese und Curry mit Python versucht
Ich habe versucht, LLVM IR mit Python auszugeben
Ich habe versucht, das Bild mit Python + OpenCV zu "binarisieren"
Ich habe versucht, die Herstellung von Sushi mit Python zu automatisieren
Ich habe versucht, Python -m summpy.server -h 127.0.0.1 -p 8080 auszuführen
Ich habe versucht, alembic auszuführen, ein Migrationstool für Python
Ich habe versucht, eine E-Mail mit SendGrid + Python zu senden
Ich habe Python> Decorator ausprobiert
Ich habe versucht, TensorFlow auszuführen
Ich habe jeden Tag LeetCode ausprobiert. 7. Reverse Integer (Python, Go)
Ich habe versucht, BERT mit Sakura VPS (ohne GPU) auszuführen.
Ich habe versucht, mit Blenders Python script_Part 01 zu beginnen
Ich habe versucht, eine CSV-Datei mit Python zu berühren
[OpenCV / Python] Ich habe versucht, Bilder mit OpenCV zu analysieren
Ich habe versucht, Python aus einer Bat-Datei auszuführen
Ich habe versucht, mit Blenders Python script_Part 02 zu beginnen
Ich habe versucht, künstliches Perzeptron mit Python zu implementieren
Ich habe jeden Tag 20 LeetCode ausprobiert. Gültige Klammern (Python, Go)
Mayungos Python-Lernfolge 1: Ich habe versucht, mit Druck zu drucken
[Python] Ich habe versucht, einen lokalen Server mit flask auszuführen
Ich habe versucht, das Problem mit Python Vol.1 zu lösen
Ich habe jeden Tag LeetCode 9 ausprobiert. Palindrome Number (Python, Go)
Ich habe jeden Tag LeetCode ausprobiert. 1. Zwei Summen (Python, Go)
Ich habe versucht, das Bild mit Python + OpenCV "morphologisch zu konvertieren"
Ich habe versucht, die API mit dem Python-Client von echonest zu erreichen
Ich habe versucht, AOJs Integer-Theorie mit Python zu lösen
Ich habe versucht, mit Elasticsearch Ranking zu lernen!