Pregiudizi su python e numpy

A volte le nostre convinzioni sono basate sul nulla e alla prova dei fatti sono sbagliate.

Il python è un linguaggio interpretato e come tale è lento: è vero ma a volte a complicare
le cose ci si mettono anche i nostri pregiudizi.

Dovevo produrre un vettore e per farlo, nella notte dei tempi, avevo scritto una funzioncina stupida:

def old_grid_generator(): Psealevel = 1000.0 scale_height = 6.0 Zdummy = np.concatenate((np.arange(80,55,-2),np.arange(54,4,-1))) return Psealevel * np.exp(-Zdummy / scale_height)
Code language: Python (python)

Come si intuisce ho usato la libreria numpy nella convinzione che la libreria, compilata dal C, facesse un buon lavoro. Ma siccome il mio cervello funziona male, qualche tempo fa, riguardandola, mi sono chiesto se questa cosa fosse vera. Allora mi sono messo a pensare 5 minuti a come avrei potuto scrivere del codice “pure python” ragionevolmente veloce, il primo risultato è stato questo:

def old_grid_generator_2(): Psealevel = 1000.0 scale_height = 6.0 from math import exp return ( Psealevel * exp(-zz / scale_height) for zz in (*range(80,55,-2),*range(54,4,-1)) )
Code language: Python (python)

confrontando i tempi che impiegano a girare:

In [302]: %timeit old_grid_generator() 5.38 µs ± 40 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) In [333]: %timeit old_grid_generator_2() 1.39 µs ± 28 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Code language: CSS (css)

Meglio se importo exp fuori dalla funzione

In [32]: %timeit old_grid_generator_2() 925 ns ± 9.11 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Code language: CSS (css)

Ancora meglio se uso la funzione itertools.chain per concatenare 2 generatori senza passare per la tupla, per convenienza ne ho fatto una terza versione.

from itertools import chain def old_grid_generator_3(): Psealevel = 1000.0 scale_height = 6.0 return ( Psealevel * exp(-zz / scale_height) for zz in chain(range(80,55,-2),range(54,4,-1))) In [114]: %timeit old_grid_generator_3() 516 ns ± 2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Code language: JavaScript (javascript)

Ah, quindi in questo caso particolare il python da solo fa 10 volte meglio di numpy.
Vabbè, mi sono detto, sto barando: la prima versione tira fuori un utilissimo
numpy.array mentre la seconda un banalissimo generatore. Allora:

In [116]: %timeit np.fromiter(old_grid_generator_3(),float) 9.47 µs ± 152 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
Code language: CSS (css)

Viene fuori che se gli faccio costruire il vettore numpy il tempo è 2 volte quello che ci mette la versione nativa numpy: se lo riprende con gli interessi. Quindi scrivere codice che usa le numpy nativamente è più efficiente che scrivere codice veloce in puro python e poi convertire i risultati in numpy.

Allora mi chiedo se a richiedere tempo è il vettore numpy o il fatto di voler “mettere a terra” il risultato immagazzinandolo in memoria.

Per testare questa ipotesi genero una tupla, oggetto decisamente più snello di un array numpy:

In [138]: %timeit tuple(old_grid_generator_3()) 7.49 µs ± 29.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
Code language: CSS (css)

Ottengo risultati simili se la tupla la genero all’interno della funzione.

Evidentemente l’operazione che prende tempo è chiedere che i valori vengano immagazzinati in un oggetto in memoria.

Ne deduco che se riuscissimo a ottenere tutti i nostri risultati usando soltanto generatori, senza mai appoggiarli in memoria, se non in ultima istanza, probabilmente otterremmo del codice rapido (confrontabile o quasi coi risultati delle numpy) e molto meno pesante in memoria. Per dirla in modo più sintetico, se scrivessimo il codice in modo un po’ più “funzionale”.

Non sono sicuro che sia sempre fattibile…

Quel che resterebbe da capire è come questo comportamento scali all’aumentare del numero di elementi del vettore.

Questo semplice esempio fa vedere è come in alcuni casi (attenzione a generalizzare) il design progettuale del “nuovo” python3 e quello delle numpy sono in aperto disaccordo.

Ma l’insegnamento più importante è quello di fermarsi un attimo a pensare prima di scrivere del codice, anche quello che svolge lavori banali.

Se SSH si stacca

In tempi di lavoro forzatamente remoto di alcune cose, anche piccole, si sente maggiormente l’esigenza. E l’esigenza è sempre la molla che spinge verso il miglioramento e la ricerca delle soluzioni.

Premetto che una parte piuttosto importante del lavoro, mio e dei miei colleghi, viene svolgo via SSH sui server che gestisco.

Accade che le sessioni SSH sui miei server si disconnettano in maniera sistematica dopo qualche minuto di connessione se lasciate inattive. Questo bizzarro comportamento si verifica soltanto quando ci si connette da una rete esterna a quella del mio posto di lavoro. Quando ci si connette dalla rete interna dell’ufficio le connessioni durano giorni. Leggi il resto…

Password bloccate per utenti indisciplinati

Uno dei problemi di gestire un computer realmente multiutente, che fornisce accesso anche a utenti non “vicini”, è che potresti non avere il controllo su come gli utenti si scelgono le password. Non è infrequente che l’utente Mario Rossi decida di utilizzare la password mrossi,rossi65 e amenità di questo genere. Fino ad adesso ho gestito pochi utenti esterni a cui non potessi raccomandare giornalmente di tenere una password forte o che non avessero molto a cuore la “salute” delle macchine che gestisco. Oggi mi sono posto il problema…e ho scoperto che la cosa è assai semplice ed è prevista di default in tutti i sistemi Unix, ma proprio perché è così semplice non è facile da trovare il rete. La soluzione?

passwd -n 9999 nomeutente


questo dice al sistema che prima di poter aggiornare la password l’utente deve aspettare 9999 giorni, ovvero poco più di 27 anni…mi sembra un’attesa sufficiente.
Alternativamente si può cambiare a mano il 4° campo della riga utente corrispondente in /etc/shadows