performance - Le prestazioni differiscono tra la codifica Python o C++di OpenCV?



(4)

Il mio obiettivo è di iniziare a poco a poco, ma prima devo decidere quale API di OpenCV è più utile. Prevedo che l'implementazione di Python sia più breve ma il tempo di esecuzione sarà più denso e lento rispetto alle implementazioni native di C ++. C'è qualche informazione in grado di commentare le differenze di prestazioni e di codifica tra queste due prospettive?


Answers

Come accennato nelle precedenti risposte, Python è più lento rispetto a C ++ o C. Python è costruito per semplicità, portabilità e, inoltre, creatività in cui gli utenti devono preoccuparsi solo del loro algoritmo, non di problemi di programmazione.

Ma qui in OpenCV, c'è qualcosa di diverso. Python-OpenCV è solo un wrapper attorno al codice C / C ++ originale. Viene normalmente utilizzato per combinare le migliori caratteristiche di entrambi i linguaggi, Prestazioni di C / C ++ e Semplicità di Python .

Quindi quando chiamate una funzione in OpenCV da Python, ciò che effettivamente viene eseguito è all'origine C / C ++ sottostante. Quindi non ci sarà molta differenza nelle prestazioni. (Ricordo che ho letto da qualche parte che la penalizzazione delle prestazioni è <1%, non ricordo dove. Una stima approssimativa con alcune funzioni di base in OpenCV mostra una penalizzazione nel caso peggiore <4% . ie penalty = [maximum time taken in Python - minimum time taken in C++]/minimum time taken in C++ ).

Il problema sorge quando il tuo codice ha molti codici Python nativi. Ad esempio, se stai creando le tue funzioni che non sono disponibili in OpenCV, le cose peggiorano. Tali codici sono eseguiti in modo nativo in Python, il che riduce notevolmente le prestazioni.

Ma la nuova interfaccia OpenCV-Python ha pieno supporto per Numpy. Numpy è un pacchetto per il calcolo scientifico in Python. È anche un wrapper attorno al codice C nativo. È una libreria altamente ottimizzata che supporta un'ampia varietà di operazioni con le matrici, altamente adatta all'elaborazione delle immagini. Quindi, se riesci a combinare correttamente le funzioni OpenCV e Numpy, otterrai un codice ad altissima velocità.

La cosa da ricordare è, cerca sempre di evitare loop e iterazioni in Python. Invece, utilizzare le funzioni di manipolazione degli array disponibili in Numpy (e OpenCV). Semplicemente aggiungendo due array numpy usando C = A+B è molto più veloce rispetto all'utilizzo di doppi loop.

Ad esempio, puoi controllare questi articoli:

  1. Manipolazione di array veloce in Python
  2. Confronto delle prestazioni delle interfacce OpenCV-Python, cv e cv2

Hai ragione, Python è quasi sempre molto più lento del C ++ in quanto richiede un interprete, che C ++ no. Tuttavia, ciò richiede che il C ++ sia fortemente tipizzato, il che lascia un margine di errore molto più piccolo. Alcune persone preferiscono essere rigorosamente codificate, mentre altre preferiscono l'indulgenza intrinseca di Python.

Se vuoi un discorso completo sugli stili di codifica Python rispetto agli stili di codifica C ++, questo non è il posto migliore, prova a cercare un articolo.

EDIT: Poiché Python è un linguaggio interpretato, mentre il C ++ è compilato fino al codice macchina, in generale , è possibile ottenere vantaggi prestazionali utilizzando C ++. Tuttavia, per quanto riguarda l'uso di OpenCV, le librerie OpenCV core sono già compilate in codice macchina, quindi il wrapper Python attorno alla libreria OpenCV sta eseguendo codice compilato. In altre parole, quando si tratta di eseguire algoritmi OpenCV computazionalmente costosi da Python, non si vedrà gran parte del successo di una performance dal momento che sono già stati compilati per l'architettura specifica con cui si sta lavorando.


Tutti i risultati di google per openCV dichiarano lo stesso: quel python sarà solo leggermente più lento. Ma non una volta ho visto alcun profilo su questo. Così ho deciso di fare un po 'e ho scoperto:

Python è molto più lento di C ++ con opencv, anche per programmi banali.

L'esempio più semplice a cui potevo pensare era quello di visualizzare l'output di una webcam sullo schermo e visualizzare il numero di fotogrammi al secondo. Con python, ho raggiunto 50FPS (su un atomo Intel). Con C ++, ho ottenuto 65 FPS, con un incremento del 25%. In entrambi i casi, l'utilizzo della CPU utilizzava un singolo core e, per quanto ne so, era vincolato dalle prestazioni della CPU. Inoltre, questo caso di test riguarda l'allineamento con ciò che ho visto nei progetti che ho trasferito da uno all'altro in passato.

Da dove viene questa differenza? In python, tutte le funzioni openCV restituiscono nuove copie delle matrici dell'immagine. Ogni volta che acquisisci un'immagine o ridimensionala, in C ++ puoi riutilizzare la memoria esistente. In Python non puoi. Sospetto che questo tempo dedicato all'allocazione della memoria sia la principale differenza, perché come altri hanno detto: il codice sottostante di openCV è C ++.

Prima di lanciare python fuori dalla finestra: python è molto più veloce da sviluppare, e se non si eseguono vincoli hardware, o se la velocità di sviluppo è più importante delle prestazioni, allora usa python. In molte applicazioni che ho fatto con openCV, ho iniziato con Python e successivamente ho convertito solo i componenti di visione del computer in C ++ (es. Usando il modulo ctype di python e compilando il codice CV in una libreria condivisa).

Codice Python:

import cv2
import time

FPS_SMOOTHING = 0.9

cap = cv2.VideoCapture(2)
fps = 0.0
prev = time.time()
while True:
    now = time.time()
    fps = (fps*FPS_SMOOTHING + (1/(now - prev))*(1.0 - FPS_SMOOTHING))
    prev = now

    print("fps: {:.1f}".format(fps))

    got, frame = cap.read()
    if got:
        cv2.imshow("asdf", frame)
    if (cv2.waitKey(2) == 27):
        break

Codice C ++:

#include <opencv2/opencv.hpp>
#include <stdint.h>

using namespace std;
using namespace cv;

#define FPS_SMOOTHING 0.9

int main(int argc, char** argv){
    VideoCapture cap(2);
    Mat frame;

    float fps = 0.0;
    double prev = clock(); 
    while (true){
        double now = (clock()/(double)CLOCKS_PER_SEC);
        fps = (fps*FPS_SMOOTHING + (1/(now - prev))*(1.0 - FPS_SMOOTHING));
        prev = now;

        printf("fps: %.1f\n", fps);

        if (cap.isOpened()){
            cap.read(frame);
        }
        imshow("asdf", frame);
        if (waitKey(2) == 27){
            break;
        }
    }
}

Possibili limiti del benchmark:

  • Frame rate della telecamera
  • Timer che misura la precisione
  • Tempo trascorso nella formattazione della stampa

Sono qualche anno indietro qui, ma:

In "Modifica 4/5/6" del post originale, stai utilizzando la costruzione:

$ /usr/bin/time cat big_file | program_to_benchmark

Questo è sbagliato in un paio di modi diversi:

  1. In realtà stai cronometrando l'esecuzione di `cat`, non il tuo punto di riferimento. L'utilizzo della CPU 'user' e 'sys' visualizzato da `time` sono quelli di` cat`, non il tuo programma benchmark. Ancora peggio, il tempo "reale" non è necessariamente preciso. A seconda dell'implementazione di `cat` e delle pipeline nel sistema operativo locale, è possibile che` cat` scriva un buffer gigante finale ed esce molto prima che il processo del lettore finisca il suo lavoro.

  2. L'uso di `cat` è inutile e in effetti controproducente; stai aggiungendo parti mobili. Se si fosse su un sistema sufficientemente vecchio (cioè con una singola CPU e - in alcune generazioni di computer - I / O più veloce della CPU) - il semplice fatto che `cat` fosse in esecuzione poteva sostanzialmente colorare i risultati. Sei anche soggetto a qualunque buffering di input e output e ad altre operazioni di `cat`. (Probabilmente questo ti guadagnerebbe un premio "Uso inutile del gatto" se fossi Randal Schwartz.

Una costruzione migliore sarebbe:

$ /usr/bin/time program_to_benchmark < big_file

In questa istruzione è la shell che apre big_file, passandola al tuo programma (beh, in realtà a `time` che poi esegue il tuo programma come sottoprocesso) come un descrittore di file già aperto. Il 100% della lettura del file è strettamente responsabilità del programma che stai cercando di confrontare. Questo ti dà una lettura reale delle sue prestazioni senza complicazioni spurie.

Citerò due possibili, ma in realtà sbagliati, "correzioni" che potrebbero anche essere considerate (ma le ho "numerate" diversamente dal momento che queste non sono cose che erano sbagliate nel post originale):

R. Potresti "sistemare" questo temporizzando solo il tuo programma:

$ cat big_file | /usr/bin/time program_to_benchmark

B. o cronometrando l'intera pipeline:

$ /usr/bin/time sh -c 'cat big_file | program_to_benchmark'

Questi sono sbagliati per gli stessi motivi del # 2: stanno ancora usando `cat` inutilmente. Li ho citati per alcuni motivi:

  • sono più "naturali" per le persone che non sono del tutto a proprio agio con le funzioni di reindirizzamento I / O della shell POSIX

  • ci possono essere casi in cui `cat` è necessario (ad esempio: il file da leggere richiede una sorta di privilegio per accedere, e non si vuole concedere tale privilegio al programma da sottoporre a benchmark:` sudo cat / dev / sda | / usr / bin / time my_compression_test --no-output`)

  • in pratica , su macchine moderne, il `gatto` aggiunto nella pipeline non ha probabilmente alcuna conseguenza reale

Ma io dico quell'ultima cosa con un po 'di esitazione. Se esaminiamo l'ultimo risultato in "Modifica 5" -

$ /usr/bin/time cat temp_big_file | wc -l
0.01user 1.34system 0:01.83elapsed 74%CPU ...

- questo afferma che `cat` ha consumato il 74% della CPU durante il test; e infatti 1.34 / 1.83 è circa il 74%. Forse una corsa di:

$ /usr/bin/time wc -l < temp_big_file

avrei preso solo i restanti 49 secondi! Probabilmente no: `cat` qui ha dovuto pagare per le chiamate di sistema read () (o equivalenti) che hanno trasferito il file da 'disk' (in effetti buffer cache), così come le pipe pipe per consegnarle a` wc`. Il test corretto avrebbe comunque dovuto fare quelle chiamate read (); solo le chiamate write-to-pipe e read-from-pipe sarebbero state salvate e quelle dovrebbero essere piuttosto economiche.

Tuttavia, prevedo che saresti in grado di misurare la differenza tra `cat file | wc -l` e `wc -l <file` e troviamo una differenza notevole (percentuale a due cifre). Ciascuno dei test più lenti avrà pagato una penalità simile in tempo assoluto; che tuttavia equivarrebbe ad una frazione più piccola del suo tempo totale più ampio.

In effetti ho eseguito alcuni test rapidi con un file di 1,5 gigabyte di garbage, su un sistema Linux 3.13 (Ubuntu 14.04), ottenendo questi risultati (questi sono in realtà i risultati "migliori di 3", dopo aver utilizzato la cache, ovviamente):

$ time wc -l < /tmp/junk
real 0.280s user 0.156s sys 0.124s (total cpu 0.280s)
$ time cat /tmp/junk | wc -l
real 0.407s user 0.157s sys 0.618s (total cpu 0.775s)
$ time sh -c 'cat /tmp/junk | wc -l'
real 0.411s user 0.118s sys 0.660s (total cpu 0.778s)

Si noti che i due risultati della pipeline dichiarano di aver impiegato più tempo CPU (utente + sys) rispetto al tempo reale. Questo perché sto usando il comando "time" incorporato della shell (Bash), che è a conoscenza della pipeline; e sono su una macchina multi-core in cui processi separati in una pipeline possono utilizzare core separati, accumulando tempo di CPU più veloce del tempo reale. Usando / usr / bin / time vedo un tempo di CPU più piccolo del tempo reale - mostrando che può solo passare il tempo in cui il singolo elemento della pipeline passa ad esso sulla sua linea di comando. Inoltre, l'output della shell fornisce i millisecondi mentre / usr / bin / time dà solo un secondo di hundreth.

Quindi, a livello di efficienza di `wc -l`,` cat` fa una grande differenza: 409/283 = 1.453 o 45.3% in più in tempo reale, e 775/280 = 2.768, o un enorme 177% in più di CPU utilizzata! Sulla mia casella di prova casualmente era-lì-al-tempo.

Dovrei aggiungere che c'è almeno un'altra differenza significativa tra questi stili di test, e non posso dire se sia un vantaggio o un difetto; devi decidere da solo:

Quando esegui `cat big_file | / usr / bin / time my_program`, il tuo programma riceve input da una pipe, precisamente al ritmo inviato da `cat`, e in blocchi non più grandi di quelli scritti da` cat`.

Quando esegui `/ usr / bin / time my_program <big_file`, il tuo programma riceve un descrittore di file aperto nel file vero e proprio. Il tuo programma - o in molti casi le librerie di I / O della lingua in cui è stato scritto - può prendere diverse azioni quando viene presentato con un descrittore di file che fa riferimento a un file normale. Può utilizzare mmap (2) per mappare il file di input nel suo spazio indirizzo, invece di utilizzare chiamate di sistema esplicite di lettura (2). Queste differenze potrebbero avere un effetto molto più grande sui risultati del benchmark rispetto al piccolo costo dell'esecuzione del file binario cat.

Ovviamente è un interessante risultato di benchmark se lo stesso programma si comporta in modo significativamente diverso tra i due casi. Dimostra che, in effetti, il programma o le sue librerie di I / O stanno facendo qualcosa di interessante, come usare mmap (). Quindi, in pratica, potrebbe essere utile eseguire i benchmark in entrambe le direzioni; forse scontando il risultato di `cat` con qualche piccolo fattore per" perdonare "il costo dell'esecuzione di` cat` stesso.





c++ python performance opencv